From d91a29c6b7cf4f2886e6e6277eeab2f5614584ca Mon Sep 17 00:00:00 2001 From: "Alberto G. Viu" Date: Thu, 11 Sep 2025 10:09:53 +0200 Subject: [PATCH] Revert "Release 7.1.0" This reverts commit aa4aa15c54d1665a68ef0a7d0aea5e7ab3d00f93. --- .eslintignore | 2 + CHANGELOG.md | 361 ++++++ CHANGELOG_de-DE.md | 356 ++++++ LICENSE.txt | 201 ++++ README.md | 106 ++ composer.json | 63 + docs/de/assets/base.css | 692 +++++++++++ docs/de/assets/base.js | 14 + docs/de/assets/highlight.js | 6 + docs/de/assets/jquery.js | 2 + docs/de/assets/monokai-sublime.css | 83 ++ docs/de/assets/scrollspy.js | 9 + docs/de/assets/sticky-kit.js | 5 + docs/de/documentation.html | 1010 ++++++++++++++++ docs/en/assets/base.css | 692 +++++++++++ docs/en/assets/base.js | 14 + docs/en/assets/highlight.js | 6 + docs/en/assets/jquery.js | 2 + docs/en/assets/monokai-sublime.css | 83 ++ docs/en/assets/scrollspy.js | 9 + docs/en/assets/sticky-kit.js | 5 + docs/en/documentation.html | 1025 +++++++++++++++++ docs/fr/assets/base.css | 692 +++++++++++ docs/fr/assets/base.js | 14 + docs/fr/assets/highlight.js | 6 + docs/fr/assets/jquery.js | 2 + docs/fr/assets/monokai-sublime.css | 83 ++ docs/fr/assets/scrollspy.js | 9 + docs/fr/assets/sticky-kit.js | 5 + docs/fr/documentation.html | 999 ++++++++++++++++ docs/it/assets/base.css | 692 +++++++++++ docs/it/assets/base.js | 14 + docs/it/assets/highlight.js | 6 + docs/it/assets/jquery.js | 2 + docs/it/assets/monokai-sublime.css | 83 ++ docs/it/assets/scrollspy.js | 9 + docs/it/assets/sticky-kit.js | 5 + docs/it/documentation.html | 1010 ++++++++++++++++ .../Controller/ConfigurationController.php | 238 ++++ .../Command/OrderDeliveryStateCommand.php | 59 + .../Handler/OrderDeliveryStateHandler.php | 88 ++ .../Service/OrderDeliveryStateService.php | 197 ++++ .../PaymentMethodConfigurationCommand.php | 62 + .../Command/PaymentMethodDefaultCommand.php | 61 + .../PaymentMethodConfigurationEntity.php | 165 +++ ...entMethodConfigurationEntityCollection.php | 29 + ...entMethodConfigurationEntityDefinition.php | 72 ++ .../PaymentMethodConfigurationService.php | 678 +++++++++++ .../Refund/Controller/RefundController.php | 153 +++ src/Core/Api/Refund/Entity/RefundEntity.php | 145 +++ .../Refund/Entity/RefundEntityCollection.php | 57 + .../Refund/Entity/RefundEntityDefinition.php | 70 ++ src/Core/Api/Refund/Service/RefundService.php | 244 ++++ src/Core/Api/Space/Service/SpaceService.php | 179 +++ .../TransactionCompletionController.php | 92 ++ .../Controller/TransactionController.php | 165 +++ .../Controller/TransactionVoidController.php | 90 ++ .../Transaction/Entity/TransactionEntity.php | 321 ++++++ .../Entity/TransactionEntityCollection.php | 46 + .../Entity/TransactionEntityDefinition.php | 89 ++ .../Transaction/Service/OrderMailService.php | 261 +++++ .../Service/TransactionService.php | 750 ++++++++++++ .../Api/WebHooks/Command/WebHooksCommand.php | 62 + .../WebHooks/Controller/WebHookController.php | 727 ++++++++++++ .../Api/WebHooks/Service/WebHooksService.php | 403 +++++++ ...HookPaymentMethodConfigurationStrategy.php | 47 + .../Strategy/WebHookRefundStrategy.php | 180 +++ .../WebHooks/Strategy/WebHookStrategyBase.php | 498 ++++++++ .../Strategy/WebHookStrategyInterface.php | 35 + .../Strategy/WebHookStrategyManager.php | 120 ++ .../WebHookTransactionInvoiceStrategy.php | 196 ++++ .../Strategy/WebHookTransactionStrategy.php | 169 +++ .../WebhookStrategyActionsInterface.php | 61 + src/Core/Api/WebHooks/Struct/Entity.php | 107 ++ .../Api/WebHooks/Struct/WebHookRequest.php | 204 ++++ .../Checkout/Cart/CustomCartPersister.php | 59 + .../VRPaymentPaymentHandler.php | 226 ++++ .../Command/CreateMerchantCommand.php | 189 +++ src/Core/Settings/Command/SettingsCommand.php | 135 +++ src/Core/Settings/Options/Integration.php | 42 + src/Core/Settings/Service/SettingsService.php | 174 +++ src/Core/Settings/Struct/Settings.php | 271 +++++ .../Controller/AccountOrderController.php | 130 +++ .../Subscriber/AccountOrderSubscriber.php | 83 ++ .../Controller/CheckoutController.php | 568 +++++++++ .../Checkout/Struct/CheckoutPageData.php | 200 ++++ .../Subscriber/CheckoutSubscriber.php | 260 +++++ .../Cookie/VRPaymentCookieProvider.php | 54 + src/Core/Util/Analytics/Analytics.php | 44 + .../Exception/InvalidPayloadException.php | 9 + src/Core/Util/LocaleCodeProvider.php | 189 +++ src/Core/Util/Payload/AbstractPayload.php | 52 + .../CustomProductsLineItemTypes.php | 16 + .../CustomProductsLineItems.php | 131 +++ src/Core/Util/Payload/RefundPayload.php | 171 +++ src/Core/Util/Payload/TransactionPayload.php | 921 +++++++++++++++ src/Core/Util/PaymentMethodUtil.php | 205 ++++ .../Traits/VRPaymentPaymentPluginTrait.php | 115 ++ ...156974PaymentMethodConfigurationEntity.php | 60 + .../Migration1590156974TransactionEntity.php | 69 ++ .../Migration1590646356OrderEntity.php | 47 + .../Migration1590646356RefundEntity.php | 59 + .../Migration1590646356TransactionEntity.php | 48 + ...701047PaymentMethodConfigurationEntity.php | 44 + .../Migration1605701048TransactionEntity.php | 78 ++ .../Migration1605701049StateMachineEntity.php | 89 ++ .../Migration1684240994TransactionEntity.php | 48 + .../api/vrpayment-configuration.service.js | 141 +++ .../service/api/vrpayment-refund.service.js | 110 ++ ...rpayment-transaction-completion.service.js | 48 + .../api/vrpayment-transaction-void.service.js | 48 + .../api/vrpayment-transaction.service.js | 71 ++ .../api/vrpayment-webhook-register.service.js | 44 + .../src/init/api-service.init.js | 42 + src/Resources/app/administration/src/main.js | 3 + .../index.html.twig | 24 + .../index.js | 86 ++ .../index.html.twig | 26 + .../index.js | 103 ++ .../index.html.twig | 31 + .../index.js | 103 ++ .../index.html.twig | 16 + .../index.js | 94 ++ .../index.html.twig | 31 + .../vrpayment-order-action-refund/index.js | 92 ++ .../index.html.twig | 25 + .../vrpayment-order-action-void/index.js | 138 +++ .../extension/sw-order/index.js | 75 ++ .../extension/sw-order/sw-order.html.twig | 16 + .../extension/sw-order/sw-order.scss | 11 + .../src/module/vrpayment-order/index.js | 40 + .../vrpayment-order-detail/index.html.twig | 200 ++++ .../page/vrpayment-order-detail/index.js | 448 +++++++ .../page/vrpayment-order-detail/index.scss | 7 + .../module/vrpayment-order/snippet/de-DE.json | 116 ++ .../module/vrpayment-order/snippet/en-GB.json | 117 ++ .../module/vrpayment-order/snippet/fr-FR.json | 116 ++ .../module/vrpayment-order/snippet/it-IT.json | 116 ++ .../module/vrpayment-settings/acl/index.js | 58 + .../index.html.twig | 43 + .../sw-vrpayment-advanced-options/index.js | 66 ++ .../sw-vrpayment-credentials/index.html.twig | 95 ++ .../sw-vrpayment-credentials/index.js | 105 ++ .../sw-vrpayment-options/index.html.twig | 97 ++ .../component/sw-vrpayment-options/index.js | 77 ++ .../index.html.twig | 31 + .../sw-vrpayment-settings-icon/index.js | 7 + .../index.html.twig | 25 + .../sw-vrpayment-storefront-options/index.js | 62 + .../src/module/vrpayment-settings/index.js | 61 + .../configuration-constants.js | 25 + .../page/vrpayment-settings/index.html.twig | 146 +++ .../page/vrpayment-settings/index.js | 317 +++++ .../vrpayment-settings/snippet/de-DE.json | 105 ++ .../vrpayment-settings/snippet/en-GB.json | 105 ++ .../vrpayment-settings/snippet/fr-FR.json | 105 ++ .../vrpayment-settings/snippet/it-IT.json | 105 ++ .../v-r-payment-payment.js | 1 + src/Resources/app/storefront/src/main.js | 16 + .../app/storefront/src/scss/base.scss | 65 ++ .../vrpayment-checkout-plugin.plugin.js | 27 + src/Resources/config/packages/monolog.yaml | 9 + src/Resources/config/plugin.png | Bin 0 -> 665 bytes src/Resources/config/routes.xml | 8 + src/Resources/config/services.xml | 21 + .../services/core/api/configuration.xml | 23 + .../core/api/order_delivery_state.xml | 24 + .../core/api/payment_method_configuration.xml | 42 + .../config/services/core/api/refund.xml | 37 + .../config/services/core/api/space.xml | 18 + .../config/services/core/api/transaction.xml | 68 ++ .../config/services/core/api/webhooks.xml | 84 ++ .../config/services/core/checkout.xml | 27 + .../config/services/core/settings.xml | 29 + .../services/core/storefront/account.xml | 32 + .../services/core/storefront/checkout.xml | 46 + src/Resources/config/services/core/util.xml | 23 + .../administration/.vite/entrypoints.json | 25 + .../public/administration/.vite/manifest.json | 11 + .../assets/v-r-payment-payment-C6eiDWfX.js | 2 + .../v-r-payment-payment-C6eiDWfX.js.map | 1 + .../assets/v-r-payment-payment-D4AH6HY2.css | 1 + src/Resources/public/storefront/js/app.js | 201 ++++ .../snippet/storefront/vrpayment.de-DE.json | 27 + .../snippet/storefront/vrpayment.en-GB.json | 27 + .../snippet/storefront/vrpayment.fr-FR.json | 27 + .../snippet/storefront/vrpayment.it-IT.json | 27 + .../order-history/order-item.html.twig | 18 + .../page/checkout/order/vrpayment.html.twig | 154 +++ .../order/vrpayment_payment.html.twig | 24 + src/VRPaymentPayment.php | 140 +++ 191 files changed, 25741 insertions(+) create mode 100644 .eslintignore create mode 100644 CHANGELOG.md create mode 100644 CHANGELOG_de-DE.md create mode 100644 LICENSE.txt create mode 100644 README.md create mode 100644 composer.json create mode 100644 docs/de/assets/base.css create mode 100644 docs/de/assets/base.js create mode 100644 docs/de/assets/highlight.js create mode 100644 docs/de/assets/jquery.js create mode 100644 docs/de/assets/monokai-sublime.css create mode 100644 docs/de/assets/scrollspy.js create mode 100644 docs/de/assets/sticky-kit.js create mode 100644 docs/de/documentation.html create mode 100644 docs/en/assets/base.css create mode 100644 docs/en/assets/base.js create mode 100644 docs/en/assets/highlight.js create mode 100644 docs/en/assets/jquery.js create mode 100644 docs/en/assets/monokai-sublime.css create mode 100644 docs/en/assets/scrollspy.js create mode 100644 docs/en/assets/sticky-kit.js create mode 100644 docs/en/documentation.html create mode 100644 docs/fr/assets/base.css create mode 100644 docs/fr/assets/base.js create mode 100644 docs/fr/assets/highlight.js create mode 100644 docs/fr/assets/jquery.js create mode 100644 docs/fr/assets/monokai-sublime.css create mode 100644 docs/fr/assets/scrollspy.js create mode 100644 docs/fr/assets/sticky-kit.js create mode 100644 docs/fr/documentation.html create mode 100644 docs/it/assets/base.css create mode 100644 docs/it/assets/base.js create mode 100644 docs/it/assets/highlight.js create mode 100644 docs/it/assets/jquery.js create mode 100644 docs/it/assets/monokai-sublime.css create mode 100644 docs/it/assets/scrollspy.js create mode 100644 docs/it/assets/sticky-kit.js create mode 100644 docs/it/documentation.html create mode 100644 src/Core/Api/Configuration/Controller/ConfigurationController.php create mode 100644 src/Core/Api/OrderDeliveryState/Command/OrderDeliveryStateCommand.php create mode 100644 src/Core/Api/OrderDeliveryState/Handler/OrderDeliveryStateHandler.php create mode 100644 src/Core/Api/OrderDeliveryState/Service/OrderDeliveryStateService.php create mode 100644 src/Core/Api/PaymentMethodConfiguration/Command/PaymentMethodConfigurationCommand.php create mode 100644 src/Core/Api/PaymentMethodConfiguration/Command/PaymentMethodDefaultCommand.php create mode 100644 src/Core/Api/PaymentMethodConfiguration/Entity/PaymentMethodConfigurationEntity.php create mode 100644 src/Core/Api/PaymentMethodConfiguration/Entity/PaymentMethodConfigurationEntityCollection.php create mode 100644 src/Core/Api/PaymentMethodConfiguration/Entity/PaymentMethodConfigurationEntityDefinition.php create mode 100644 src/Core/Api/PaymentMethodConfiguration/Service/PaymentMethodConfigurationService.php create mode 100644 src/Core/Api/Refund/Controller/RefundController.php create mode 100644 src/Core/Api/Refund/Entity/RefundEntity.php create mode 100644 src/Core/Api/Refund/Entity/RefundEntityCollection.php create mode 100644 src/Core/Api/Refund/Entity/RefundEntityDefinition.php create mode 100644 src/Core/Api/Refund/Service/RefundService.php create mode 100644 src/Core/Api/Space/Service/SpaceService.php create mode 100644 src/Core/Api/Transaction/Controller/TransactionCompletionController.php create mode 100644 src/Core/Api/Transaction/Controller/TransactionController.php create mode 100644 src/Core/Api/Transaction/Controller/TransactionVoidController.php create mode 100644 src/Core/Api/Transaction/Entity/TransactionEntity.php create mode 100644 src/Core/Api/Transaction/Entity/TransactionEntityCollection.php create mode 100644 src/Core/Api/Transaction/Entity/TransactionEntityDefinition.php create mode 100644 src/Core/Api/Transaction/Service/OrderMailService.php create mode 100644 src/Core/Api/Transaction/Service/TransactionService.php create mode 100644 src/Core/Api/WebHooks/Command/WebHooksCommand.php create mode 100644 src/Core/Api/WebHooks/Controller/WebHookController.php create mode 100644 src/Core/Api/WebHooks/Service/WebHooksService.php create mode 100644 src/Core/Api/WebHooks/Strategy/WebHookPaymentMethodConfigurationStrategy.php create mode 100644 src/Core/Api/WebHooks/Strategy/WebHookRefundStrategy.php create mode 100644 src/Core/Api/WebHooks/Strategy/WebHookStrategyBase.php create mode 100644 src/Core/Api/WebHooks/Strategy/WebHookStrategyInterface.php create mode 100644 src/Core/Api/WebHooks/Strategy/WebHookStrategyManager.php create mode 100644 src/Core/Api/WebHooks/Strategy/WebHookTransactionInvoiceStrategy.php create mode 100644 src/Core/Api/WebHooks/Strategy/WebHookTransactionStrategy.php create mode 100644 src/Core/Api/WebHooks/Strategy/WebhookStrategyActionsInterface.php create mode 100644 src/Core/Api/WebHooks/Struct/Entity.php create mode 100644 src/Core/Api/WebHooks/Struct/WebHookRequest.php create mode 100644 src/Core/Checkout/Cart/CustomCartPersister.php create mode 100644 src/Core/Checkout/PaymentHandler/VRPaymentPaymentHandler.php create mode 100644 src/Core/Settings/Command/CreateMerchantCommand.php create mode 100644 src/Core/Settings/Command/SettingsCommand.php create mode 100644 src/Core/Settings/Options/Integration.php create mode 100644 src/Core/Settings/Service/SettingsService.php create mode 100644 src/Core/Settings/Struct/Settings.php create mode 100644 src/Core/Storefront/Account/Controller/AccountOrderController.php create mode 100644 src/Core/Storefront/Account/Subscriber/AccountOrderSubscriber.php create mode 100644 src/Core/Storefront/Checkout/Controller/CheckoutController.php create mode 100644 src/Core/Storefront/Checkout/Struct/CheckoutPageData.php create mode 100644 src/Core/Storefront/Checkout/Subscriber/CheckoutSubscriber.php create mode 100644 src/Core/Storefront/Framework/Cookie/VRPaymentCookieProvider.php create mode 100644 src/Core/Util/Analytics/Analytics.php create mode 100644 src/Core/Util/Exception/InvalidPayloadException.php create mode 100644 src/Core/Util/LocaleCodeProvider.php create mode 100644 src/Core/Util/Payload/AbstractPayload.php create mode 100644 src/Core/Util/Payload/CustomProducts/CustomProductsLineItemTypes.php create mode 100644 src/Core/Util/Payload/CustomProducts/CustomProductsLineItems.php create mode 100644 src/Core/Util/Payload/RefundPayload.php create mode 100644 src/Core/Util/Payload/TransactionPayload.php create mode 100644 src/Core/Util/PaymentMethodUtil.php create mode 100644 src/Core/Util/Traits/VRPaymentPaymentPluginTrait.php create mode 100644 src/Migration/Migration1590156974PaymentMethodConfigurationEntity.php create mode 100644 src/Migration/Migration1590156974TransactionEntity.php create mode 100644 src/Migration/Migration1590646356OrderEntity.php create mode 100644 src/Migration/Migration1590646356RefundEntity.php create mode 100644 src/Migration/Migration1590646356TransactionEntity.php create mode 100644 src/Migration/Migration1605701047PaymentMethodConfigurationEntity.php create mode 100644 src/Migration/Migration1605701048TransactionEntity.php create mode 100644 src/Migration/Migration1605701049StateMachineEntity.php create mode 100644 src/Migration/Migration1684240994TransactionEntity.php create mode 100644 src/Resources/app/administration/src/core/service/api/vrpayment-configuration.service.js create mode 100644 src/Resources/app/administration/src/core/service/api/vrpayment-refund.service.js create mode 100644 src/Resources/app/administration/src/core/service/api/vrpayment-transaction-completion.service.js create mode 100644 src/Resources/app/administration/src/core/service/api/vrpayment-transaction-void.service.js create mode 100644 src/Resources/app/administration/src/core/service/api/vrpayment-transaction.service.js create mode 100644 src/Resources/app/administration/src/core/service/api/vrpayment-webhook-register.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/vrpayment-order/component/vrpayment-order-action-completion/index.html.twig create mode 100644 src/Resources/app/administration/src/module/vrpayment-order/component/vrpayment-order-action-completion/index.js create mode 100644 src/Resources/app/administration/src/module/vrpayment-order/component/vrpayment-order-action-refund-by-amount/index.html.twig create mode 100644 src/Resources/app/administration/src/module/vrpayment-order/component/vrpayment-order-action-refund-by-amount/index.js create mode 100644 src/Resources/app/administration/src/module/vrpayment-order/component/vrpayment-order-action-refund-partial/index.html.twig create mode 100644 src/Resources/app/administration/src/module/vrpayment-order/component/vrpayment-order-action-refund-partial/index.js create mode 100644 src/Resources/app/administration/src/module/vrpayment-order/component/vrpayment-order-action-refund-selected/index.html.twig create mode 100644 src/Resources/app/administration/src/module/vrpayment-order/component/vrpayment-order-action-refund-selected/index.js create mode 100644 src/Resources/app/administration/src/module/vrpayment-order/component/vrpayment-order-action-refund/index.html.twig create mode 100644 src/Resources/app/administration/src/module/vrpayment-order/component/vrpayment-order-action-refund/index.js create mode 100644 src/Resources/app/administration/src/module/vrpayment-order/component/vrpayment-order-action-void/index.html.twig create mode 100644 src/Resources/app/administration/src/module/vrpayment-order/component/vrpayment-order-action-void/index.js create mode 100644 src/Resources/app/administration/src/module/vrpayment-order/extension/sw-order/index.js create mode 100644 src/Resources/app/administration/src/module/vrpayment-order/extension/sw-order/sw-order.html.twig create mode 100644 src/Resources/app/administration/src/module/vrpayment-order/extension/sw-order/sw-order.scss create mode 100644 src/Resources/app/administration/src/module/vrpayment-order/index.js create mode 100644 src/Resources/app/administration/src/module/vrpayment-order/page/vrpayment-order-detail/index.html.twig create mode 100644 src/Resources/app/administration/src/module/vrpayment-order/page/vrpayment-order-detail/index.js create mode 100644 src/Resources/app/administration/src/module/vrpayment-order/page/vrpayment-order-detail/index.scss create mode 100644 src/Resources/app/administration/src/module/vrpayment-order/snippet/de-DE.json create mode 100644 src/Resources/app/administration/src/module/vrpayment-order/snippet/en-GB.json create mode 100644 src/Resources/app/administration/src/module/vrpayment-order/snippet/fr-FR.json create mode 100644 src/Resources/app/administration/src/module/vrpayment-order/snippet/it-IT.json create mode 100644 src/Resources/app/administration/src/module/vrpayment-settings/acl/index.js create mode 100644 src/Resources/app/administration/src/module/vrpayment-settings/component/sw-vrpayment-advanced-options/index.html.twig create mode 100644 src/Resources/app/administration/src/module/vrpayment-settings/component/sw-vrpayment-advanced-options/index.js create mode 100644 src/Resources/app/administration/src/module/vrpayment-settings/component/sw-vrpayment-credentials/index.html.twig create mode 100644 src/Resources/app/administration/src/module/vrpayment-settings/component/sw-vrpayment-credentials/index.js create mode 100644 src/Resources/app/administration/src/module/vrpayment-settings/component/sw-vrpayment-options/index.html.twig create mode 100644 src/Resources/app/administration/src/module/vrpayment-settings/component/sw-vrpayment-options/index.js create mode 100644 src/Resources/app/administration/src/module/vrpayment-settings/component/sw-vrpayment-settings-icon/index.html.twig create mode 100644 src/Resources/app/administration/src/module/vrpayment-settings/component/sw-vrpayment-settings-icon/index.js create mode 100644 src/Resources/app/administration/src/module/vrpayment-settings/component/sw-vrpayment-storefront-options/index.html.twig create mode 100644 src/Resources/app/administration/src/module/vrpayment-settings/component/sw-vrpayment-storefront-options/index.js create mode 100644 src/Resources/app/administration/src/module/vrpayment-settings/index.js create mode 100644 src/Resources/app/administration/src/module/vrpayment-settings/page/vrpayment-settings/configuration-constants.js create mode 100644 src/Resources/app/administration/src/module/vrpayment-settings/page/vrpayment-settings/index.html.twig create mode 100644 src/Resources/app/administration/src/module/vrpayment-settings/page/vrpayment-settings/index.js create mode 100644 src/Resources/app/administration/src/module/vrpayment-settings/snippet/de-DE.json create mode 100644 src/Resources/app/administration/src/module/vrpayment-settings/snippet/en-GB.json create mode 100644 src/Resources/app/administration/src/module/vrpayment-settings/snippet/fr-FR.json create mode 100644 src/Resources/app/administration/src/module/vrpayment-settings/snippet/it-IT.json create mode 100644 src/Resources/app/storefront/dist/storefront/js/v-r-payment-payment/v-r-payment-payment.js create mode 100644 src/Resources/app/storefront/src/main.js create mode 100644 src/Resources/app/storefront/src/scss/base.scss create mode 100644 src/Resources/app/storefront/src/vrpayment-checkout-plugin/vrpayment-checkout-plugin.plugin.js create mode 100644 src/Resources/config/packages/monolog.yaml 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/config/services/core/api/configuration.xml create mode 100644 src/Resources/config/services/core/api/order_delivery_state.xml create mode 100644 src/Resources/config/services/core/api/payment_method_configuration.xml create mode 100644 src/Resources/config/services/core/api/refund.xml create mode 100644 src/Resources/config/services/core/api/space.xml create mode 100644 src/Resources/config/services/core/api/transaction.xml create mode 100644 src/Resources/config/services/core/api/webhooks.xml create mode 100644 src/Resources/config/services/core/checkout.xml create mode 100644 src/Resources/config/services/core/settings.xml create mode 100644 src/Resources/config/services/core/storefront/account.xml create mode 100644 src/Resources/config/services/core/storefront/checkout.xml create mode 100644 src/Resources/config/services/core/util.xml create mode 100644 src/Resources/public/administration/.vite/entrypoints.json create mode 100644 src/Resources/public/administration/.vite/manifest.json create mode 100644 src/Resources/public/administration/assets/v-r-payment-payment-C6eiDWfX.js create mode 100644 src/Resources/public/administration/assets/v-r-payment-payment-C6eiDWfX.js.map create mode 100644 src/Resources/public/administration/assets/v-r-payment-payment-D4AH6HY2.css create mode 100644 src/Resources/public/storefront/js/app.js create mode 100644 src/Resources/snippet/storefront/vrpayment.de-DE.json create mode 100644 src/Resources/snippet/storefront/vrpayment.en-GB.json create mode 100644 src/Resources/snippet/storefront/vrpayment.fr-FR.json create mode 100644 src/Resources/snippet/storefront/vrpayment.it-IT.json create mode 100644 src/Resources/views/storefront/page/account/order-history/order-item.html.twig create mode 100644 src/Resources/views/storefront/page/checkout/order/vrpayment.html.twig create mode 100644 src/Resources/views/storefront/page/checkout/order/vrpayment_payment.html.twig create mode 100644 src/VRPaymentPayment.php diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..027c14a --- /dev/null +++ b/.eslintignore @@ -0,0 +1,2 @@ +src/Resources/app/storefront/dist/storefront/js/* +src/Resources/public/administration/js/* \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..f2dfb16 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,361 @@ +# 7.0.1 +## Feature +- Add plugin version metric + +## Bugfix +- Fixed error message when refund amount exceeds total +- Fixed bug where only 25 sales channels showed in the dropdown +- Removed erroneous logs + +# 7.0.0 +- Compatibility with Shopware 6.7.0 + +# 6.1.14 +- Disable Recreate Cart for Headless Storefront Order +- Added the correct Exception Type to the finalize method + +# 6.1.13 +- Updated English documentation +- Added French, German and Italian documentation + +# 6.1.12 +- Compatibility with 6.6.10.x +- Prevent duplicate transactions being created when the first times out +- Fix for error when changing space credentials +- Payment status now shows refunded/partially refunded +- Order delivery status now shows 'open' instead of 'hold' + +# 6.1.11 +- Implement payment page integration. +- Fixed bug with duplicate payment methods appearing + +# 6.1.10 +- Multiple/bulk refund for line item. +- Partial line item refund. +- Fixed refund by line item option. +- Composer dependencies are managed by Shopware system + +# 6.1.9 +- Prevents calling a non existing method in webhook invocation. +- Prevents error if delivery data is empty +- Refactored composer.json, completing its require section. + +# 6.1.8 +- Implemented key signing +- Tax rate adjustment when products have different tax rates + +# 6.1.7 +- Fixed messaging which showed that shipping and billing address were always the same + +# 6.1.6 +- Bumped sdk version + +# 6.1.5 +- Support for Shopware 6.6.3.1 and Vue 3 + +# 6.1.4 +- Improved plugin's settings form +- Support for Shopware 6.6.2.0 + +# 6.1.3 +- Solvency check support for Powerpay and MF Group Invoice payment methods +- Improved handling of abandoned transactions + +# 6.1.2 +- Fixed redirect to confirmation page after reload + +# 6.1.1 +- Fixed deprecated OrderNotFoundException + +# 6.1.0 +- Fixed checkout issues after deactivating/activating plugin +- Fixed plugin uninstall action +- Fixed invoice payment method email function when order is shipped + +# 6.0.0 +- Support for Shopware 6.6 + +# 5.0.7 +- Fix for refunds when a discount code is used + +# 5.0.6 +- Version bump for marketplace release + +# 5.0.5 +- Fixed an issue where all payment methods disappeared upon activation of the plugin. +- Fixed an issue where the shopping cart doubled in quantity if the customer placed an item in the shopping cart, went to payment via TWINT, cancelled the transaction in TWINT using "Cancel payment", and was then redirected back to the store. + +# 5.0.4 +- Adjust documentation and release command + +# 5.0.3 +- Support of PHP 8.2 +- Cast to string the option of product attribute. +- If delivery is null do not try to hold it. + +# 5.0.2 +- Fix bug which happens when pressing back to shop or home clears cart. + +# 5.0.1 +- Adjust documentation + +# 5.0.0 +- Update composer file to only support 6.5 + +# 4.0.56 +- Adjustment of the documentation + +# 4.0.54 +- Support of Shopware 6.5 +- Support of latest PHP SDK 3.2.0 + +# 4.0.53 +- Creation of a new column in the transaction table called erp_merchant_id + +# 4.0.52 +- Support of Shopware 6.4.20.1 + +# 4.0.51 +- Wrong link format in error message. + +# 4.0.45 +- Add additional information of the Credit Card (Validity Date, Pseudo Credit Card number and PayID) for transaction using this Payment Method +- Compatibility SW v6.4.17.1 +- +# 4.0.42 +- Rollback to remove functionality of sending version to payment portal + +# 4.0.41 +- Sends to the payment portal a more specific version of shopware being used. + +# 4.0.36 +- Compatibility SW v6.4.13.0 + +# 4.0.29 +- Fix to hide birthdate field if it's already provided +- Tested against SW v6.4.9.0 + +# 4.0.28 +- Added italian translations +- Tested against SW v6.4.9.0 + +# 4.0.26 +- Added documentation around flow builder + +# 4.0.25 +- Fixed transaction invoice instant payment handling. +- Tested with v6.4.7.0 + +# 4.0.24 +- Added refunds by amount + +# 4.0.23 +- Fixed cart recreate function for custom products + +# 4.0.22 +- Added support for French + +# 4.0.21 +- Custom products options displayed as separate line items + +# 4.0.20 +- Fixed company name for shipping address + +# 4.0.17 +- Fixed settings to import webhooks and payment methods + +# 4.0.16 +- Added settings to control update of webhooks and payment methods + +# 4.0.15 +- Adjust VR Payment/SW6 documentation - how to do refunds + +# 4.0.14 +- Support for Shopware 6.4.6 + +# 4.0.13 +- Loader Chrome IOS fix + +# 4.0.12 +- Security fix + +# 4.0.11 +- Reverted auto-submit on empty iframe as it is not working properly at all cases + +# 4.0.10 +- Fixed "Allow payment change after checkout" option behavior + +# 4.0.9 +- Allow to mark payment status as paid from status reminded + +# 4.0.8 +- Checkout form auto submission implemented when iFrame returns no input fields + +# 4.0.7 +- Fix Transaction Rollback error on unsupported languages + +# 4.0.6 +- Fix for delivery state change error + +# 4.0.5 +- Fixed plugin uninstall action + +# 4.0.4 +- Line item based refunds + +# 4.0.3 +- Update SDK + +# 4.0.2 +- Fixed shipping line item name + +# 4.0.1 +- Fixed tax calculation for custom products + +# 4.0.0 +- Support for Shopware 6.4 + +# 3.1.0 +- Support for Custom Products plugin + +# 3.0.0 +- Fix transaction versioning +- Update SDK + +# 2.1.1 +- Round amounts +- Redirect if the cart can not be recreated + +# 2.1.0 +- Fix email issues + +# 2.0.0 +- Fix cart recreation on promotions +- Remove availability rules +- Handle orders less than or equal to zero + +# 1.4.3 +- Silence missing order webhook errors +- Fix iframe breakout + +# 1.4.2 +- Fix payment method bug on first time install + +# 1.4.1 +- Fetch active payment methods only + +# 1.4.0 +- Fix payment method availability rule +- Fix email sending +- Cancel failed orders + +# 1.3.0 +- Update payment method syncing + +# 1.2.0 +- Add payment method availability rule +- Hardcoded system languages + +# 1.1.27 +- Retry orders on unavailable payment method + +# 1.1.26 +- Fix locales and translations + +# 1.1.25 +- Fix Email sending + +# 1.1.24 +- Fix webhook response +- Fix translation +- Prepare for Shopware 6.4 + +# 1.1.23 +- Submit payment form when iframe has no fields + +# 1.1.22 +- Order invoice download setting + +# 1.1.21 +- Remove hardcoded Shopware API version + +# 1.1.20 +- Update webhook URLs on plugin update +- Add translations +- Fix email bug + +# 1.1.19 +- Allow customers to download order invoices + +# 1.1.18 +- Test against Shopware 6.3 +- Fix error on invalid space id +- Remove hardcoded Shopware API version + +# 1.1.17 +- Use DAL on webhook locks + +# 1.1.16 +- Only provide translations for available languages +- Return CustomerCanceledAsyncPaymentException on cancelled transactions +- Update SDK to 2.1.1 + +# 1.1.15 +- Send customer first name and last name from billing and shipping profiles +- Respect Shop URL + +# 1.1.14 +- Add cookies to the cookie manager +- Resize icon to 40px * 40px +- Fix line item attributes + +# 1.1.13 +- Include vendor folder in Shopware store releases + +# 1.1.12 +- Update doc path + +# 1.1.11 +- Add documentation + +# 1.1.10 +- Stop responding with server errors when orders are not found + +# 1.1.9 +- Put try catch on webhook install + +# 1.1.8 +- Remove unhelpful tickets info in release comments + +# 1.1.7 +- Implement promotions +- Code refactoring + +# 1.1.6 +- Disable sales channel selection on showcases +- Add product attributes to transaction payload + +# 1.1.5 +- Fix settings bug + +# 1.1.4 +- Disable changing credentials on the showcases + +# 1.1.3 +- Make line item consistency default +- Confirm transaction right away +- Update settings descriptions + +# 1.1.2 +- Prepare internal server side install for showcases and demos + +# 1.1.1 +- Stop default emails being sent +- Prettify payment page + +# 1.1.0 +- Handle empty/default Settings values +- Save refunds to db, and reload order tab on changes + +# 1.0.0 +- First version of the VRPayment integrations for Shopware 6 diff --git a/CHANGELOG_de-DE.md b/CHANGELOG_de-DE.md new file mode 100644 index 0000000..57e960f --- /dev/null +++ b/CHANGELOG_de-DE.md @@ -0,0 +1,356 @@ +# 7.0.1 +- Plugin-Versionsmetrik hinzugefügt +- Fehlermeldung behoben, wenn der Rückerstattungsbetrag den Gesamtbetrag überschreitet +- Fehler behoben, bei dem nur 25 Vertriebskanäle in der Dropdown-Liste angezeigt wurden +- Fehlerhafte Protokolle entfernt + +# 7.0.0 +- Kompatibilität mit Shopware 6.7.0 + +# 6.1.14 +– Warenkorb neu erstellen für Headless Storefront Order deaktivieren +– Der korrekte Ausnahmetyp wurde zur Finalisierungsmethode hinzugefügt + +# 6.1.13 +– Englische Dokumentation aktualisiert +– Französische, deutsche und italienische Dokumentation hinzugefügt + +# 6.1.12 +- Kompatibilität mit 6.6.10.x +- Verhindern Sie, dass beim ersten Timeout doppelte Transaktionen erstellt werden +- Fehler beim Ändern der Space-Anmeldeinformationen behoben +- Der Zahlungsstatus zeigt jetzt „erstattet/teilweise erstattet“ an +- Der Lieferstatus der Bestellung wird jetzt „Offen“ statt „Halten“ angezeigt. + +# 6.1.11 +- Integration der Zahlungsseite implementieren. +- Fehler mit doppelten angezeigten Zahlungsmethoden behoben + +# 6.1.10 +- Mehrfache/gesammelte Rückerstattung für Einzelposten. +- Teilweise Rückerstattung von Einzelposten. +- Fehler bei der Rückerstattung durch die Option Einzelposten behoben. +- Composer-Abhängigkeiten werden vom Shopware-System verwaltet + +# 6.1.9 +- Verhindert das Aufrufen einer nicht existierenden Methode bei der Webhook-Ausführung. +- Verhindert einen Fehler, wenn Lieferdaten leer sind. +- Composer.json überarbeitet und den Abschnitt "require" vervollständigt. + +# 6.1.8 +- Schlüsselsignatur implementiert +- Steuersatzanpassung, wenn Produkte unterschiedliche Steuersätze haben + +# 6.1.7 +- Meldung behoben, die anzeigte, dass Versand- und Rechnungsadresse immer identisch waren + +# 6.1.6 +- Versionserhöhung für das SDK + +# 6.1.5 +- Unterstützung von Shopware 6.6.3.1 und Vue 3 + +# 6.1.4 +- Das Einstellungsformular des Plugins wurde verbessert +- Unterstützung von Shopware 6.6.2.0 + +# 6.1.3 +- Unterstützung der Bonitätsprüfung für die Zahlungsmethoden Powerpay und MF Group Invoice +- Verbesserte Handhabung abgebrochener Transaktionen + +# 6.1.2 +- Die Weiterleitung zur Bestätigungsseite nach dem Neuladen wurde behoben + +# 6.1.1 +- Fixed deprecated OrderNotFoundException + +# 6.1.0 +- Checkout-Probleme nach dem Deaktivieren/Aktivieren des Plugins behoben +- Plugin-Deinstallationsaktion behoben +- Die E-Mail-Funktion für die Rechnungszahlungsmethode beim Versand der Bestellung wurde korrigiert + +# 6.0.0 +- Unterstützung von Shopware 6.6 + +# 5.0.7 +- Fix für Rückerstattungen, wenn ein Rabattcode verwendet wird + +# 5.0.6 +- Versionserhöhung für die Marktveröffentlichung + +# 5.0.5 +– Es wurde ein Problem behoben, bei dem alle Zahlungsmethoden nach der Aktivierung des Plugins verschwanden. +- Es wurde ein Problem behoben, bei dem sich die Warenkorbmenge verdoppelte, wenn der Kunde einen Artikel in den Warenkorb legte, über TWINT zur Zahlung ging, die Transaktion in TWINT mit „Zahlung stornieren“ abbrach und anschliessend zurück zum Shop weitergeleitet wurde. + +# 5.0.4 +- Dokumentation und Freigabebefehl anpassen + +# 5.0.3 +- Unterstützung des neuesten PHP 8.2 +– Umwandeln, um die Option des Produktattributs in einen String umzuwandeln. +- Wenn die Lieferung null ist, versuchen Sie nicht, sie zurückzuhalten. + +# 5.0.2 +- Behebung eines Fehlers, der auftritt, wenn Sie auf „Zurück zum Shop“ oder „Zuhause“ drücken, um den Warenkorb zu löschen. + +# 5.0.1 +- Anpassung der Dokumentation + +# 5.0.0 +- Aktualisieren Sie die Composer-Datei so, dass sie nur 6.5 unterstützt + +# 4.0.56 +- Anpassung der Dokumentation + +# 4.0.54 +- Unterstützung von Shopware 6.5 +- Unterstützung des neuesten PHP SDK 3.2.0 + +# 4.0.53 +- Erstellung einer neuen Spalte in der Transaktionstabelle mit dem Namen erp_merchant_id + +# 4.0.52 +- Unterstützung von Shopware 6.4.20.1 + +# 4.0.51 +- Falsches Linkformat in der Fehlermeldung. + +# 4.0.50 +- Steuerinformationen wurden von der Versands- zu der Rechnungsstellung verschoben. +- Teilweise war die Synchronisierung der Portal Daten zu SW6 unvollständig. +- Lösen eines Fehlers: Der bezahlte Betrag im Portal wurde nicht an SW6 gemeldet. +- Lösen eines Fehlers: Nach Wechsel der Zahlungsmethode im Checkout, wurde der Zahlungsstatus auf "bezahlt" gesetzt. +- Lösen eines Fehlers: Beim Auflisten der Zahlungsmethoden durch den Kunden im Checkout Prozess. + +# 4.0.45 +- Fügen Sie zusätzliche Informationen der Kreditkarte (Gültigkeitsdatum, Pseudo-Kreditkartennummer und PayID) für Transaktionen mit dieser Zahlungsmethode hinzu +- Getestet mit SW v6.4.17.1 +- +# 4.0.29 +- Korrektur zum Ausblenden des Geburtsdatumsfelds, wenn es bereits vorhanden ist +- Getestet mit SW v6.4.9.0 + +# 4.0.28 +- Italienische Übersetzungen hinzugefügt +- Getestet mit SW v6.4.9.0 + +# 4.0.26 +- Dokumentation zum Flow Builder hinzugefügt + +# 4.0.25 +- Die Handhabung der sofortigen Zahlung von Transaktionsrechnungen wurde korrigiert. +- Getestet mit v6.4.7.0 + +# 4.0.24 +- Rückerstattungen nach Betrag hinzugefügt + +# 4.0.23 +- Korrigierte Warenkorb-Neuerstellungsfunktion für benutzerdefinierte Produkte + +# 4.0.22 +- Unterstützung für Französisch hinzugefügt + +# 4.0.21 +- Benutzerdefinierte Produktoptionen werden als separate Einzelposten angezeigt + +# 4.0.20 +- Fester Firmenname für Lieferadresse + +# 4.0.17 +- Einstellungen zum Importieren von Webhooks und Zahlungsmethoden korrigiert + +# 4.0.16 +- Einstellungen zur Steuerung der Aktualisierung von Webhooks und Zahlungsmethoden hinzugefügt + +# 4.0.15 +- VR Payment/SW6-Dokumentation anpassen – wie man Rückerstattungen durchführt + +# 4.0.14 +- Unterstützung für Shopware 6.4.6 + +# 4.0.13 +- Loader Chrome IOS beheben + +# 4.0.12 +- Implementierte Sicherheitskorrektur + +# 4.0.11 +- Automatisches Senden bei leerem iframe zurückgesetzt, da es nicht in allen Fällen richtig funktioniert + +# 4.0.10 +- Das Verhalten der Option "Zahlungsänderung nach der Kasse zulassen" behoben + +# 4.0.9 +- Erlaube, den Zahlungsstatus als bezahlt ab Status erinnert zu markieren + +# 4.0.8 +- Automatische Übermittlung des Checkout-Formulars implementiert, wenn iFrame keine Eingabefelder zurückgibt + +# 4.0.7 +- Behebung des Transaktions-Rollback-Fehlers in nicht unterstützten Sprachen + +# 4.0.6 +- Fehler beim Ändern des Lieferstatus behoben + +# 4.0.5 +- Deinstallation Aktion des Plugins behoben + +# 4.0.4 +- Erstattungen von Werbebuchungen + +# 4.0.3 +- Aktualisieren Sie das SDK + +# 4.0.2 +- Der Name der Versand-Einzelposten wurde korrigiert + +# 4.0.1 +- Feste Steuerberechnung für kundenspezifische Produkte + +# 4.0.0 +- Unterstützung für Shopware 6.4 + +# 3.1.0 +- Unterstützung für Custom Products Plugin + +# 3.0.0 +- Korrigieren Sie die Transaktionsversionierung +- Aktualisieren Sie das SDK + +# 2.1.1 +- Runde Beträge +- Weiterleiten, wenn der Wagen nicht neu erstellt werden kann + +# 2.1.0 +- E-Mail-Probleme behoben + +# 2.0.0 +- Warenkorb-Wiederherstellung bei Werbeaktionen korrigiert +- Verfügbarkeitsregeln entfernt +- Verbessertes Behandeln von Aufträge kleiner oder gleich Null + +# 1.4.3 +- Fehlende Webhook-Fehler ausschließen +- Iframe-Ausbruch behoben + +# 1.4.2 +- Behebung des Fehlers bei der Zahlungsmethode bei der Erstinstallation + +# 1.4.1 +- Rufen Sie nur aktive Zahlungsmethoden ab + +# 1.4.0 +- Festlegen der Verfügbarkeitsregel für Zahlungsmethoden +- E-Mail-Versand korrigiert +- Fehlgeschlagene Bestellungen stornieren + +# 1.3.0 +- Aktualisieren Sie die Synchronisierung der Zahlungsmethode + +# 1.2.0 +- Verfügbarkeitsregel für Zahlungsmethoden hinzufügen +- Hardcodierte Systemsprachen + +# 1.1.27 +- Wiederholen Sie Bestellungen bei nicht verfügbarer Zahlungsmethode + +# 1.1.26 +- Korrigieren Sie Gebietsschemas und Übersetzungen + +# 1.1.25 +- E-Mail-Versand korrigiert + +# 1.1.24 +- Webhook-Antwort korrigiert +- Übersetzung korrigieren +- Bereiten Sie sich auf Shopware vor 6.4 + +# 1.1.23 +- Senden Sie das Zahlungsformular, wenn iframe keine Felder enthält + +# 1.1.22 +- Einstellung zum Herunterladen der Bestellrechnung + +# 1.1.21 +- Entfernen Sie die fest codierte Shopware-API-Version + +# 1.1.20 +- Aktualisieren Sie die Webhook-URLs beim Plugin-Update +- Übersetzungen hinzufügen +- E-Mail-Fehler behoben + +# 1.1.19 +- Kunden können Bestellrechnungen herunterladen + +# 1.1.18 +- Test gegen Shopware 6.3 +- Fehler bei ungültiger Speicherplatz-ID behoben +- Entfernen Sie die fest codierte Shopware-API-Version + +# 1.1.17 +- Verwenden Sie DAL für Webhook-Sperren + +# 1.1.16 +- Stellen Sie nur Übersetzungen für verfügbare Sprachen bereit +- CustomerCanceledAsyncPaymentException für stornierte Transaktionen zurückgeben +- Aktualisieren Sie das SDK auf 2.1.1 + +# 1.1.15 +- Senden Sie den Vor- und Nachnamen des Kunden aus den Rechnungs- und Versandprofilen +- Respektieren Sie die Shop-URL + +# 1.1.14 +- Fügen Sie dem Cookie-Manager Cookies hinzu +- Ändern Sie die Größe des Symbols auf 40px * 40px +- Korrektur von Werbebuchungsattributen + +# 1.1.13 +- Fügen Sie den Lieferantenordner in Shopware Store-Versionen ein + +# 1.1.12 +- Dokumentpfad aktualisieren + +# 1.1.11 +- Dokumentation hinzufügen + +# 1.1.10 +- Reagieren Sie nicht mehr mit Serverfehlern, wenn keine Bestellungen gefunden werden + +# 1.1.9 +- Setzen Sie try catch auf die Webhook-Installation + +# 1.1.8 +- Entfernen Sie nicht hilfreiche Ticketinformationen in den Release-Kommentaren + +# 1.1.7 +- Werbeaktionen durchführen +- Code Refactoring + +# 1.1.6 +- Deaktivieren Sie die Auswahl der Vertriebskanäle für Vitrinen +- Fügen Sie der Transaktionsnutzlast Produktattribute hinzu + +# 1.1.5 +- Einstellungsfehler behoben + +# 1.1.4 +- Deaktivieren Sie das Ändern der Anmeldeinformationen für die Vitrinen + +# 1.1.3 +- Legen Sie die Konsistenz der Werbebuchung als Standard fest +- Bestätigen Sie die Transaktion sofort +- Aktualisieren Sie die Einstellungsbeschreibungen + +# 1.1.2 +- Bereiten Sie die interne serverseitige Installation für Vitrinen und Demos vor + +# 1.1.1 +- Stoppen Sie das Senden von Standard-E-Mails +- Verschönern Sie die Zahlungsseite + +# 1.1.0 +- Behandeln Sie leere / Standardeinstellungswerte +- Speichern Sie Rückerstattungen in db und laden Sie die Registerkarte Bestellung bei Änderungen neu + +# 1.0.0 +- Erste Version der VRPayment-Integrationen für Shopware 6 diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..cb82feb --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2025 VR Payment GmbH + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..e4d10b1 --- /dev/null +++ b/README.md @@ -0,0 +1,106 @@ + + +VR Payment Integration for Shopware 6 +============================= + +The VR Payment plugin wraps around the VR Payment API. This library facilitates your interaction with various services such as transactions. +Please note that this plugin is for versions 6.5, 6.6 or 6.7. For the 6.4 plugin please visit [our Shopware 6.4 plugin](https://github.com/vr-payment/shopware-6-4). + +## Requirements + +- Shopware 6.7.x, 6.6.x or 6.5.x. See table below. +- PHP minimum version supported by the each shop version. + +## Documentation + +- For English documentation click [here](https://docs.plugin-documentation.vr-payment.de/vr-payment/shopware-6/7.0.1/docs/en/documentation.html) +- Für die deutsche Dokumentation klicken Sie [hier](https://docs.plugin-documentation.vr-payment.de/vr-payment/shopware-6/7.0.1/docs/de/documentation.html) +- Pour la documentation Française, cliquez [ici](https://docs.plugin-documentation.vr-payment.de/vr-payment/shopware-6/7.0.1/docs/fr/documentation.html) +- Per la documentazione in tedesco, clicca [qui](https://docs.plugin-documentation.vr-payment.de/vr-payment/shopware-6/7.0.1/docs/it/documentation.html) + +## Installation + +### **Via Composer (Recommended)** +1. Navigate to your Shopware root directory. +2. Run: + +```bash +Copy +composer require vrpayment/shopware-6 +php bin/console plugin:refresh +php bin/console plugin:install --activate --clearCache VRPaymentPayment +``` + +### Manual Installation + +1. Download the latest [Release](../../releases) +2. Extract the ZIP to custom/plugins/VRPaymentPayment. + +```bash +Copy +bin/console plugin:refresh +bin/console plugin:install --activate --clearCache VRPaymentPayment +``` + +## Configuration +### API Credentials + +1. Navigate to Shopware Admin > Settings > VRPayment Payment. +2. Enter your Space ID, User ID, and API Key (obtained from the [VR Payment Portal](https://gateway.vr-payment.de/)). + +### Payment Methods + +Configure supported methods (e.g., credit cards, Apple Pay) via the [VR Payment Portal](https://gateway.vr-payment.de/). + +### Key Features +**iFrame Integration**: Embed payment forms directly into your checkout. + +**Refunds & Captures**: Trigger full/partial refunds and captures from Shopware or the [VR Payment Portal](https://gateway.vr-payment.de/). + +**Multi-Store Support**: Manage configurations across multiple stores. + +**Automatic Updates**: Payment methods sync dynamically via the VRPayment API. + +## Compatibiliity + +___________________________________________________________________________________ +| Shopware 6 version | Plugin major version | Supported until | +|-------------------------------|------------------------|------------------------| +| Shopware 6.7.x | 7.x | Further notice | +| Shopware 6.6.x | 6.x | December 2025 | +| Shopware 6.5.x | 5.x | October 2024 | +----------------------------------------------------------------------------------- + +### Troubleshooting +**Logs**: Check payment logs with: + +```bash +Copy +tail -f var/log/vrpayment_payment*.log +``` +### Common Issues: + +Ensure composer update vrpayment/shopware-6 is run after updates. + +Verify API credentials match your VRPayment account. + +## FAQs +**Q: Does this plugin support one-click payments?** +A: Yes, via tokenization in the VRPayment Portal. + +**Q: How do I handle PCI compliance?** +A: The plugin uses iFrame integration, reducing PCI requirements to SAQ-A. + +### Changelog +For version-specific updates, see the [GitHub Releases](https://github.com/vr-payment/shopware-6/releases). + +### Contributing +Report issues via GitHub Issues. + +Follow the Shopware Plugin Base Guide for development. + +This template combines technical clarity with user-friendly guidance. For advanced customization (e.g., overriding templates or payment handlers), refer to the Shopware Documentation. + +## License + +Please see the [license file](https://github.com/vr-payment/shopware-6/blob/master/LICENSE.txt) for more information. diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..cb18b7a --- /dev/null +++ b/composer.json @@ -0,0 +1,63 @@ +{ + "authors": [ + { + "homepage": "https://www.vr-payment.de/", + "name": "VR Payment" + } + ], + "autoload": { + "psr-4": { + "VRPaymentPayment\\": "src/" + } + }, + "description": "VRPayment integration for Shopware 6", + "extra": { + "copyright": "(c) by VR Payment", + "description": { + "de-DE": "VRPayment integration für Shopware 6", + "en-GB": "VRPayment integration for Shopware 6", + "fr-FR": "Intégration de VRPayment pour Shopware 6", + "it-IT": "Integrazione VRPayment per Shopware" + }, + "label": { + "de-DE": "VRPayment Produkte für Shopware 6", + "en-GB": "VRPayment Products for Shopware 6", + "fr-FR": "VRPayment Produits for Shopware 6", + "it-IT": "VRPayment Prodotti per Shopware 6" + }, + "manufacturerLink": { + "de-DE": "https://www.vr-payment.de/", + "en-GB": "https://www.vr-payment.de/", + "fr-FR": "https://www.vr-payment.de/", + "it-IT": "https://www.vr-payment.de/" + }, + "supportLink": { + "de-DE": "https://www.vr-payment.de/hotline", + "en-GB": "https://www.vr-payment.de/hotline", + "fr-FR": "https://www.vr-payment.de/hotline", + "it-IT": "https://www.vr-payment.de/hotline" + }, + "shopware-plugin-class": "VRPaymentPayment\\VRPaymentPayment" + }, + "homepage": "https://www.vr-payment.de//", + "keywords": [ + "VR Payment", + "payment", + "php", + "shopware" + ], + "license": "Apache-2.0", + "name": "vrpayment/shopware-6", + "require": { + "ext-curl": "*", + "ext-json": "*", + "ext-mbstring": "*", + "php": ">=8.2", + "shopware/core": "~6.7.0", + "shopware/administration": "~6.7.0", + "shopware/storefront":"~6.7.0", + "vrpayment/sdk": "^4.0.0" + }, + "type": "shopware-platform-plugin", + "version": "7.0.1" +} diff --git a/docs/de/assets/base.css b/docs/de/assets/base.css new file mode 100644 index 0000000..bb25826 --- /dev/null +++ b/docs/de/assets/base.css @@ -0,0 +1,692 @@ +* { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} + +*:before, *:after { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} + +html { + font-size: 100%; + font-family: sans-serif; + line-height: 1.15; + -webkit-text-size-adjust: 100%; + -ms-text-size-adjust: 100%; + -ms-overflow-style: scrollbar; + -webkit-tap-highlight-color: transparent; +} + +@-ms-viewport { + width: device-width; +} + +article, aside, dialog, figcaption, figure, footer, header, hgroup, main, nav, section { + display: block; +} + +body { + margin: 0; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; + font-size: 1rem; + font-weight: 300; + line-height: 1.5; + color: #212529; + text-align: left; + background-color: #fff; + padding-right: 0 !important; + position: relative; +} + +html,body { + width: 100%; + height: 100%; +} + +[tabindex="-1"]:focus { + outline: 0 !important; +} + +hr { + box-sizing: content-box; + height: 0; + overflow: visible; +} + +h1, h2, h3, h4, h5, h6 { + margin-top: 0; + margin-bottom: 0.5rem; +} + +h1 { + font-size: 2.5rem; + font-weight: 200; + margin-bottom: 1.875rem; +} + +h2 { + font-size: 1.625rem; + font-weight: 300; + margin-bottom: 1.3rem; +} + +h3 { + font-size: 1.3rem; + font-weight: 300; + margin-top: 1.3rem; +} + +h4 { + font-size: 1.125rem; + font-weight: 400; + margin-top: 1.875rem; + margin-bottom: 1.3rem; +} + +h5 { + font-size: 1rem; + font-weight: bold; + margin-top: 1.875rem; + margin-bottom: 1.3rem; +} + +p { + margin-top: 0; + margin-bottom: 1rem; +} + +abbr[title], abbr[data-original-title] { + text-decoration: underline; + -webkit-text-decoration: underline dotted; + text-decoration: underline dotted; + cursor: help; + border-bottom: 0; +} + +address { + margin-bottom: 1rem; + font-style: normal; + line-height: inherit; +} + +ol, ul, dl { + margin-top: 0; + margin-bottom: 1rem; +} + +ol ol, ul ul, ol ul, ul ol { + margin-bottom: 0; +} + +dt { + font-weight: 700; +} + +dd { + margin-bottom: .5rem; + margin-left: 0; +} + +blockquote { + margin: 0 0 1rem; +} + +dfn { + font-style: italic; +} + +b, strong { + font-weight: bold; +} + +small { + font-size: 80%; +} + +sub, sup { + position: relative; + font-size: 75%; + line-height: 0; + vertical-align: baseline; +} + +sub { + bottom: -.25em; +} + +sup { + top: -.5em; +} + +a { + color: #007bff; + text-decoration: none; + background-color: transparent; + -webkit-text-decoration-skip: objects; +} + +a:hover { + color: #0056b3; + text-decoration: underline; +} + +a:not([href]):not([tabindex]) { + color: inherit; + text-decoration: none; +} + +a:not([href]):not([tabindex]):hover, a:not([href]):not([tabindex]):focus { + color: inherit; + text-decoration: none; +} + +a:not([href]):not([tabindex]):focus { + outline: 0; +} + +pre, code, kbd, samp { + font-family: monospace, monospace; + font-size: 90%; + padding: 2px 4px 2px 4px; + color: #c7254e; + background-color: #f9f2f4; + border-radius: 4px; +} + +pre { + margin-top: 0; + margin-bottom: 1rem; + overflow: auto; + -ms-overflow-style: scrollbar; +} + +figure { + margin: 0 0 1rem; +} + +img { + vertical-align: middle; + border-style: none; +} + +svg:not(:root) { + overflow: hidden; +} + +table { + border-collapse: collapse; + background-color: transparent; +} + +caption { + padding-top: 8px; + padding-bottom: 8px; + color: #a7a7a7; + text-align: left; +} + +th { + text-align: left; +} + +output { + display: inline-block; +} + +summary { + display: list-item; + cursor: pointer; +} + +template { + display: none; +} + +table col[class*="col-"] { + position: static; + float: none; + display: table-column; +} + +table td[class*="col-"],table th[class*="col-"] { + position: static; + float: none; + display: table-cell; +} + +ol.glossary { + counter-reset: glossary-counter; + list-style: none; + padding-left: 40px; +} + +ol.glossary li { + counter-increment: glossary-counter; + position: relative; +} + +ol.glossary li::before { + content: counter(glossary-counter); + position: absolute; + background-color: #73EAA9; + color: #fff; + border-radius: 100px; + width: 24px; + left: -40px; + text-align: center; + font-weight: bold; + line-height: 24px; +} + +.layout-wrapper { + position: relative; + width: 100%; + height: auto; + min-height: 100%; +} + +.layout-title { + padding: 1.875rem 0; + border-bottom: 1px solid #f0f0f0; +} + +.layout-title h1 { + font-size: 3rem; + font-weight: 200; + text-align: center; + margin: 0; +} + +.layout-title h2 { + font-size: 2rem; + font-weight: 200; + text-align: center; + color: #999; + margin-bottom: 0; +} + +.layout-navigation .nav { + padding: 1.875rem 0; + border-bottom: 1px solid #f0f0f0; + text-align: center; + background: #fff; + z-index: 1000; +} + +.layout-navigation .nav > li { + display: inline-block; +} + +.layout-navigation .nav > li > a { + border: 1px solid #007bff; + border-radius: 100px; + padding: 6px 12px; + margin: 0 8px; +} + +.layout-navigation .nav > li > a:hover, .layout-navigation .nav > li > a:active, .layout-navigation .nav > li > a:focus { + border: 1px solid #0056b3; + color: #0056b3; + text-decoration: none; +} + +.layout-content { + position: relative; +} + +.layout-content:before, .layout-content:after { + content: " "; + display: table; +} + +.layout-content:after { + clear: both; +} + +.layout-content .col-right { + width: 25%; + float: right; +} + +.layout-content .col-right-wrapper { + width: 100%; + position: relative; + overflow-x: hidden; + overflow-y: auto; + padding: 2.5rem 2rem 0; +} + +.layout-content .col-body { + width: 75%; + float: left; +} + +.layout-content .col-body:before, .layout-content .col-body:after { + content: " "; + display: table; +} + +.layout-content .col-body:after { + clear: both; +} + +.layout-content .col-body-wrapper { + position: relative; + width: 100%; + padding: 2.5rem 2rem 0; +} + +.nav { + padding-left: 0; + margin-bottom: 0; + list-style: none; + line-height: 2; +} + +.table-of-contents { + padding: 1.25rem 0; +} + +.table-of-contents .nav > li > a { + display: flex; +} + +.table-of-contents .nav > li > a .item-number { + display: none; +} + +.table-of-contents .nav > li > a .item-title { + color: #212529; + overflow: hidden; + text-overflow: ellipsis; + flex-grow: 1; + white-space: nowrap; +} + +.table-of-contents .nav > li > a .item-title:hover { + color: #0056b3; +} + +.table-of-contents .nav > li.extended > a .item-title, .table-of-contents .nav > li.active > a .item-title, .table-of-contents .nav > li.extended > a .item-title:hover, .table-of-contents .nav > li.active > a .item-title:hover { + color: #007bff; +} + +.table-of-contents > .nav > li > .nav { + display: none; + margin-bottom: 0.5rem; +} + +.table-of-contents > .nav > li > .nav > li > a { + padding-left: 1rem; +} + +.table-of-contents > .nav > li > .nav > li > a .item-title { + font-size: 0.875rem; +} + +.table-of-contents > .nav > li > .nav > li > .nav > li > a { + padding-left: 2rem; +} + +.table-of-contents > .nav > li > .nav > li > .nav > li > a .item-title { + font-size: 0.75rem; +} + +.table-of-contents > .nav > li.active > .nav { + display: block; +} + +.chapter { + margin: 0 0 6rem; + font-weight: 300; +} + +.section { + margin-top: 3rem; +} + +.chapter > .chapter-title h1, .chapter > .chapter-title h2, .chapter > .chapter-title h3, .chapter > .chapter-title h4, .chapter > .chapter-title h5, .chapter > .chapter-title h6, .section > .section-title h1, .section > .section-title h2, .section > .section-title h3, .section > .section-title h4, .section > .section-title h5, .section > .section-title h6 { + margin-top: 0; +} + +.chapter > .chapter-title h1 { + border-bottom: 2px solid #eeeeee; + margin-bottom: 1.5rem; + padding-bottom: 0.2em; +} + +.chapter-title .title-number, .section-title .title-number { + display: none; +} + +.paragraph { + line-height: 1.75em; +} + +.paragraph + .paragraph { + margin-top: 1em; +} + +.dlist { + margin-top: 30px; +} + +.dlist dl dt { + float: left; + width: 160px; + clear: left; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.dlist dl dd { + margin-left: 180px; +} + +.ulist { + margin-top: 30px; +} + +.imageblock { + margin: 30px auto; +} + +.imageblock .content img { + max-width: 100%; +} + +.imageblock .title { + padding: 10px 0 0; +} + +.exampleblock, .quoteblock, .literalblock { + background: #f5f4f4; + padding: 20px; + margin: 30px 0; +} + +.exampleblock .title, .quoteblock .title, .literalblock .title { + text-transform: uppercase; + font-size: 0.75em; + font-weight: 400; + color: #979797; + margin-bottom: 10px; +} + +.quoteblock blockquote { + margin: 0; + padding: 0; + border: 0; + font-size: inherit; +} + +.quoteblock blockquote p:last-child, .quoteblock blockquote ul:last-child, .quoteblock blockquote ol:last-child { + margin-bottom: 9px; +} + +.literalblock pre { + border: 0; + padding: 0; + margin: 0; +} + +.listingblock { + margin: 30px 0; +} + +.listingblock pre { + border: 0; + padding: 0; + margin: 0; +} + +.listingblock pre code { + display: block; + padding: 20px; +} + +.admonitionblock { + line-height: 1.8em; + padding: 20px; + margin: 30px 0; +} + +.admonitionblock .icon { + display: none; +} + +.admonitionblock.important { + background: #fce1e1; + border-left: 5px solid #ff6060; +} + +.admonitionblock.note, .admonitionblock.tip { + background: #e0f2fc; + border-left: 5px solid #88d5ff; +} + +.admonitionblock.caution, .admonitionblock.warning { + background: #fdf3d8; + border-left: 5px solid #f1c654; +} + +table.tableblock { + background-color: #fff; + width: 100%; + max-width: 100%; + margin-bottom: 18px; + margin: 30px 0; +} + +table.tableblock > thead > tr > th, table.tableblock > tbody > tr > th, table.tableblock > tfoot > tr > th, table.tableblock > thead > tr > td, table.tableblock > tbody > tr > td, table.tableblock > tfoot > tr > td { + padding: 8px; + line-height: 1.42857143; + vertical-align: top; + border-top: 1px solid #eee; +} + +table.tableblock > thead > tr > th { + vertical-align: bottom; + border-bottom: 2px solid #eee; +} + +table.tableblock > caption + thead > tr:first-child > th, table.tableblock > colgroup + thead > tr:first-child > th, table.tableblock > thead:first-child > tr:first-child > th, table.tableblock > caption + thead > tr:first-child > td, table.tableblock > colgroup + thead > tr:first-child > td, table.tableblock > thead:first-child > tr:first-child > td { + border-top: 0; +} + +table.tableblock > tbody + tbody { + border-top: 2px solid #eee; +} + +table.tableblock .table { + background-color: #fff; +} + +table.tableblock > tbody > tr:nth-of-type(odd) { + background-color: #f7f7f7; +} + +table.tableblock > thead > tr > th p:last-child, table.tableblock > tbody > tr > th p:last-child, table.tableblock > tfoot > tr > th p:last-child, table.tableblock > thead > tr > td p:last-child, table.tableblock > tbody > tr > td p:last-child, table.tableblock > tfoot > tr > td p:last-child { + margin-bottom: 0; +} + +.loaded .table-of-contents .nav .nav { + display: none; +} + +@media (min-width: 1200px) { + .layout-wrapper .layout-title, .layout-wrapper .layout-navigation, .layout-wrapper .layout-content { + max-width: 1200px; + margin-left: auto; + margin-right: auto; + } +} + +@media (max-width: 991px) { + html { + font-size: 90%; + } + + .layout-content .col-right { + display: none; + } + + .layout-content .col-body { + width: 100%; + } +} + +@media print { + body { + color: #000; + font-family: Georgia, "Times New Roman", Times, serif; + } + + a { + color: #000; + } + + h1 { + font-size: 1.6rem; + } + + h2 { + font-size: 1.4rem; + } + + h3 { + font-size: 1.2rem; + } + + h4 { + font-size: 1rem; + } + + h5 { + font-size: 0.9rem; + } + + .layout-title h1 { + font-size: 2rem; + } + + .layout-content .col-right { + display: none; + } + + .layout-content .col-body { + width: 100%; + } + + .chapter { + margin-bottom: 3rem; + } + + .section { + margin-top: 2rem; + } +} diff --git a/docs/de/assets/base.js b/docs/de/assets/base.js new file mode 100644 index 0000000..df5c13f --- /dev/null +++ b/docs/de/assets/base.js @@ -0,0 +1,14 @@ +(function($){ + + hljs.initHighlightingOnLoad(); + + $(document).ready(function(){ + $('.col-right-wrapper').stick_in_parent({ + parent: '.layout-content' + }); + $('body').scrollspy({ + target: '.table-of-contents' + }); + }); + +})(jQuery); diff --git a/docs/de/assets/highlight.js b/docs/de/assets/highlight.js new file mode 100644 index 0000000..9a30d5f --- /dev/null +++ b/docs/de/assets/highlight.js @@ -0,0 +1,6 @@ +/* + Highlight.js 10.0.3 (a4b1bd2d) + License: BSD-3-Clause + Copyright (c) 2006-2020, Ivan Sagalaev +*/ +var hljs=function(){"use strict";function e(n){Object.freeze(n);var t="function"==typeof n;return Object.getOwnPropertyNames(n).forEach((function(r){!n.hasOwnProperty(r)||null===n[r]||"object"!=typeof n[r]&&"function"!=typeof n[r]||t&&("caller"===r||"callee"===r||"arguments"===r)||Object.isFrozen(n[r])||e(n[r])})),n}function n(e){return e.replace(/&/g,"&").replace(//g,">")}function t(e){var n,t={},r=Array.prototype.slice.call(arguments,1);for(n in e)t[n]=e[n];return r.forEach((function(e){for(n in e)t[n]=e[n]})),t}function r(e){return e.nodeName.toLowerCase()}var a=Object.freeze({__proto__:null,escapeHTML:n,inherit:t,nodeStream:function(e){var n=[];return function e(t,a){for(var i=t.firstChild;i;i=i.nextSibling)3===i.nodeType?a+=i.nodeValue.length:1===i.nodeType&&(n.push({event:"start",offset:a,node:i}),a=e(i,a),r(i).match(/br|hr|img|input/)||n.push({event:"stop",offset:a,node:i}));return a}(e,0),n},mergeStreams:function(e,t,a){var i=0,s="",o=[];function l(){return e.length&&t.length?e[0].offset!==t[0].offset?e[0].offset"}function u(e){s+=""}function d(e){("start"===e.event?c:u)(e.node)}for(;e.length||t.length;){var g=l();if(s+=n(a.substring(i,g[0].offset)),i=g[0].offset,g===e){o.reverse().forEach(u);do{d(g.splice(0,1)[0]),g=l()}while(g===e&&g.length&&g[0].offset===i);o.reverse().forEach(c)}else"start"===g[0].event?o.push(g[0].node):o.pop(),d(g.splice(0,1)[0])}return s+n(a.substr(i))}});const i="",s=e=>!!e.kind;class o{constructor(e,n){this.buffer="",this.classPrefix=n.classPrefix,e.walk(this)}addText(e){this.buffer+=n(e)}openNode(e){if(!s(e))return;let n=e.kind;e.sublanguage||(n=`${this.classPrefix}${n}`),this.span(n)}closeNode(e){s(e)&&(this.buffer+=i)}span(e){this.buffer+=``}value(){return this.buffer}}class l{constructor(){this.rootNode={children:[]},this.stack=[this.rootNode]}get top(){return this.stack[this.stack.length-1]}get root(){return this.rootNode}add(e){this.top.children.push(e)}openNode(e){let n={kind:e,children:[]};this.add(n),this.stack.push(n)}closeNode(){if(this.stack.length>1)return this.stack.pop()}closeAllNodes(){for(;this.closeNode(););}toJSON(){return JSON.stringify(this.rootNode,null,4)}walk(e){return this.constructor._walk(e,this.rootNode)}static _walk(e,n){return"string"==typeof n?e.addText(n):n.children&&(e.openNode(n),n.children.forEach(n=>this._walk(e,n)),e.closeNode(n)),e}static _collapse(e){e.children&&(e.children.every(e=>"string"==typeof e)?(e.text=e.children.join(""),delete e.children):e.children.forEach(e=>{"string"!=typeof e&&l._collapse(e)}))}}class c extends l{constructor(e){super(),this.options=e}addKeyword(e,n){""!==e&&(this.openNode(n),this.addText(e),this.closeNode())}addText(e){""!==e&&this.add(e)}addSublanguage(e,n){let t=e.root;t.kind=n,t.sublanguage=!0,this.add(t)}toHTML(){return new o(this,this.options).value()}finalize(){}}function u(e){return e&&e.source||e}const d="(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)",g={begin:"\\\\[\\s\\S]",relevance:0},h={className:"string",begin:"'",end:"'",illegal:"\\n",contains:[g]},f={className:"string",begin:'"',end:'"',illegal:"\\n",contains:[g]},p={begin:/\b(a|an|the|are|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|they|like|more)\b/},m=function(e,n,r){var a=t({className:"comment",begin:e,end:n,contains:[]},r||{});return a.contains.push(p),a.contains.push({className:"doctag",begin:"(?:TODO|FIXME|NOTE|BUG|XXX):",relevance:0}),a},b=m("//","$"),v=m("/\\*","\\*/"),x=m("#","$");var _=Object.freeze({__proto__:null,IDENT_RE:"[a-zA-Z]\\w*",UNDERSCORE_IDENT_RE:"[a-zA-Z_]\\w*",NUMBER_RE:"\\b\\d+(\\.\\d+)?",C_NUMBER_RE:d,BINARY_NUMBER_RE:"\\b(0b[01]+)",RE_STARTERS_RE:"!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~",BACKSLASH_ESCAPE:g,APOS_STRING_MODE:h,QUOTE_STRING_MODE:f,PHRASAL_WORDS_MODE:p,COMMENT:m,C_LINE_COMMENT_MODE:b,C_BLOCK_COMMENT_MODE:v,HASH_COMMENT_MODE:x,NUMBER_MODE:{className:"number",begin:"\\b\\d+(\\.\\d+)?",relevance:0},C_NUMBER_MODE:{className:"number",begin:d,relevance:0},BINARY_NUMBER_MODE:{className:"number",begin:"\\b(0b[01]+)",relevance:0},CSS_NUMBER_MODE:{className:"number",begin:"\\b\\d+(\\.\\d+)?(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?",relevance:0},REGEXP_MODE:{begin:/(?=\/[^\/\n]*\/)/,contains:[{className:"regexp",begin:/\//,end:/\/[gimuy]*/,illegal:/\n/,contains:[g,{begin:/\[/,end:/\]/,relevance:0,contains:[g]}]}]},TITLE_MODE:{className:"title",begin:"[a-zA-Z]\\w*",relevance:0},UNDERSCORE_TITLE_MODE:{className:"title",begin:"[a-zA-Z_]\\w*",relevance:0},METHOD_GUARD:{begin:"\\.\\s*[a-zA-Z_]\\w*",relevance:0}}),E="of and for in not or if then".split(" ");function R(e,n){return n?+n:(t=e,E.includes(t.toLowerCase())?0:1);var t}const N=n,w=t,{nodeStream:y,mergeStreams:O}=a;return function(n){var r=[],a={},i={},s=[],o=!0,l=/((^(<[^>]+>|\t|)+|(?:\n)))/gm,d="Could not find the language '{}', did you forget to load/include a language module?",g={noHighlightRe:/^(no-?highlight)$/i,languageDetectRe:/\blang(?:uage)?-([\w-]+)\b/i,classPrefix:"hljs-",tabReplace:null,useBR:!1,languages:void 0,__emitter:c};function h(e){return g.noHighlightRe.test(e)}function f(e,n,t,r){var a={code:n,language:e};T("before:highlight",a);var i=a.result?a.result:p(a.language,a.code,t,r);return i.code=a.code,T("after:highlight",i),i}function p(e,n,r,i){var s=n;function l(e,n){var t=v.case_insensitive?n[0].toLowerCase():n[0];return e.keywords.hasOwnProperty(t)&&e.keywords[t]}function c(){null!=_.subLanguage?function(){if(""!==k){var e="string"==typeof _.subLanguage;if(!e||a[_.subLanguage]){var n=e?p(_.subLanguage,k,!0,E[_.subLanguage]):m(k,_.subLanguage.length?_.subLanguage:void 0);_.relevance>0&&(T+=n.relevance),e&&(E[_.subLanguage]=n.top),w.addSublanguage(n.emitter,n.language)}else w.addText(k)}}():function(){var e,n,t,r;if(_.keywords){for(n=0,_.lexemesRe.lastIndex=0,t=_.lexemesRe.exec(k),r="";t;){r+=k.substring(n,t.index);var a=null;(e=l(_,t))?(w.addText(r),r="",T+=e[1],a=e[0],w.addKeyword(t[0],a)):r+=t[0],n=_.lexemesRe.lastIndex,t=_.lexemesRe.exec(k)}r+=k.substr(n),w.addText(r)}else w.addText(k)}(),k=""}function h(e){e.className&&w.openNode(e.className),_=Object.create(e,{parent:{value:_}})}var f={};function b(n,t){var a,i=t&&t[0];if(k+=n,null==i)return c(),0;if("begin"==f.type&&"end"==t.type&&f.index==t.index&&""===i){if(k+=s.slice(t.index,t.index+1),!o)throw(a=Error("0 width match regex")).languageName=e,a.badRule=f.rule,a;return 1}if(f=t,"begin"===t.type)return function(e){var n=e[0],t=e.rule;return t.__onBegin&&(t.__onBegin(e)||{}).ignoreMatch?function(e){return 0===_.matcher.regexIndex?(k+=e[0],1):(B=!0,0)}(n):(t&&t.endSameAsBegin&&(t.endRe=RegExp(n.replace(/[-\/\\^$*+?.()|[\]{}]/g,"\\$&"),"m")),t.skip?k+=n:(t.excludeBegin&&(k+=n),c(),t.returnBegin||t.excludeBegin||(k=n)),h(t),t.returnBegin?0:n.length)}(t);if("illegal"===t.type&&!r)throw(a=Error('Illegal lexeme "'+i+'" for mode "'+(_.className||"")+'"')).mode=_,a;if("end"===t.type){var l=function(e){var n=e[0],t=s.substr(e.index),r=function e(n,t){if(function(e,n){var t=e&&e.exec(n);return t&&0===t.index}(n.endRe,t)){for(;n.endsParent&&n.parent;)n=n.parent;return n}if(n.endsWithParent)return e(n.parent,t)}(_,t);if(r){var a=_;a.skip?k+=n:(a.returnEnd||a.excludeEnd||(k+=n),c(),a.excludeEnd&&(k=n));do{_.className&&w.closeNode(),_.skip||_.subLanguage||(T+=_.relevance),_=_.parent}while(_!==r.parent);return r.starts&&(r.endSameAsBegin&&(r.starts.endRe=r.endRe),h(r.starts)),a.returnEnd?0:n.length}}(t);if(null!=l)return l}if("illegal"===t.type&&""===i)return 1;if(A>1e5&&A>3*t.index)throw Error("potential infinite loop, way more iterations than matches");return k+=i,i.length}var v=M(e);if(!v)throw console.error(d.replace("{}",e)),Error('Unknown language: "'+e+'"');!function(e){function n(n,t){return RegExp(u(n),"m"+(e.case_insensitive?"i":"")+(t?"g":""))}class r{constructor(){this.matchIndexes={},this.regexes=[],this.matchAt=1,this.position=0}addRule(e,n){n.position=this.position++,this.matchIndexes[this.matchAt]=n,this.regexes.push([n,e]),this.matchAt+=function(e){return RegExp(e.toString()+"|").exec("").length-1}(e)+1}compile(){0===this.regexes.length&&(this.exec=()=>null);let e=this.regexes.map(e=>e[1]);this.matcherRe=n(function(e,n){for(var t=/\[(?:[^\\\]]|\\.)*\]|\(\??|\\([1-9][0-9]*)|\\./,r=0,a="",i=0;i0&&(a+="|"),a+="(";o.length>0;){var l=t.exec(o);if(null==l){a+=o;break}a+=o.substring(0,l.index),o=o.substring(l.index+l[0].length),"\\"==l[0][0]&&l[1]?a+="\\"+(+l[1]+s):(a+=l[0],"("==l[0]&&r++)}a+=")"}return a}(e),!0),this.lastIndex=0}exec(e){this.matcherRe.lastIndex=this.lastIndex;let n=this.matcherRe.exec(e);if(!n)return null;let t=n.findIndex((e,n)=>n>0&&null!=e),r=this.matchIndexes[t];return Object.assign(n,r)}}class a{constructor(){this.rules=[],this.multiRegexes=[],this.count=0,this.lastIndex=0,this.regexIndex=0}getMatcher(e){if(this.multiRegexes[e])return this.multiRegexes[e];let n=new r;return this.rules.slice(e).forEach(([e,t])=>n.addRule(e,t)),n.compile(),this.multiRegexes[e]=n,n}considerAll(){this.regexIndex=0}addRule(e,n){this.rules.push([e,n]),"begin"===n.type&&this.count++}exec(e){let n=this.getMatcher(this.regexIndex);n.lastIndex=this.lastIndex;let t=n.exec(e);return t&&(this.regexIndex+=t.position+1,this.regexIndex===this.count&&(this.regexIndex=0)),t}}function i(e){let n=e.input[e.index-1],t=e.input[e.index+e[0].length];if("."===n||"."===t)return{ignoreMatch:!0}}if(e.contains&&e.contains.includes("self"))throw Error("ERR: contains `self` is not supported at the top-level of a language. See documentation.");!function r(s,o){s.compiled||(s.compiled=!0,s.__onBegin=null,s.keywords=s.keywords||s.beginKeywords,s.keywords&&(s.keywords=function(e,n){var t={};return"string"==typeof e?r("keyword",e):Object.keys(e).forEach((function(n){r(n,e[n])})),t;function r(e,r){n&&(r=r.toLowerCase()),r.split(" ").forEach((function(n){var r=n.split("|");t[r[0]]=[e,R(r[0],r[1])]}))}}(s.keywords,e.case_insensitive)),s.lexemesRe=n(s.lexemes||/\w+/,!0),o&&(s.beginKeywords&&(s.begin="\\b("+s.beginKeywords.split(" ").join("|")+")(?=\\b|\\s)",s.__onBegin=i),s.begin||(s.begin=/\B|\b/),s.beginRe=n(s.begin),s.endSameAsBegin&&(s.end=s.begin),s.end||s.endsWithParent||(s.end=/\B|\b/),s.end&&(s.endRe=n(s.end)),s.terminator_end=u(s.end)||"",s.endsWithParent&&o.terminator_end&&(s.terminator_end+=(s.end?"|":"")+o.terminator_end)),s.illegal&&(s.illegalRe=n(s.illegal)),null==s.relevance&&(s.relevance=1),s.contains||(s.contains=[]),s.contains=[].concat(...s.contains.map((function(e){return function(e){return e.variants&&!e.cached_variants&&(e.cached_variants=e.variants.map((function(n){return t(e,{variants:null},n)}))),e.cached_variants?e.cached_variants:function e(n){return!!n&&(n.endsWithParent||e(n.starts))}(e)?t(e,{starts:e.starts?t(e.starts):null}):Object.isFrozen(e)?t(e):e}("self"===e?s:e)}))),s.contains.forEach((function(e){r(e,s)})),s.starts&&r(s.starts,o),s.matcher=function(e){let n=new a;return e.contains.forEach(e=>n.addRule(e.begin,{rule:e,type:"begin"})),e.terminator_end&&n.addRule(e.terminator_end,{type:"end"}),e.illegal&&n.addRule(e.illegal,{type:"illegal"}),n}(s))}(e)}(v);var x,_=i||v,E={},w=new g.__emitter(g);!function(){for(var e=[],n=_;n!==v;n=n.parent)n.className&&e.unshift(n.className);e.forEach(e=>w.openNode(e))}();var y,O,k="",T=0,L=0,A=0,B=!1;try{for(_.matcher.considerAll();A++,B?B=!1:(_.matcher.lastIndex=L,_.matcher.considerAll()),y=_.matcher.exec(s);)O=b(s.substring(L,y.index),y),L=y.index+O;return b(s.substr(L)),w.closeAllNodes(),w.finalize(),x=w.toHTML(),{relevance:T,value:x,language:e,illegal:!1,emitter:w,top:_}}catch(n){if(n.message&&n.message.includes("Illegal"))return{illegal:!0,illegalBy:{msg:n.message,context:s.slice(L-100,L+100),mode:n.mode},sofar:x,relevance:0,value:N(s),emitter:w};if(o)return{relevance:0,value:N(s),emitter:w,language:e,top:_,errorRaised:n};throw n}}function m(e,n){n=n||g.languages||Object.keys(a);var t=function(e){const n={relevance:0,emitter:new g.__emitter(g),value:N(e),illegal:!1,top:E};return n.emitter.addText(e),n}(e),r=t;return n.filter(M).filter(k).forEach((function(n){var a=p(n,e,!1);a.language=n,a.relevance>r.relevance&&(r=a),a.relevance>t.relevance&&(r=t,t=a)})),r.language&&(t.second_best=r),t}function b(e){return g.tabReplace||g.useBR?e.replace(l,(function(e,n){return g.useBR&&"\n"===e?"
":g.tabReplace?n.replace(/\t/g,g.tabReplace):""})):e}function v(e){var n,t,r,a,s,o=function(e){var n,t=e.className+" ";if(t+=e.parentNode?e.parentNode.className:"",n=g.languageDetectRe.exec(t)){var r=M(n[1]);return r||(console.warn(d.replace("{}",n[1])),console.warn("Falling back to no-highlight mode for this block.",e)),r?n[1]:"no-highlight"}return t.split(/\s+/).find(e=>h(e)||M(e))}(e);h(o)||(T("before:highlightBlock",{block:e,language:o}),g.useBR?(n=document.createElement("div")).innerHTML=e.innerHTML.replace(/\n/g,"").replace(//g,"\n"):n=e,s=n.textContent,r=o?f(o,s,!0):m(s),(t=y(n)).length&&((a=document.createElement("div")).innerHTML=r.value,r.value=O(t,y(a),s)),r.value=b(r.value),T("after:highlightBlock",{block:e,result:r}),e.innerHTML=r.value,e.className=function(e,n,t){var r=n?i[n]:t,a=[e.trim()];return e.match(/\bhljs\b/)||a.push("hljs"),e.includes(r)||a.push(r),a.join(" ").trim()}(e.className,o,r.language),e.result={language:r.language,re:r.relevance},r.second_best&&(e.second_best={language:r.second_best.language,re:r.second_best.relevance}))}function x(){if(!x.called){x.called=!0;var e=document.querySelectorAll("pre code");r.forEach.call(e,v)}}const E={disableAutodetect:!0,name:"Plain text"};function M(e){return e=(e||"").toLowerCase(),a[e]||a[i[e]]}function k(e){var n=M(e);return n&&!n.disableAutodetect}function T(e,n){var t=e;s.forEach((function(e){e[t]&&e[t](n)}))}Object.assign(n,{highlight:f,highlightAuto:m,fixMarkup:b,highlightBlock:v,configure:function(e){g=w(g,e)},initHighlighting:x,initHighlightingOnLoad:function(){window.addEventListener("DOMContentLoaded",x,!1)},registerLanguage:function(e,t){var r;try{r=t(n)}catch(n){if(console.error("Language definition for '{}' could not be registered.".replace("{}",e)),!o)throw n;console.error(n),r=E}r.name||(r.name=e),a[e]=r,r.rawDefinition=t.bind(null,n),r.aliases&&r.aliases.forEach((function(n){i[n]=e}))},listLanguages:function(){return Object.keys(a)},getLanguage:M,requireLanguage:function(e){var n=M(e);if(n)return n;throw Error("The '{}' language is required, but not loaded.".replace("{}",e))},autoDetection:k,inherit:w,addPlugin:function(e,n){s.push(e)}}),n.debugMode=function(){o=!1},n.safeMode=function(){o=!0},n.versionString="10.0.3";for(const n in _)"object"==typeof _[n]&&e(_[n]);return Object.assign(n,_),n}({})}();"object"==typeof exports&&"undefined"!=typeof module&&(module.exports=hljs);hljs.registerLanguage("css",function(){"use strict";return function(e){var n={begin:/(?:[A-Z\_\.\-]+|--[a-zA-Z0-9_-]+)\s*:/,returnBegin:!0,end:";",endsWithParent:!0,contains:[{className:"attribute",begin:/\S/,end:":",excludeEnd:!0,starts:{endsWithParent:!0,excludeEnd:!0,contains:[{begin:/[\w-]+\(/,returnBegin:!0,contains:[{className:"built_in",begin:/[\w-]+/},{begin:/\(/,end:/\)/,contains:[e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,e.CSS_NUMBER_MODE]}]},e.CSS_NUMBER_MODE,e.QUOTE_STRING_MODE,e.APOS_STRING_MODE,e.C_BLOCK_COMMENT_MODE,{className:"number",begin:"#[0-9A-Fa-f]+"},{className:"meta",begin:"!important"}]}}]};return{name:"CSS",case_insensitive:!0,illegal:/[=\/|'\$]/,contains:[e.C_BLOCK_COMMENT_MODE,{className:"selector-id",begin:/#[A-Za-z0-9_-]+/},{className:"selector-class",begin:/\.[A-Za-z0-9_-]+/},{className:"selector-attr",begin:/\[/,end:/\]/,illegal:"$",contains:[e.APOS_STRING_MODE,e.QUOTE_STRING_MODE]},{className:"selector-pseudo",begin:/:(:)?[a-zA-Z0-9\_\-\+\(\)"'.]+/},{begin:"@(page|font-face)",lexemes:"@[a-z-]+",keywords:"@page @font-face"},{begin:"@",end:"[{;]",illegal:/:/,returnBegin:!0,contains:[{className:"keyword",begin:/@\-?\w[\w]*(\-\w+)*/},{begin:/\s/,endsWithParent:!0,excludeEnd:!0,relevance:0,keywords:"and or not only",contains:[{begin:/[a-z-]+:/,className:"attribute"},e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,e.CSS_NUMBER_MODE]}]},{className:"selector-tag",begin:"[a-zA-Z-][a-zA-Z0-9_-]*",relevance:0},{begin:"{",end:"}",illegal:/\S/,contains:[e.C_BLOCK_COMMENT_MODE,n]}]}}}());hljs.registerLanguage("bash",function(){"use strict";return function(e){const s={};Object.assign(s,{className:"variable",variants:[{begin:/\$[\w\d#@][\w\d_]*/},{begin:/\$\{/,end:/\}/,contains:[{begin:/:-/,contains:[s]}]}]});const n={className:"subst",begin:/\$\(/,end:/\)/,contains:[e.BACKSLASH_ESCAPE]},t={className:"string",begin:/"/,end:/"/,contains:[e.BACKSLASH_ESCAPE,s,n]};n.contains.push(t);const a={begin:/\$\(\(/,end:/\)\)/,contains:[{begin:/\d+#[0-9a-f]+/,className:"number"},e.NUMBER_MODE,s]};return{name:"Bash",aliases:["sh","zsh"],lexemes:/\b-?[a-z\._]+\b/,keywords:{keyword:"if then else elif fi for while in do done case esac function",literal:"true false",built_in:"break cd continue eval exec exit export getopts hash pwd readonly return shift test times trap umask unset alias bind builtin caller command declare echo enable help let local logout mapfile printf read readarray source type typeset ulimit unalias set shopt autoload bg bindkey bye cap chdir clone comparguments compcall compctl compdescribe compfiles compgroups compquote comptags comptry compvalues dirs disable disown echotc echoti emulate fc fg float functions getcap getln history integer jobs kill limit log noglob popd print pushd pushln rehash sched setcap setopt stat suspend ttyctl unfunction unhash unlimit unsetopt vared wait whence where which zcompile zformat zftp zle zmodload zparseopts zprof zpty zregexparse zsocket zstyle ztcp",_:"-ne -eq -lt -gt -f -d -e -s -l -a"},contains:[{className:"meta",begin:/^#![^\n]+sh\s*$/,relevance:10},{className:"function",begin:/\w[\w\d_]*\s*\(\s*\)\s*\{/,returnBegin:!0,contains:[e.inherit(e.TITLE_MODE,{begin:/\w[\w\d_]*/})],relevance:0},a,e.HASH_COMMENT_MODE,t,{className:"",begin:/\\"/},{className:"string",begin:/'/,end:/'/},s]}}}());hljs.registerLanguage("xml",function(){"use strict";return function(e){var n={className:"symbol",begin:"&[a-z]+;|&#[0-9]+;|&#x[a-f0-9]+;"},a={begin:"\\s",contains:[{className:"meta-keyword",begin:"#?[a-z_][a-z1-9_-]+",illegal:"\\n"}]},s=e.inherit(a,{begin:"\\(",end:"\\)"}),t=e.inherit(e.APOS_STRING_MODE,{className:"meta-string"}),i=e.inherit(e.QUOTE_STRING_MODE,{className:"meta-string"}),c={endsWithParent:!0,illegal:/`]+/}]}]}]};return{name:"HTML, XML",aliases:["html","xhtml","rss","atom","xjb","xsd","xsl","plist","wsf","svg"],case_insensitive:!0,contains:[{className:"meta",begin:"",relevance:10,contains:[a,i,t,s,{begin:"\\[",end:"\\]",contains:[{className:"meta",begin:"",contains:[a,s,i,t]}]}]},e.COMMENT("\x3c!--","--\x3e",{relevance:10}),{begin:"<\\!\\[CDATA\\[",end:"\\]\\]>",relevance:10},n,{className:"meta",begin:/<\?xml/,end:/\?>/,relevance:10},{className:"tag",begin:")",end:">",keywords:{name:"style"},contains:[c],starts:{end:"",returnEnd:!0,subLanguage:["css","xml"]}},{className:"tag",begin:")",end:">",keywords:{name:"script"},contains:[c],starts:{end:"<\/script>",returnEnd:!0,subLanguage:["javascript","handlebars","xml"]}},{className:"tag",begin:"",contains:[{className:"name",begin:/[^\/><\s]+/,relevance:0},c]}]}}}());hljs.registerLanguage("php",function(){"use strict";return function(e){var r={begin:"\\$+[a-zA-Z_-ÿ][a-zA-Z0-9_-ÿ]*"},t={className:"meta",variants:[{begin:/<\?php/,relevance:10},{begin:/<\?[=]?/},{begin:/\?>/}]},a={className:"string",contains:[e.BACKSLASH_ESCAPE,t],variants:[{begin:'b"',end:'"'},{begin:"b'",end:"'"},e.inherit(e.APOS_STRING_MODE,{illegal:null}),e.inherit(e.QUOTE_STRING_MODE,{illegal:null})]},n={variants:[e.BINARY_NUMBER_MODE,e.C_NUMBER_MODE]},i={keyword:"__CLASS__ __DIR__ __FILE__ __FUNCTION__ __LINE__ __METHOD__ __NAMESPACE__ __TRAIT__ die echo exit include include_once print require require_once array abstract and as binary bool boolean break callable case catch class clone const continue declare default do double else elseif empty enddeclare endfor endforeach endif endswitch endwhile eval extends final finally float for foreach from global goto if implements instanceof insteadof int integer interface isset iterable list new object or private protected public real return string switch throw trait try unset use var void while xor yield",literal:"false null true",built_in:"Error|0 AppendIterator ArgumentCountError ArithmeticError ArrayIterator ArrayObject AssertionError BadFunctionCallException BadMethodCallException CachingIterator CallbackFilterIterator CompileError Countable DirectoryIterator DivisionByZeroError DomainException EmptyIterator ErrorException Exception FilesystemIterator FilterIterator GlobIterator InfiniteIterator InvalidArgumentException IteratorIterator LengthException LimitIterator LogicException MultipleIterator NoRewindIterator OutOfBoundsException OutOfRangeException OuterIterator OverflowException ParentIterator ParseError RangeException RecursiveArrayIterator RecursiveCachingIterator RecursiveCallbackFilterIterator RecursiveDirectoryIterator RecursiveFilterIterator RecursiveIterator RecursiveIteratorIterator RecursiveRegexIterator RecursiveTreeIterator RegexIterator RuntimeException SeekableIterator SplDoublyLinkedList SplFileInfo SplFileObject SplFixedArray SplHeap SplMaxHeap SplMinHeap SplObjectStorage SplObserver SplObserver SplPriorityQueue SplQueue SplStack SplSubject SplSubject SplTempFileObject TypeError UnderflowException UnexpectedValueException ArrayAccess Closure Generator Iterator IteratorAggregate Serializable Throwable Traversable WeakReference Directory __PHP_Incomplete_Class parent php_user_filter self static stdClass"};return{aliases:["php","php3","php4","php5","php6","php7"],case_insensitive:!0,keywords:i,contains:[e.HASH_COMMENT_MODE,e.COMMENT("//","$",{contains:[t]}),e.COMMENT("/\\*","\\*/",{contains:[{className:"doctag",begin:"@[A-Za-z]+"}]}),e.COMMENT("__halt_compiler.+?;",!1,{endsWithParent:!0,keywords:"__halt_compiler",lexemes:e.UNDERSCORE_IDENT_RE}),{className:"string",begin:/<<<['"]?\w+['"]?$/,end:/^\w+;?$/,contains:[e.BACKSLASH_ESCAPE,{className:"subst",variants:[{begin:/\$\w+/},{begin:/\{\$/,end:/\}/}]}]},t,{className:"keyword",begin:/\$this\b/},r,{begin:/(::|->)+[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/},{className:"function",beginKeywords:"fn function",end:/[;{]/,excludeEnd:!0,illegal:"[$%\\[]",contains:[e.UNDERSCORE_TITLE_MODE,{className:"params",begin:"\\(",end:"\\)",excludeBegin:!0,excludeEnd:!0,keywords:i,contains:["self",r,e.C_BLOCK_COMMENT_MODE,a,n]}]},{className:"class",beginKeywords:"class interface",end:"{",excludeEnd:!0,illegal:/[:\(\$"]/,contains:[{beginKeywords:"extends implements"},e.UNDERSCORE_TITLE_MODE]},{beginKeywords:"namespace",end:";",illegal:/[\.']/,contains:[e.UNDERSCORE_TITLE_MODE]},{beginKeywords:"use",end:";",contains:[e.UNDERSCORE_TITLE_MODE]},{begin:"=>"},a,n]}}}());hljs.registerLanguage("php-template",function(){"use strict";return function(n){return{name:"PHP template",subLanguage:"xml",contains:[{begin:/<\?(php|=)?/,end:/\?>/,subLanguage:"php",contains:[{begin:"/\\*",end:"\\*/",skip:!0},{begin:'b"',end:'"',skip:!0},{begin:"b'",end:"'",skip:!0},n.inherit(n.APOS_STRING_MODE,{illegal:null,className:null,contains:null,skip:!0}),n.inherit(n.QUOTE_STRING_MODE,{illegal:null,className:null,contains:null,skip:!0})]}]}}}());hljs.registerLanguage("sql",function(){"use strict";return function(e){var t=e.COMMENT("--","$");return{name:"SQL",case_insensitive:!0,illegal:/[<>{}*]/,contains:[{beginKeywords:"begin end start commit rollback savepoint lock alter create drop rename call delete do handler insert load replace select truncate update set show pragma grant merge describe use explain help declare prepare execute deallocate release unlock purge reset change stop analyze cache flush optimize repair kill install uninstall checksum restore check backup revoke comment values with",end:/;/,endsWithParent:!0,lexemes:/[\w\.]+/,keywords:{keyword:"as abort abs absolute acc acce accep accept access accessed accessible account acos action activate add addtime admin administer advanced advise aes_decrypt aes_encrypt after agent aggregate ali alia alias all allocate allow alter always analyze ancillary and anti any anydata anydataset anyschema anytype apply archive archived archivelog are as asc ascii asin assembly assertion associate asynchronous at atan atn2 attr attri attrib attribu attribut attribute attributes audit authenticated authentication authid authors auto autoallocate autodblink autoextend automatic availability avg backup badfile basicfile before begin beginning benchmark between bfile bfile_base big bigfile bin binary_double binary_float binlog bit_and bit_count bit_length bit_or bit_xor bitmap blob_base block blocksize body both bound bucket buffer_cache buffer_pool build bulk by byte byteordermark bytes cache caching call calling cancel capacity cascade cascaded case cast catalog category ceil ceiling chain change changed char_base char_length character_length characters characterset charindex charset charsetform charsetid check checksum checksum_agg child choose chr chunk class cleanup clear client clob clob_base clone close cluster_id cluster_probability cluster_set clustering coalesce coercibility col collate collation collect colu colum column column_value columns columns_updated comment commit compact compatibility compiled complete composite_limit compound compress compute concat concat_ws concurrent confirm conn connec connect connect_by_iscycle connect_by_isleaf connect_by_root connect_time connection consider consistent constant constraint constraints constructor container content contents context contributors controlfile conv convert convert_tz corr corr_k corr_s corresponding corruption cos cost count count_big counted covar_pop covar_samp cpu_per_call cpu_per_session crc32 create creation critical cross cube cume_dist curdate current current_date current_time current_timestamp current_user cursor curtime customdatum cycle data database databases datafile datafiles datalength date_add date_cache date_format date_sub dateadd datediff datefromparts datename datepart datetime2fromparts day day_to_second dayname dayofmonth dayofweek dayofyear days db_role_change dbtimezone ddl deallocate declare decode decompose decrement decrypt deduplicate def defa defau defaul default defaults deferred defi defin define degrees delayed delegate delete delete_all delimited demand dense_rank depth dequeue des_decrypt des_encrypt des_key_file desc descr descri describ describe descriptor deterministic diagnostics difference dimension direct_load directory disable disable_all disallow disassociate discardfile disconnect diskgroup distinct distinctrow distribute distributed div do document domain dotnet double downgrade drop dumpfile duplicate duration each edition editionable editions element ellipsis else elsif elt empty enable enable_all enclosed encode encoding encrypt end end-exec endian enforced engine engines enqueue enterprise entityescaping eomonth error errors escaped evalname evaluate event eventdata events except exception exceptions exchange exclude excluding execu execut execute exempt exists exit exp expire explain explode export export_set extended extent external external_1 external_2 externally extract failed failed_login_attempts failover failure far fast feature_set feature_value fetch field fields file file_name_convert filesystem_like_logging final finish first first_value fixed flash_cache flashback floor flush following follows for forall force foreign form forma format found found_rows freelist freelists freepools fresh from from_base64 from_days ftp full function general generated get get_format get_lock getdate getutcdate global global_name globally go goto grant grants greatest group group_concat group_id grouping grouping_id groups gtid_subtract guarantee guard handler hash hashkeys having hea head headi headin heading heap help hex hierarchy high high_priority hosts hour hours http id ident_current ident_incr ident_seed identified identity idle_time if ifnull ignore iif ilike ilm immediate import in include including increment index indexes indexing indextype indicator indices inet6_aton inet6_ntoa inet_aton inet_ntoa infile initial initialized initially initrans inmemory inner innodb input insert install instance instantiable instr interface interleaved intersect into invalidate invisible is is_free_lock is_ipv4 is_ipv4_compat is_not is_not_null is_used_lock isdate isnull isolation iterate java join json json_exists keep keep_duplicates key keys kill language large last last_day last_insert_id last_value lateral lax lcase lead leading least leaves left len lenght length less level levels library like like2 like4 likec limit lines link list listagg little ln load load_file lob lobs local localtime localtimestamp locate locator lock locked log log10 log2 logfile logfiles logging logical logical_reads_per_call logoff logon logs long loop low low_priority lower lpad lrtrim ltrim main make_set makedate maketime managed management manual map mapping mask master master_pos_wait match matched materialized max maxextents maximize maxinstances maxlen maxlogfiles maxloghistory maxlogmembers maxsize maxtrans md5 measures median medium member memcompress memory merge microsecond mid migration min minextents minimum mining minus minute minutes minvalue missing mod mode model modification modify module monitoring month months mount move movement multiset mutex name name_const names nan national native natural nav nchar nclob nested never new newline next nextval no no_write_to_binlog noarchivelog noaudit nobadfile nocheck nocompress nocopy nocycle nodelay nodiscardfile noentityescaping noguarantee nokeep nologfile nomapping nomaxvalue nominimize nominvalue nomonitoring none noneditionable nonschema noorder nopr nopro noprom nopromp noprompt norely noresetlogs noreverse normal norowdependencies noschemacheck noswitch not nothing notice notnull notrim novalidate now nowait nth_value nullif nulls num numb numbe nvarchar nvarchar2 object ocicoll ocidate ocidatetime ociduration ociinterval ociloblocator ocinumber ociref ocirefcursor ocirowid ocistring ocitype oct octet_length of off offline offset oid oidindex old on online only opaque open operations operator optimal optimize option optionally or oracle oracle_date oradata ord ordaudio orddicom orddoc order ordimage ordinality ordvideo organization orlany orlvary out outer outfile outline output over overflow overriding package pad parallel parallel_enable parameters parent parse partial partition partitions pascal passing password password_grace_time password_lock_time password_reuse_max password_reuse_time password_verify_function patch path patindex pctincrease pctthreshold pctused pctversion percent percent_rank percentile_cont percentile_disc performance period period_add period_diff permanent physical pi pipe pipelined pivot pluggable plugin policy position post_transaction pow power pragma prebuilt precedes preceding precision prediction prediction_cost prediction_details prediction_probability prediction_set prepare present preserve prior priority private private_sga privileges procedural procedure procedure_analyze processlist profiles project prompt protection public publishingservername purge quarter query quick quiesce quota quotename radians raise rand range rank raw read reads readsize rebuild record records recover recovery recursive recycle redo reduced ref reference referenced references referencing refresh regexp_like register regr_avgx regr_avgy regr_count regr_intercept regr_r2 regr_slope regr_sxx regr_sxy reject rekey relational relative relaylog release release_lock relies_on relocate rely rem remainder rename repair repeat replace replicate replication required reset resetlogs resize resource respect restore restricted result result_cache resumable resume retention return returning returns reuse reverse revoke right rlike role roles rollback rolling rollup round row row_count rowdependencies rowid rownum rows rtrim rules safe salt sample save savepoint sb1 sb2 sb4 scan schema schemacheck scn scope scroll sdo_georaster sdo_topo_geometry search sec_to_time second seconds section securefile security seed segment select self semi sequence sequential serializable server servererror session session_user sessions_per_user set sets settings sha sha1 sha2 share shared shared_pool short show shrink shutdown si_averagecolor si_colorhistogram si_featurelist si_positionalcolor si_stillimage si_texture siblings sid sign sin size size_t sizes skip slave sleep smalldatetimefromparts smallfile snapshot some soname sort soundex source space sparse spfile split sql sql_big_result sql_buffer_result sql_cache sql_calc_found_rows sql_small_result sql_variant_property sqlcode sqldata sqlerror sqlname sqlstate sqrt square standalone standby start starting startup statement static statistics stats_binomial_test stats_crosstab stats_ks_test stats_mode stats_mw_test stats_one_way_anova stats_t_test_ stats_t_test_indep stats_t_test_one stats_t_test_paired stats_wsr_test status std stddev stddev_pop stddev_samp stdev stop storage store stored str str_to_date straight_join strcmp strict string struct stuff style subdate subpartition subpartitions substitutable substr substring subtime subtring_index subtype success sum suspend switch switchoffset switchover sync synchronous synonym sys sys_xmlagg sysasm sysaux sysdate sysdatetimeoffset sysdba sysoper system system_user sysutcdatetime table tables tablespace tablesample tan tdo template temporary terminated tertiary_weights test than then thread through tier ties time time_format time_zone timediff timefromparts timeout timestamp timestampadd timestampdiff timezone_abbr timezone_minute timezone_region to to_base64 to_date to_days to_seconds todatetimeoffset trace tracking transaction transactional translate translation treat trigger trigger_nestlevel triggers trim truncate try_cast try_convert try_parse type ub1 ub2 ub4 ucase unarchived unbounded uncompress under undo unhex unicode uniform uninstall union unique unix_timestamp unknown unlimited unlock unnest unpivot unrecoverable unsafe unsigned until untrusted unusable unused update updated upgrade upped upper upsert url urowid usable usage use use_stored_outlines user user_data user_resources users using utc_date utc_timestamp uuid uuid_short validate validate_password_strength validation valist value values var var_samp varcharc vari varia variab variabl variable variables variance varp varraw varrawc varray verify version versions view virtual visible void wait wallet warning warnings week weekday weekofyear wellformed when whene whenev wheneve whenever where while whitespace window with within without work wrapped xdb xml xmlagg xmlattributes xmlcast xmlcolattval xmlelement xmlexists xmlforest xmlindex xmlnamespaces xmlpi xmlquery xmlroot xmlschema xmlserialize xmltable xmltype xor year year_to_month years yearweek",literal:"true false null unknown",built_in:"array bigint binary bit blob bool boolean char character date dec decimal float int int8 integer interval number numeric real record serial serial8 smallint text time timestamp tinyint varchar varchar2 varying void"},contains:[{className:"string",begin:"'",end:"'",contains:[{begin:"''"}]},{className:"string",begin:'"',end:'"',contains:[{begin:'""'}]},{className:"string",begin:"`",end:"`"},e.C_NUMBER_MODE,e.C_BLOCK_COMMENT_MODE,t,e.HASH_COMMENT_MODE]},e.C_BLOCK_COMMENT_MODE,t,e.HASH_COMMENT_MODE]}}}());hljs.registerLanguage("python",function(){"use strict";return function(e){var n={keyword:"and elif is global as in if from raise for except finally print import pass return exec else break not with class assert yield try while continue del or def lambda async await nonlocal|10",built_in:"Ellipsis NotImplemented",literal:"False None True"},a={className:"meta",begin:/^(>>>|\.\.\.) /},i={className:"subst",begin:/\{/,end:/\}/,keywords:n,illegal:/#/},s={begin:/\{\{/,relevance:0},r={className:"string",contains:[e.BACKSLASH_ESCAPE],variants:[{begin:/(u|b)?r?'''/,end:/'''/,contains:[e.BACKSLASH_ESCAPE,a],relevance:10},{begin:/(u|b)?r?"""/,end:/"""/,contains:[e.BACKSLASH_ESCAPE,a],relevance:10},{begin:/(fr|rf|f)'''/,end:/'''/,contains:[e.BACKSLASH_ESCAPE,a,s,i]},{begin:/(fr|rf|f)"""/,end:/"""/,contains:[e.BACKSLASH_ESCAPE,a,s,i]},{begin:/(u|r|ur)'/,end:/'/,relevance:10},{begin:/(u|r|ur)"/,end:/"/,relevance:10},{begin:/(b|br)'/,end:/'/},{begin:/(b|br)"/,end:/"/},{begin:/(fr|rf|f)'/,end:/'/,contains:[e.BACKSLASH_ESCAPE,s,i]},{begin:/(fr|rf|f)"/,end:/"/,contains:[e.BACKSLASH_ESCAPE,s,i]},e.APOS_STRING_MODE,e.QUOTE_STRING_MODE]},l={className:"number",relevance:0,variants:[{begin:e.BINARY_NUMBER_RE+"[lLjJ]?"},{begin:"\\b(0o[0-7]+)[lLjJ]?"},{begin:e.C_NUMBER_RE+"[lLjJ]?"}]},t={className:"params",variants:[{begin:/\(\s*\)/,skip:!0,className:null},{begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,contains:["self",a,l,r,e.HASH_COMMENT_MODE]}]};return i.contains=[r,l,a],{name:"Python",aliases:["py","gyp","ipython"],keywords:n,illegal:/(<\/|->|\?)|=>/,contains:[a,l,{beginKeywords:"if",relevance:0},r,e.HASH_COMMENT_MODE,{variants:[{className:"function",beginKeywords:"def"},{className:"class",beginKeywords:"class"}],end:/:/,illegal:/[${=;\n,]/,contains:[e.UNDERSCORE_TITLE_MODE,t,{begin:/->/,endsWithParent:!0,keywords:"None"}]},{className:"meta",begin:/^[\t ]*@/,end:/$/},{begin:/\b(print|exec)\(/}]}}}());hljs.registerLanguage("plaintext",function(){"use strict";return function(t){return{name:"Plain text",aliases:["text","txt"],disableAutodetect:!0}}}());hljs.registerLanguage("diff",function(){"use strict";return function(e){return{name:"Diff",aliases:["patch"],contains:[{className:"meta",relevance:10,variants:[{begin:/^@@ +\-\d+,\d+ +\+\d+,\d+ +@@$/},{begin:/^\*\*\* +\d+,\d+ +\*\*\*\*$/},{begin:/^\-\-\- +\d+,\d+ +\-\-\-\-$/}]},{className:"comment",variants:[{begin:/Index: /,end:/$/},{begin:/={3,}/,end:/$/},{begin:/^\-{3}/,end:/$/},{begin:/^\*{3} /,end:/$/},{begin:/^\+{3}/,end:/$/},{begin:/^\*{15}$/}]},{className:"addition",begin:"^\\+",end:"$"},{className:"deletion",begin:"^\\-",end:"$"},{className:"addition",begin:"^\\!",end:"$"}]}}}());hljs.registerLanguage("csharp",function(){"use strict";return function(e){var n={keyword:"abstract as base bool break byte case catch char checked const continue decimal default delegate do double enum event explicit extern finally fixed float for foreach goto if implicit in int interface internal is lock long object operator out override params private protected public readonly ref sbyte sealed short sizeof stackalloc static string struct switch this try typeof uint ulong unchecked unsafe ushort using virtual void volatile while add alias ascending async await by descending dynamic equals from get global group into join let nameof on orderby partial remove select set value var when where yield",literal:"null false true"},i=e.inherit(e.TITLE_MODE,{begin:"[a-zA-Z](\\.?\\w)*"}),a={className:"number",variants:[{begin:"\\b(0b[01']+)"},{begin:"(-?)\\b([\\d']+(\\.[\\d']*)?|\\.[\\d']+)(u|U|l|L|ul|UL|f|F|b|B)"},{begin:"(-?)(\\b0[xX][a-fA-F0-9']+|(\\b[\\d']+(\\.[\\d']*)?|\\.[\\d']+)([eE][-+]?[\\d']+)?)"}],relevance:0},s={className:"string",begin:'@"',end:'"',contains:[{begin:'""'}]},t=e.inherit(s,{illegal:/\n/}),l={className:"subst",begin:"{",end:"}",keywords:n},r=e.inherit(l,{illegal:/\n/}),c={className:"string",begin:/\$"/,end:'"',illegal:/\n/,contains:[{begin:"{{"},{begin:"}}"},e.BACKSLASH_ESCAPE,r]},o={className:"string",begin:/\$@"/,end:'"',contains:[{begin:"{{"},{begin:"}}"},{begin:'""'},l]},g=e.inherit(o,{illegal:/\n/,contains:[{begin:"{{"},{begin:"}}"},{begin:'""'},r]});l.contains=[o,c,s,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,a,e.C_BLOCK_COMMENT_MODE],r.contains=[g,c,t,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,a,e.inherit(e.C_BLOCK_COMMENT_MODE,{illegal:/\n/})];var d={variants:[o,c,s,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE]},E=e.IDENT_RE+"(<"+e.IDENT_RE+"(\\s*,\\s*"+e.IDENT_RE+")*>)?(\\[\\])?",_={begin:"@"+e.IDENT_RE,relevance:0};return{name:"C#",aliases:["cs","c#"],keywords:n,illegal:/::/,contains:[e.COMMENT("///","$",{returnBegin:!0,contains:[{className:"doctag",variants:[{begin:"///",relevance:0},{begin:"\x3c!--|--\x3e"},{begin:""}]}]}),e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,{className:"meta",begin:"#",end:"$",keywords:{"meta-keyword":"if else elif endif define undef warning error line region endregion pragma checksum"}},d,a,{beginKeywords:"class interface",end:/[{;=]/,illegal:/[^\s:,]/,contains:[{beginKeywords:"where class"},i,{begin:"<",end:">",keywords:"in out"},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},{beginKeywords:"namespace",end:/[{;=]/,illegal:/[^\s:]/,contains:[i,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},{className:"meta",begin:"^\\s*\\[",excludeBegin:!0,end:"\\]",excludeEnd:!0,contains:[{className:"meta-string",begin:/"/,end:/"/}]},{beginKeywords:"new return throw await else",relevance:0},{className:"function",begin:"("+E+"\\s+)+"+e.IDENT_RE+"\\s*\\(",returnBegin:!0,end:/\s*[{;=]/,excludeEnd:!0,keywords:n,contains:[{begin:e.IDENT_RE+"\\s*\\(",returnBegin:!0,contains:[e.TITLE_MODE],relevance:0},{className:"params",begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:n,relevance:0,contains:[d,a,e.C_BLOCK_COMMENT_MODE]},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},_]}}}());hljs.registerLanguage("ruby",function(){"use strict";return function(e){var n="[a-zA-Z_]\\w*[!?=]?|[-+~]\\@|<<|>>|=~|===?|<=>|[<>]=?|\\*\\*|[-/+%^&*~`|]|\\[\\]=?",a={keyword:"and then defined module in return redo if BEGIN retry end for self when next until do begin unless END rescue else break undef not super class case require yield alias while ensure elsif or include attr_reader attr_writer attr_accessor",literal:"true false nil"},s={className:"doctag",begin:"@[A-Za-z]+"},i={begin:"#<",end:">"},r=[e.COMMENT("#","$",{contains:[s]}),e.COMMENT("^\\=begin","^\\=end",{contains:[s],relevance:10}),e.COMMENT("^__END__","\\n$")],c={className:"subst",begin:"#\\{",end:"}",keywords:a},t={className:"string",contains:[e.BACKSLASH_ESCAPE,c],variants:[{begin:/'/,end:/'/},{begin:/"/,end:/"/},{begin:/`/,end:/`/},{begin:"%[qQwWx]?\\(",end:"\\)"},{begin:"%[qQwWx]?\\[",end:"\\]"},{begin:"%[qQwWx]?{",end:"}"},{begin:"%[qQwWx]?<",end:">"},{begin:"%[qQwWx]?/",end:"/"},{begin:"%[qQwWx]?%",end:"%"},{begin:"%[qQwWx]?-",end:"-"},{begin:"%[qQwWx]?\\|",end:"\\|"},{begin:/\B\?(\\\d{1,3}|\\x[A-Fa-f0-9]{1,2}|\\u[A-Fa-f0-9]{4}|\\?\S)\b/},{begin:/<<[-~]?'?(\w+)(?:.|\n)*?\n\s*\1\b/,returnBegin:!0,contains:[{begin:/<<[-~]?'?/},{begin:/\w+/,endSameAsBegin:!0,contains:[e.BACKSLASH_ESCAPE,c]}]}]},b={className:"params",begin:"\\(",end:"\\)",endsParent:!0,keywords:a},d=[t,i,{className:"class",beginKeywords:"class module",end:"$|;",illegal:/=/,contains:[e.inherit(e.TITLE_MODE,{begin:"[A-Za-z_]\\w*(::\\w+)*(\\?|\\!)?"}),{begin:"<\\s*",contains:[{begin:"("+e.IDENT_RE+"::)?"+e.IDENT_RE}]}].concat(r)},{className:"function",beginKeywords:"def",end:"$|;",contains:[e.inherit(e.TITLE_MODE,{begin:n}),b].concat(r)},{begin:e.IDENT_RE+"::"},{className:"symbol",begin:e.UNDERSCORE_IDENT_RE+"(\\!|\\?)?:",relevance:0},{className:"symbol",begin:":(?!\\s)",contains:[t,{begin:n}],relevance:0},{className:"number",begin:"(\\b0[0-7_]+)|(\\b0x[0-9a-fA-F_]+)|(\\b[1-9][0-9_]*(\\.[0-9_]+)?)|[0_]\\b",relevance:0},{begin:"(\\$\\W)|((\\$|\\@\\@?)(\\w+))"},{className:"params",begin:/\|/,end:/\|/,keywords:a},{begin:"("+e.RE_STARTERS_RE+"|unless)\\s*",keywords:"unless",contains:[i,{className:"regexp",contains:[e.BACKSLASH_ESCAPE,c],illegal:/\n/,variants:[{begin:"/",end:"/[a-z]*"},{begin:"%r{",end:"}[a-z]*"},{begin:"%r\\(",end:"\\)[a-z]*"},{begin:"%r!",end:"![a-z]*"},{begin:"%r\\[",end:"\\][a-z]*"}]}].concat(r),relevance:0}].concat(r);c.contains=d,b.contains=d;var g=[{begin:/^\s*=>/,starts:{end:"$",contains:d}},{className:"meta",begin:"^([>?]>|[\\w#]+\\(\\w+\\):\\d+:\\d+>|(\\w+-)?\\d+\\.\\d+\\.\\d(p\\d+)?[^>]+>)",starts:{end:"$",contains:d}}];return{name:"Ruby",aliases:["rb","gemspec","podspec","thor","irb"],keywords:a,illegal:/\/\*/,contains:r.concat(g).concat(d)}}}());hljs.registerLanguage("scss",function(){"use strict";return function(e){var t={className:"variable",begin:"(\\$[a-zA-Z-][a-zA-Z0-9_-]*)\\b"},i={className:"number",begin:"#[0-9A-Fa-f]+"};return e.CSS_NUMBER_MODE,e.QUOTE_STRING_MODE,e.APOS_STRING_MODE,e.C_BLOCK_COMMENT_MODE,{name:"SCSS",case_insensitive:!0,illegal:"[=/|']",contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,{className:"selector-id",begin:"\\#[A-Za-z0-9_-]+",relevance:0},{className:"selector-class",begin:"\\.[A-Za-z0-9_-]+",relevance:0},{className:"selector-attr",begin:"\\[",end:"\\]",illegal:"$"},{className:"selector-tag",begin:"\\b(a|abbr|acronym|address|area|article|aside|audio|b|base|big|blockquote|body|br|button|canvas|caption|cite|code|col|colgroup|command|datalist|dd|del|details|dfn|div|dl|dt|em|embed|fieldset|figcaption|figure|footer|form|frame|frameset|(h[1-6])|head|header|hgroup|hr|html|i|iframe|img|input|ins|kbd|keygen|label|legend|li|link|map|mark|meta|meter|nav|noframes|noscript|object|ol|optgroup|option|output|p|param|pre|progress|q|rp|rt|ruby|samp|script|section|select|small|span|strike|strong|style|sub|sup|table|tbody|td|textarea|tfoot|th|thead|time|title|tr|tt|ul|var|video)\\b",relevance:0},{className:"selector-pseudo",begin:":(visited|valid|root|right|required|read-write|read-only|out-range|optional|only-of-type|only-child|nth-of-type|nth-last-of-type|nth-last-child|nth-child|not|link|left|last-of-type|last-child|lang|invalid|indeterminate|in-range|hover|focus|first-of-type|first-line|first-letter|first-child|first|enabled|empty|disabled|default|checked|before|after|active)"},{className:"selector-pseudo",begin:"::(after|before|choices|first-letter|first-line|repeat-index|repeat-item|selection|value)"},t,{className:"attribute",begin:"\\b(src|z-index|word-wrap|word-spacing|word-break|width|widows|white-space|visibility|vertical-align|unicode-bidi|transition-timing-function|transition-property|transition-duration|transition-delay|transition|transform-style|transform-origin|transform|top|text-underline-position|text-transform|text-shadow|text-rendering|text-overflow|text-indent|text-decoration-style|text-decoration-line|text-decoration-color|text-decoration|text-align-last|text-align|tab-size|table-layout|right|resize|quotes|position|pointer-events|perspective-origin|perspective|page-break-inside|page-break-before|page-break-after|padding-top|padding-right|padding-left|padding-bottom|padding|overflow-y|overflow-x|overflow-wrap|overflow|outline-width|outline-style|outline-offset|outline-color|outline|orphans|order|opacity|object-position|object-fit|normal|none|nav-up|nav-right|nav-left|nav-index|nav-down|min-width|min-height|max-width|max-height|mask|marks|margin-top|margin-right|margin-left|margin-bottom|margin|list-style-type|list-style-position|list-style-image|list-style|line-height|letter-spacing|left|justify-content|initial|inherit|ime-mode|image-orientation|image-resolution|image-rendering|icon|hyphens|height|font-weight|font-variant-ligatures|font-variant|font-style|font-stretch|font-size-adjust|font-size|font-language-override|font-kerning|font-feature-settings|font-family|font|float|flex-wrap|flex-shrink|flex-grow|flex-flow|flex-direction|flex-basis|flex|filter|empty-cells|display|direction|cursor|counter-reset|counter-increment|content|column-width|column-span|column-rule-width|column-rule-style|column-rule-color|column-rule|column-gap|column-fill|column-count|columns|color|clip-path|clip|clear|caption-side|break-inside|break-before|break-after|box-sizing|box-shadow|box-decoration-break|bottom|border-width|border-top-width|border-top-style|border-top-right-radius|border-top-left-radius|border-top-color|border-top|border-style|border-spacing|border-right-width|border-right-style|border-right-color|border-right|border-radius|border-left-width|border-left-style|border-left-color|border-left|border-image-width|border-image-source|border-image-slice|border-image-repeat|border-image-outset|border-image|border-color|border-collapse|border-bottom-width|border-bottom-style|border-bottom-right-radius|border-bottom-left-radius|border-bottom-color|border-bottom|border|background-size|background-repeat|background-position|background-origin|background-image|background-color|background-clip|background-attachment|background-blend-mode|background|backface-visibility|auto|animation-timing-function|animation-play-state|animation-name|animation-iteration-count|animation-fill-mode|animation-duration|animation-direction|animation-delay|animation|align-self|align-items|align-content)\\b",illegal:"[^\\s]"},{begin:"\\b(whitespace|wait|w-resize|visible|vertical-text|vertical-ideographic|uppercase|upper-roman|upper-alpha|underline|transparent|top|thin|thick|text|text-top|text-bottom|tb-rl|table-header-group|table-footer-group|sw-resize|super|strict|static|square|solid|small-caps|separate|se-resize|scroll|s-resize|rtl|row-resize|ridge|right|repeat|repeat-y|repeat-x|relative|progress|pointer|overline|outside|outset|oblique|nowrap|not-allowed|normal|none|nw-resize|no-repeat|no-drop|newspaper|ne-resize|n-resize|move|middle|medium|ltr|lr-tb|lowercase|lower-roman|lower-alpha|loose|list-item|line|line-through|line-edge|lighter|left|keep-all|justify|italic|inter-word|inter-ideograph|inside|inset|inline|inline-block|inherit|inactive|ideograph-space|ideograph-parenthesis|ideograph-numeric|ideograph-alpha|horizontal|hidden|help|hand|groove|fixed|ellipsis|e-resize|double|dotted|distribute|distribute-space|distribute-letter|distribute-all-lines|disc|disabled|default|decimal|dashed|crosshair|collapse|col-resize|circle|char|center|capitalize|break-word|break-all|bottom|both|bolder|bold|block|bidi-override|below|baseline|auto|always|all-scroll|absolute|table|table-cell)\\b"},{begin:":",end:";",contains:[t,i,e.CSS_NUMBER_MODE,e.QUOTE_STRING_MODE,e.APOS_STRING_MODE,{className:"meta",begin:"!important"}]},{begin:"@(page|font-face)",lexemes:"@[a-z-]+",keywords:"@page @font-face"},{begin:"@",end:"[{;]",returnBegin:!0,keywords:"and or not only",contains:[{begin:"@[a-z-]+",className:"keyword"},t,e.QUOTE_STRING_MODE,e.APOS_STRING_MODE,i,e.CSS_NUMBER_MODE]}]}}}());hljs.registerLanguage("http",function(){"use strict";return function(e){var n="HTTP/[0-9\\.]+";return{name:"HTTP",aliases:["https"],illegal:"\\S",contains:[{begin:"^"+n,end:"$",contains:[{className:"number",begin:"\\b\\d{3}\\b"}]},{begin:"^[A-Z]+ (.*?) "+n+"$",returnBegin:!0,end:"$",contains:[{className:"string",begin:" ",end:" ",excludeBegin:!0,excludeEnd:!0},{begin:n},{className:"keyword",begin:"[A-Z]+"}]},{className:"attribute",begin:"^\\w",end:": ",excludeEnd:!0,illegal:"\\n|\\s|=",starts:{end:"$",relevance:0}},{begin:"\\n\\n",starts:{subLanguage:[],endsWithParent:!0}}]}}}());hljs.registerLanguage("java",function(){"use strict";return function(e){var a="false synchronized int abstract float private char boolean var static null if const for true while long strictfp finally protected import native final void enum else break transient catch instanceof byte super volatile case assert short package default double public try this switch continue throws protected public private module requires exports do",n={className:"meta",begin:"@[À-ʸa-zA-Z_$][À-ʸa-zA-Z_$0-9]*",contains:[{begin:/\(/,end:/\)/,contains:["self"]}]};return{name:"Java",aliases:["jsp"],keywords:a,illegal:/<\/|#/,contains:[e.COMMENT("/\\*\\*","\\*/",{relevance:0,contains:[{begin:/\w+@/,relevance:0},{className:"doctag",begin:"@[A-Za-z]+"}]}),e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,{className:"class",beginKeywords:"class interface",end:/[{;=]/,excludeEnd:!0,keywords:"class interface",illegal:/[:"\[\]]/,contains:[{beginKeywords:"extends implements"},e.UNDERSCORE_TITLE_MODE]},{beginKeywords:"new throw return else",relevance:0},{className:"function",begin:"([À-ʸa-zA-Z_$][À-ʸa-zA-Z_$0-9]*(<[À-ʸa-zA-Z_$][À-ʸa-zA-Z_$0-9]*(\\s*,\\s*[À-ʸa-zA-Z_$][À-ʸa-zA-Z_$0-9]*)*>)?\\s+)+"+e.UNDERSCORE_IDENT_RE+"\\s*\\(",returnBegin:!0,end:/[{;=]/,excludeEnd:!0,keywords:a,contains:[{begin:e.UNDERSCORE_IDENT_RE+"\\s*\\(",returnBegin:!0,relevance:0,contains:[e.UNDERSCORE_TITLE_MODE]},{className:"params",begin:/\(/,end:/\)/,keywords:a,relevance:0,contains:[n,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,e.C_NUMBER_MODE,e.C_BLOCK_COMMENT_MODE]},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},{className:"number",begin:"\\b(0[bB]([01]+[01_]+[01]+|[01]+)|0[xX]([a-fA-F0-9]+[a-fA-F0-9_]+[a-fA-F0-9]+|[a-fA-F0-9]+)|(([\\d]+[\\d_]+[\\d]+|[\\d]+)(\\.([\\d]+[\\d_]+[\\d]+|[\\d]+))?|\\.([\\d]+[\\d_]+[\\d]+|[\\d]+))([eE][-+]?\\d+)?)[lLfF]?",relevance:0},n]}}}());hljs.registerLanguage("swift",function(){"use strict";return function(e){var i={keyword:"#available #colorLiteral #column #else #elseif #endif #file #fileLiteral #function #if #imageLiteral #line #selector #sourceLocation _ __COLUMN__ __FILE__ __FUNCTION__ __LINE__ Any as as! as? associatedtype associativity break case catch class continue convenience default defer deinit didSet do dynamic dynamicType else enum extension fallthrough false fileprivate final for func get guard if import in indirect infix init inout internal is lazy left let mutating nil none nonmutating open operator optional override postfix precedence prefix private protocol Protocol public repeat required rethrows return right self Self set static struct subscript super switch throw throws true try try! try? Type typealias unowned var weak where while willSet",literal:"true false nil",built_in:"abs advance alignof alignofValue anyGenerator assert assertionFailure bridgeFromObjectiveC bridgeFromObjectiveCUnconditional bridgeToObjectiveC bridgeToObjectiveCUnconditional c compactMap contains count countElements countLeadingZeros debugPrint debugPrintln distance dropFirst dropLast dump encodeBitsAsWords enumerate equal fatalError filter find getBridgedObjectiveCType getVaList indices insertionSort isBridgedToObjectiveC isBridgedVerbatimToObjectiveC isUniquelyReferenced isUniquelyReferencedNonObjC join lazy lexicographicalCompare map max maxElement min minElement numericCast overlaps partition posix precondition preconditionFailure print println quickSort readLine reduce reflect reinterpretCast reverse roundUpToAlignment sizeof sizeofValue sort split startsWith stride strideof strideofValue swap toString transcode underestimateCount unsafeAddressOf unsafeBitCast unsafeDowncast unsafeUnwrap unsafeReflect withExtendedLifetime withObjectAtPlusZero withUnsafePointer withUnsafePointerToObject withUnsafeMutablePointer withUnsafeMutablePointers withUnsafePointer withUnsafePointers withVaList zip"},n=e.COMMENT("/\\*","\\*/",{contains:["self"]}),t={className:"subst",begin:/\\\(/,end:"\\)",keywords:i,contains:[]},a={className:"string",contains:[e.BACKSLASH_ESCAPE,t],variants:[{begin:/"""/,end:/"""/},{begin:/"/,end:/"/}]},r={className:"number",begin:"\\b([\\d_]+(\\.[\\deE_]+)?|0x[a-fA-F0-9_]+(\\.[a-fA-F0-9p_]+)?|0b[01_]+|0o[0-7_]+)\\b",relevance:0};return t.contains=[r],{name:"Swift",keywords:i,contains:[a,e.C_LINE_COMMENT_MODE,n,{className:"type",begin:"\\b[A-Z][\\wÀ-ʸ']*[!?]"},{className:"type",begin:"\\b[A-Z][\\wÀ-ʸ']*",relevance:0},r,{className:"function",beginKeywords:"func",end:"{",excludeEnd:!0,contains:[e.inherit(e.TITLE_MODE,{begin:/[A-Za-z$_][0-9A-Za-z$_]*/}),{begin://},{className:"params",begin:/\(/,end:/\)/,endsParent:!0,keywords:i,contains:["self",r,a,e.C_BLOCK_COMMENT_MODE,{begin:":"}],illegal:/["']/}],illegal:/\[|%/},{className:"class",beginKeywords:"struct protocol class extension enum",keywords:i,end:"\\{",excludeEnd:!0,contains:[e.inherit(e.TITLE_MODE,{begin:/[A-Za-z$_][\u00C0-\u02B80-9A-Za-z$_]*/})]},{className:"meta",begin:"(@discardableResult|@warn_unused_result|@exported|@lazy|@noescape|@NSCopying|@NSManaged|@objc|@objcMembers|@convention|@required|@noreturn|@IBAction|@IBDesignable|@IBInspectable|@IBOutlet|@infix|@prefix|@postfix|@autoclosure|@testable|@available|@nonobjc|@NSApplicationMain|@UIApplicationMain|@dynamicMemberLookup|@propertyWrapper)"},{beginKeywords:"import",end:/$/,contains:[e.C_LINE_COMMENT_MODE,n]}]}}}());hljs.registerLanguage("less",function(){"use strict";return function(e){var n="([\\w-]+|@{[\\w-]+})",a=[],s=[],t=function(e){return{className:"string",begin:"~?"+e+".*?"+e}},r=function(e,n,a){return{className:e,begin:n,relevance:a}},i={begin:"\\(",end:"\\)",contains:s,relevance:0};s.push(e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,t("'"),t('"'),e.CSS_NUMBER_MODE,{begin:"(url|data-uri)\\(",starts:{className:"string",end:"[\\)\\n]",excludeEnd:!0}},r("number","#[0-9A-Fa-f]+\\b"),i,r("variable","@@?[\\w-]+",10),r("variable","@{[\\w-]+}"),r("built_in","~?`[^`]*?`"),{className:"attribute",begin:"[\\w-]+\\s*:",end:":",returnBegin:!0,excludeEnd:!0},{className:"meta",begin:"!important"});var c=s.concat({begin:"{",end:"}",contains:a}),l={beginKeywords:"when",endsWithParent:!0,contains:[{beginKeywords:"and not"}].concat(s)},o={begin:n+"\\s*:",returnBegin:!0,end:"[;}]",relevance:0,contains:[{className:"attribute",begin:n,end:":",excludeEnd:!0,starts:{endsWithParent:!0,illegal:"[<=$]",relevance:0,contains:s}}]},g={className:"keyword",begin:"@(import|media|charset|font-face|(-[a-z]+-)?keyframes|supports|document|namespace|page|viewport|host)\\b",starts:{end:"[;{}]",returnEnd:!0,contains:s,relevance:0}},d={className:"variable",variants:[{begin:"@[\\w-]+\\s*:",relevance:15},{begin:"@[\\w-]+"}],starts:{end:"[;}]",returnEnd:!0,contains:c}},b={variants:[{begin:"[\\.#:&\\[>]",end:"[;{}]"},{begin:n,end:"{"}],returnBegin:!0,returnEnd:!0,illegal:"[<='$\"]",relevance:0,contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,l,r("keyword","all\\b"),r("variable","@{[\\w-]+}"),r("selector-tag",n+"%?",0),r("selector-id","#"+n),r("selector-class","\\."+n,0),r("selector-tag","&",0),{className:"selector-attr",begin:"\\[",end:"\\]"},{className:"selector-pseudo",begin:/:(:)?[a-zA-Z0-9\_\-\+\(\)"'.]+/},{begin:"\\(",end:"\\)",contains:c},{begin:"!important"}]};return a.push(e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,g,d,o,b),{name:"Less",case_insensitive:!0,illegal:"[=>'/<($\"]",contains:a}}}());hljs.registerLanguage("json",function(){"use strict";return function(n){var e={literal:"true false null"},i=[n.C_LINE_COMMENT_MODE,n.C_BLOCK_COMMENT_MODE],t=[n.QUOTE_STRING_MODE,n.C_NUMBER_MODE],a={end:",",endsWithParent:!0,excludeEnd:!0,contains:t,keywords:e},l={begin:"{",end:"}",contains:[{className:"attr",begin:/"/,end:/"/,contains:[n.BACKSLASH_ESCAPE],illegal:"\\n"},n.inherit(a,{begin:/:/})].concat(i),illegal:"\\S"},s={begin:"\\[",end:"\\]",contains:[n.inherit(a)],illegal:"\\S"};return t.push(l,s),i.forEach((function(n){t.push(n)})),{name:"JSON",contains:t,keywords:e,illegal:"\\S"}}}());hljs.registerLanguage("typescript",function(){"use strict";return function(e){var n={keyword:"in if for while finally var new function do return void else break catch instanceof with throw case default try this switch continue typeof delete let yield const class public private protected get set super static implements enum export import declare type namespace abstract as from extends async await",literal:"true false null undefined NaN Infinity",built_in:"eval isFinite isNaN parseFloat parseInt decodeURI decodeURIComponent encodeURI encodeURIComponent escape unescape Object Function Boolean Error EvalError InternalError RangeError ReferenceError StopIteration SyntaxError TypeError URIError Number Math Date String RegExp Array Float32Array Float64Array Int16Array Int32Array Int8Array Uint16Array Uint32Array Uint8Array Uint8ClampedArray ArrayBuffer DataView JSON Intl arguments require module console window document any number boolean string void Promise"},r={className:"meta",begin:"@[A-Za-z$_][0-9A-Za-z$_]*"},a={begin:"\\(",end:/\)/,keywords:n,contains:["self",e.QUOTE_STRING_MODE,e.APOS_STRING_MODE,e.NUMBER_MODE]},t={className:"params",begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:n,contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,r,a]},s={className:"number",variants:[{begin:"\\b(0[bB][01]+)n?"},{begin:"\\b(0[oO][0-7]+)n?"},{begin:e.C_NUMBER_RE+"n?"}],relevance:0},i={className:"subst",begin:"\\$\\{",end:"\\}",keywords:n,contains:[]},o={begin:"html`",end:"",starts:{end:"`",returnEnd:!1,contains:[e.BACKSLASH_ESCAPE,i],subLanguage:"xml"}},c={begin:"css`",end:"",starts:{end:"`",returnEnd:!1,contains:[e.BACKSLASH_ESCAPE,i],subLanguage:"css"}},E={className:"string",begin:"`",end:"`",contains:[e.BACKSLASH_ESCAPE,i]};return i.contains=[e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,o,c,E,s,e.REGEXP_MODE],{name:"TypeScript",aliases:["ts"],keywords:n,contains:[{className:"meta",begin:/^\s*['"]use strict['"]/},e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,o,c,E,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,s,{begin:"("+e.RE_STARTERS_RE+"|\\b(case|return|throw)\\b)\\s*",keywords:"return throw case",contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,e.REGEXP_MODE,{className:"function",begin:"(\\(.*?\\)|"+e.IDENT_RE+")\\s*=>",returnBegin:!0,end:"\\s*=>",contains:[{className:"params",variants:[{begin:e.IDENT_RE},{begin:/\(\s*\)/},{begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:n,contains:["self",e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]}]}]}],relevance:0},{className:"function",beginKeywords:"function",end:/[\{;]/,excludeEnd:!0,keywords:n,contains:["self",e.inherit(e.TITLE_MODE,{begin:"[A-Za-z$_][0-9A-Za-z$_]*"}),t],illegal:/%/,relevance:0},{beginKeywords:"constructor",end:/[\{;]/,excludeEnd:!0,contains:["self",t]},{begin:/module\./,keywords:{built_in:"module"},relevance:0},{beginKeywords:"module",end:/\{/,excludeEnd:!0},{beginKeywords:"interface",end:/\{/,excludeEnd:!0,keywords:"interface extends"},{begin:/\$[(.]/},{begin:"\\."+e.IDENT_RE,relevance:0},r,a]}}}());hljs.registerLanguage("c-like",function(){"use strict";return function(e){function t(e){return"(?:"+e+")?"}var n="(decltype\\(auto\\)|"+t("[a-zA-Z_]\\w*::")+"[a-zA-Z_]\\w*"+t("<.*?>")+")",r={className:"keyword",begin:"\\b[a-z\\d_]*_t\\b"},a={className:"string",variants:[{begin:'(u8?|U|L)?"',end:'"',illegal:"\\n",contains:[e.BACKSLASH_ESCAPE]},{begin:"(u8?|U|L)?'(\\\\(x[0-9A-Fa-f]{2}|u[0-9A-Fa-f]{4,8}|[0-7]{3}|\\S)|.)",end:"'",illegal:"."},{begin:/(?:u8?|U|L)?R"([^()\\ ]{0,16})\((?:.|\n)*?\)\1"/}]},s={className:"number",variants:[{begin:"\\b(0b[01']+)"},{begin:"(-?)\\b([\\d']+(\\.[\\d']*)?|\\.[\\d']+)(u|U|l|L|ul|UL|f|F|b|B)"},{begin:"(-?)(\\b0[xX][a-fA-F0-9']+|(\\b[\\d']+(\\.[\\d']*)?|\\.[\\d']+)([eE][-+]?[\\d']+)?)"}],relevance:0},i={className:"meta",begin:/#\s*[a-z]+\b/,end:/$/,keywords:{"meta-keyword":"if else elif endif define undef warning error line pragma _Pragma ifdef ifndef include"},contains:[{begin:/\\\n/,relevance:0},e.inherit(a,{className:"meta-string"}),{className:"meta-string",begin:/<.*?>/,end:/$/,illegal:"\\n"},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},c={className:"title",begin:t("[a-zA-Z_]\\w*::")+e.IDENT_RE,relevance:0},o=t("[a-zA-Z_]\\w*::")+e.IDENT_RE+"\\s*\\(",l={keyword:"int float while private char char8_t char16_t char32_t catch import module export virtual operator sizeof dynamic_cast|10 typedef const_cast|10 const for static_cast|10 union namespace unsigned long volatile static protected bool template mutable if public friend do goto auto void enum else break extern using asm case typeid wchar_t short reinterpret_cast|10 default double register explicit signed typename try this switch continue inline delete alignas alignof constexpr consteval constinit decltype concept co_await co_return co_yield requires noexcept static_assert thread_local restrict final override atomic_bool atomic_char atomic_schar atomic_uchar atomic_short atomic_ushort atomic_int atomic_uint atomic_long atomic_ulong atomic_llong atomic_ullong new throw return and and_eq bitand bitor compl not not_eq or or_eq xor xor_eq",built_in:"std string wstring cin cout cerr clog stdin stdout stderr stringstream istringstream ostringstream auto_ptr deque list queue stack vector map set bitset multiset multimap unordered_set unordered_map unordered_multiset unordered_multimap array shared_ptr abort terminate abs acos asin atan2 atan calloc ceil cosh cos exit exp fabs floor fmod fprintf fputs free frexp fscanf future isalnum isalpha iscntrl isdigit isgraph islower isprint ispunct isspace isupper isxdigit tolower toupper labs ldexp log10 log malloc realloc memchr memcmp memcpy memset modf pow printf putchar puts scanf sinh sin snprintf sprintf sqrt sscanf strcat strchr strcmp strcpy strcspn strlen strncat strncmp strncpy strpbrk strrchr strspn strstr tanh tan vfprintf vprintf vsprintf endl initializer_list unique_ptr _Bool complex _Complex imaginary _Imaginary",literal:"true false nullptr NULL"},d=[r,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,s,a],_={variants:[{begin:/=/,end:/;/},{begin:/\(/,end:/\)/},{beginKeywords:"new throw return else",end:/;/}],keywords:l,contains:d.concat([{begin:/\(/,end:/\)/,keywords:l,contains:d.concat(["self"]),relevance:0}]),relevance:0},u={className:"function",begin:"("+n+"[\\*&\\s]+)+"+o,returnBegin:!0,end:/[{;=]/,excludeEnd:!0,keywords:l,illegal:/[^\w\s\*&:<>]/,contains:[{begin:"decltype\\(auto\\)",keywords:l,relevance:0},{begin:o,returnBegin:!0,contains:[c],relevance:0},{className:"params",begin:/\(/,end:/\)/,keywords:l,relevance:0,contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,a,s,r,{begin:/\(/,end:/\)/,keywords:l,relevance:0,contains:["self",e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,a,s,r]}]},r,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,i]};return{aliases:["c","cc","h","c++","h++","hpp","hh","hxx","cxx"],keywords:l,disableAutodetect:!0,illegal:"",keywords:l,contains:["self",r]},{begin:e.IDENT_RE+"::",keywords:l},{className:"class",beginKeywords:"class struct",end:/[{;:]/,contains:[{begin://,contains:["self"]},e.TITLE_MODE]}]),exports:{preprocessor:i,strings:a,keywords:l}}}}());hljs.registerLanguage("cpp",function(){"use strict";return function(e){var t=e.getLanguage("c-like").rawDefinition();return t.disableAutodetect=!1,t.name="C++",t.aliases=["cc","c++","h++","hpp","hh","hxx","cxx"],t}}());hljs.registerLanguage("properties",function(){"use strict";return function(e){var n="[ \\t\\f]*",t="("+n+"[:=]"+n+"|[ \\t\\f]+)",a="([^\\\\:= \\t\\f\\n]|\\\\.)+",s={end:t,relevance:0,starts:{className:"string",end:/$/,relevance:0,contains:[{begin:"\\\\\\n"}]}};return{name:".properties",case_insensitive:!0,illegal:/\S/,contains:[e.COMMENT("^\\s*[!#]","$"),{begin:"([^\\\\\\W:= \\t\\f\\n]|\\\\.)+"+t,returnBegin:!0,contains:[{className:"attr",begin:"([^\\\\\\W:= \\t\\f\\n]|\\\\.)+",endsParent:!0,relevance:0}],starts:s},{begin:a+t,returnBegin:!0,relevance:0,contains:[{className:"meta",begin:a,endsParent:!0,relevance:0}],starts:s},{className:"attr",relevance:0,begin:a+n+"$"}]}}}());hljs.registerLanguage("twig",function(){"use strict";return function(e){var a="attribute block constant cycle date dump include max min parent random range source template_from_string",n={beginKeywords:a,keywords:{name:a},relevance:0,contains:[{className:"params",begin:"\\(",end:"\\)"}]},t={begin:/\|[A-Za-z_]+:?/,keywords:"abs batch capitalize column convert_encoding date date_modify default escape filter first format inky_to_html inline_css join json_encode keys last length lower map markdown merge nl2br number_format raw reduce replace reverse round slice sort spaceless split striptags title trim upper url_encode",contains:[n]},s="apply autoescape block deprecated do embed extends filter flush for from if import include macro sandbox set use verbatim with";return s=s+" "+s.split(" ").map((function(e){return"end"+e})).join(" "),{name:"Twig",aliases:["craftcms"],case_insensitive:!0,subLanguage:"xml",contains:[e.COMMENT(/\{#/,/#}/),{className:"template-tag",begin:/\{%/,end:/%}/,contains:[{className:"name",begin:/\w+/,keywords:s,starts:{endsWithParent:!0,contains:[t,n],relevance:0}}]},{className:"template-variable",begin:/\{\{/,end:/}}/,contains:["self",t,n]}]}}}());hljs.registerLanguage("javascript",function(){"use strict";return function(e){var n={begin:/<[A-Za-z0-9\\._:-]+/,end:/\/[A-Za-z0-9\\._:-]+>|\/>/},a="[A-Za-z$_][0-9A-Za-z$_]*",s={keyword:"in of if for while finally var new function do return void else break catch instanceof with throw case default try this switch continue typeof delete let yield const export super debugger as async await static import from as",literal:"true false null undefined NaN Infinity",built_in:"eval isFinite isNaN parseFloat parseInt decodeURI decodeURIComponent encodeURI encodeURIComponent escape unescape Object Function Boolean Error EvalError InternalError RangeError ReferenceError StopIteration SyntaxError TypeError URIError Number Math Date String RegExp Array Float32Array Float64Array Int16Array Int32Array Int8Array Uint16Array Uint32Array Uint8Array Uint8ClampedArray ArrayBuffer DataView JSON Intl arguments require module console window document Symbol Set Map WeakSet WeakMap Proxy Reflect Promise"},r={className:"number",variants:[{begin:"\\b(0[bB][01]+)n?"},{begin:"\\b(0[oO][0-7]+)n?"},{begin:e.C_NUMBER_RE+"n?"}],relevance:0},i={className:"subst",begin:"\\$\\{",end:"\\}",keywords:s,contains:[]},t={begin:"html`",end:"",starts:{end:"`",returnEnd:!1,contains:[e.BACKSLASH_ESCAPE,i],subLanguage:"xml"}},c={begin:"css`",end:"",starts:{end:"`",returnEnd:!1,contains:[e.BACKSLASH_ESCAPE,i],subLanguage:"css"}},o={className:"string",begin:"`",end:"`",contains:[e.BACKSLASH_ESCAPE,i]};i.contains=[e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,t,c,o,r,e.REGEXP_MODE];var l=i.contains.concat([e.C_BLOCK_COMMENT_MODE,e.C_LINE_COMMENT_MODE]),d={className:"params",begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,contains:l};return{name:"JavaScript",aliases:["js","jsx","mjs","cjs"],keywords:s,contains:[{className:"meta",relevance:10,begin:/^\s*['"]use (strict|asm)['"]/},{className:"meta",begin:/^#!/,end:/$/},e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,t,c,o,e.C_LINE_COMMENT_MODE,e.COMMENT("/\\*\\*","\\*/",{relevance:0,contains:[{className:"doctag",begin:"@[A-Za-z]+",contains:[{className:"type",begin:"\\{",end:"\\}",relevance:0},{className:"variable",begin:a+"(?=\\s*(-)|$)",endsParent:!0,relevance:0},{begin:/(?=[^\n])\s/,relevance:0}]}]}),e.C_BLOCK_COMMENT_MODE,r,{begin:/[{,\n]\s*/,relevance:0,contains:[{begin:a+"\\s*:",returnBegin:!0,relevance:0,contains:[{className:"attr",begin:a,relevance:0}]}]},{begin:"("+e.RE_STARTERS_RE+"|\\b(case|return|throw)\\b)\\s*",keywords:"return throw case",contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,e.REGEXP_MODE,{className:"function",begin:"(\\(.*?\\)|"+a+")\\s*=>",returnBegin:!0,end:"\\s*=>",contains:[{className:"params",variants:[{begin:a},{begin:/\(\s*\)/},{begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:s,contains:l}]}]},{begin:/,/,relevance:0},{className:"",begin:/\s/,end:/\s*/,skip:!0},{variants:[{begin:"<>",end:""},{begin:n.begin,end:n.end}],subLanguage:"xml",contains:[{begin:n.begin,end:n.end,skip:!0,contains:["self"]}]}],relevance:0},{className:"function",beginKeywords:"function",end:/\{/,excludeEnd:!0,contains:[e.inherit(e.TITLE_MODE,{begin:a}),d],illegal:/\[|%/},{begin:/\$[(.]/},e.METHOD_GUARD,{className:"class",beginKeywords:"class",end:/[{;=]/,excludeEnd:!0,illegal:/[:"\[\]]/,contains:[{beginKeywords:"extends"},e.UNDERSCORE_TITLE_MODE]},{beginKeywords:"constructor",end:/\{/,excludeEnd:!0},{begin:"(get|set)\\s+(?="+a+"\\()",end:/{/,keywords:"get set",contains:[e.inherit(e.TITLE_MODE,{begin:a}),{begin:/\(\)/},d]}],illegal:/#(?!!)/}}}()); \ No newline at end of file diff --git a/docs/de/assets/jquery.js b/docs/de/assets/jquery.js new file mode 100644 index 0000000..9fd22ca --- /dev/null +++ b/docs/de/assets/jquery.js @@ -0,0 +1,2 @@ +/*! jQuery v3.5.0 | (c) JS Foundation and other contributors | jquery.org/license */ +!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(C,e){"use strict";var t=[],r=Object.getPrototypeOf,s=t.slice,g=t.flat?function(e){return t.flat.call(e)}:function(e){return t.concat.apply([],e)},u=t.push,i=t.indexOf,n={},o=n.toString,v=n.hasOwnProperty,a=v.toString,l=a.call(Object),y={},m=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType},x=function(e){return null!=e&&e===e.window},E=C.document,c={type:!0,src:!0,nonce:!0,noModule:!0};function b(e,t,n){var r,i,o=(n=n||E).createElement("script");if(o.text=e,t)for(r in c)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function w(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[o.call(e)]||"object":typeof e}var f="3.5.0",S=function(e,t){return new S.fn.init(e,t)};function p(e){var t=!!e&&"length"in e&&e.length,n=w(e);return!m(e)&&!x(e)&&("array"===n||0===t||"number"==typeof t&&0+~]|"+M+")"+M+"*"),U=new RegExp(M+"|>"),X=new RegExp(F),V=new RegExp("^"+I+"$"),G={ID:new RegExp("^#("+I+")"),CLASS:new RegExp("^\\.("+I+")"),TAG:new RegExp("^("+I+"|[*])"),ATTR:new RegExp("^"+W),PSEUDO:new RegExp("^"+F),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+R+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/HTML$/i,Q=/^(?:input|select|textarea|button)$/i,J=/^h\d$/i,K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\[\\da-fA-F]{1,6}"+M+"?|\\\\([^\\r\\n\\f])","g"),ne=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},re=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){T()},ae=be(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{H.apply(t=O.call(p.childNodes),p.childNodes),t[p.childNodes.length].nodeType}catch(e){H={apply:t.length?function(e,t){L.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&(T(e),e=e||C,E)){if(11!==p&&(u=Z.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&y(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return H.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&d.getElementsByClassName&&e.getElementsByClassName)return H.apply(n,e.getElementsByClassName(i)),n}if(d.qsa&&!N[t+" "]&&(!v||!v.test(t))&&(1!==p||"object"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===p&&(U.test(t)||z.test(t))){(f=ee.test(t)&&ye(e.parentNode)||e)===e&&d.scope||((s=e.getAttribute("id"))?s=s.replace(re,ie):e.setAttribute("id",s=S)),o=(l=h(t)).length;while(o--)l[o]=(s?"#"+s:":scope")+" "+xe(l[o]);c=l.join(",")}try{return H.apply(n,f.querySelectorAll(c)),n}catch(e){N(t,!0)}finally{s===S&&e.removeAttribute("id")}}}return g(t.replace($,"$1"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function le(e){return e[S]=!0,e}function ce(e){var t=C.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){var n=e.split("|"),r=n.length;while(r--)b.attrHandle[n[r]]=t}function pe(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function de(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return("input"===t||"button"===t)&&e.type===n}}function ge(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ve(a){return le(function(o){return o=+o,le(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ye(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}for(e in d=se.support={},i=se.isXML=function(e){var t=e.namespaceURI,n=(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||"HTML")},T=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:p;return r!=C&&9===r.nodeType&&r.documentElement&&(a=(C=r).documentElement,E=!i(C),p!=C&&(n=C.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",oe,!1):n.attachEvent&&n.attachEvent("onunload",oe)),d.scope=ce(function(e){return a.appendChild(e).appendChild(C.createElement("div")),"undefined"!=typeof e.querySelectorAll&&!e.querySelectorAll(":scope fieldset div").length}),d.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),d.getElementsByTagName=ce(function(e){return e.appendChild(C.createComment("")),!e.getElementsByTagName("*").length}),d.getElementsByClassName=K.test(C.getElementsByClassName),d.getById=ce(function(e){return a.appendChild(e).id=S,!C.getElementsByName||!C.getElementsByName(S).length}),d.getById?(b.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=d.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):d.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},b.find.CLASS=d.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],v=[],(d.qsa=K.test(C.querySelectorAll))&&(ce(function(e){var t;a.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&v.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||v.push("\\["+M+"*(?:value|"+R+")"),e.querySelectorAll("[id~="+S+"-]").length||v.push("~="),(t=C.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||v.push("\\["+M+"*name"+M+"*="+M+"*(?:''|\"\")"),e.querySelectorAll(":checked").length||v.push(":checked"),e.querySelectorAll("a#"+S+"+*").length||v.push(".#.+[+~]"),e.querySelectorAll("\\\f"),v.push("[\\r\\n\\f]")}),ce(function(e){e.innerHTML="";var t=C.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&v.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&v.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&v.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),v.push(",.*:")})),(d.matchesSelector=K.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){d.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",F)}),v=v.length&&new RegExp(v.join("|")),s=s.length&&new RegExp(s.join("|")),t=K.test(a.compareDocumentPosition),y=t||K.test(a.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},D=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!d.sortDetached&&t.compareDocumentPosition(e)===n?e==C||e.ownerDocument==p&&y(p,e)?-1:t==C||t.ownerDocument==p&&y(p,t)?1:u?P(u,e)-P(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e==C?-1:t==C?1:i?-1:o?1:u?P(u,e)-P(u,t):0;if(i===o)return pe(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?pe(a[r],s[r]):a[r]==p?-1:s[r]==p?1:0}),C},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if(T(e),d.matchesSelector&&E&&!N[t+" "]&&(!s||!s.test(t))&&(!v||!v.test(t)))try{var n=c.call(e,t);if(n||d.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){N(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return G.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=h(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=m[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&m(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function D(e,n,r){return m(n)?S.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?S.grep(e,function(e){return e===n!==r}):"string"!=typeof n?S.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(S.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||j,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:q.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof S?t[0]:t,S.merge(this,S.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:E,!0)),N.test(r[1])&&S.isPlainObject(t))for(r in t)m(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=E.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):m(e)?void 0!==n.ready?n.ready(e):e(S):S.makeArray(e,this)}).prototype=S.fn,j=S(E);var L=/^(?:parents|prev(?:Until|All))/,H={children:!0,contents:!0,next:!0,prev:!0};function O(e,t){while((e=e[t])&&1!==e.nodeType);return e}S.fn.extend({has:function(e){var t=S(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,he=/^$|^module$|\/(?:java|ecma)script/i;ce=E.createDocumentFragment().appendChild(E.createElement("div")),(fe=E.createElement("input")).setAttribute("type","radio"),fe.setAttribute("checked","checked"),fe.setAttribute("name","t"),ce.appendChild(fe),y.checkClone=ce.cloneNode(!0).cloneNode(!0).lastChild.checked,ce.innerHTML="",y.noCloneChecked=!!ce.cloneNode(!0).lastChild.defaultValue,ce.innerHTML="",y.option=!!ce.lastChild;var ge={thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};function ve(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&A(e,t)?S.merge([e],n):n}function ye(e,t){for(var n=0,r=e.length;n",""]);var me=/<|&#?\w+;/;function xe(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d\s*$/g;function qe(e,t){return A(e,"table")&&A(11!==t.nodeType?t:t.firstChild,"tr")&&S(e).children("tbody")[0]||e}function Le(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function He(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Oe(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(Y.hasData(e)&&(s=Y.get(e).events))for(i in Y.remove(t,"handle events"),s)for(n=0,r=s[i].length;n").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),E.head.appendChild(r[0])},abort:function(){i&&i()}}});var Ut,Xt=[],Vt=/(=)\?(?=&|$)|\?\?/;S.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=Xt.pop()||S.expando+"_"+Ct.guid++;return this[e]=!0,e}}),S.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Vt.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Vt.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=m(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Vt,"$1"+r):!1!==e.jsonp&&(e.url+=(Et.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||S.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=C[r],C[r]=function(){o=arguments},n.always(function(){void 0===i?S(C).removeProp(r):C[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,Xt.push(r)),o&&m(i)&&i(o[0]),o=i=void 0}),"script"}),y.createHTMLDocument=((Ut=E.implementation.createHTMLDocument("").body).innerHTML="
",2===Ut.childNodes.length),S.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(y.createHTMLDocument?((r=(t=E.implementation.createHTMLDocument("")).createElement("base")).href=E.location.href,t.head.appendChild(r)):t=E),o=!n&&[],(i=N.exec(e))?[t.createElement(i[1])]:(i=xe([e],t,o),o&&o.length&&S(o).remove(),S.merge([],i.childNodes)));var r,i,o},S.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(S.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},S.expr.pseudos.animated=function(t){return S.grep(S.timers,function(e){return t===e.elem}).length},S.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=S.css(e,"position"),c=S(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=S.css(e,"top"),u=S.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),m(t)&&(t=t.call(e,n,S.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):("number"==typeof f.top&&(f.top+="px"),"number"==typeof f.left&&(f.left+="px"),c.css(f))}},S.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){S.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===S.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===S.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=S(e).offset()).top+=S.css(e,"borderTopWidth",!0),i.left+=S.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-S.css(r,"marginTop",!0),left:t.left-i.left-S.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===S.css(e,"position"))e=e.offsetParent;return e||re})}}),S.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;S.fn[t]=function(e){return $(this,function(e,t,n){var r;if(x(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),S.each(["top","left"],function(e,n){S.cssHooks[n]=$e(y.pixelPosition,function(e,t){if(t)return t=Be(e,n),Me.test(t)?S(e).position()[n]+"px":t})}),S.each({Height:"height",Width:"width"},function(a,s){S.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){S.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return $(this,function(e,t,n){var r;return x(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?S.css(e,t,i):S.style(e,t,n,i)},s,n?e:void 0,n)}})}),S.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){S.fn[t]=function(e){return this.on(t,e)}}),S.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),S.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){S.fn[n]=function(e,t){return 0 a",this.offsets=[],this.targets=[],this.activeTarget=null,this.scrollHeight=0,this.$scrollElement.on("scroll.bs.scrollspy",r.proxy(this.process,this)),this.refresh(),this.process()}function s(i){return this.each(function(){var t=r(this),s=t.data("bs.scrollspy"),e="object"==typeof i&&i;s||t.data("bs.scrollspy",s=new o(this,e)),"string"==typeof i&&s[i]()})}o.VERSION="3.3.7",o.DEFAULTS={offset:10},o.prototype.getScrollHeight=function(){return this.$scrollElement[0].scrollHeight||Math.max(this.$body[0].scrollHeight,document.documentElement.scrollHeight)},o.prototype.refresh=function(){var t=this,i="offset",o=0;this.offsets=[],this.targets=[],this.scrollHeight=this.getScrollHeight(),r.isWindow(this.$scrollElement[0])||(i="position",o=this.$scrollElement.scrollTop()),this.$body.find(this.selector).map(function(){var t=r(this),s=t.data("target")||t.attr("href"),e=/^#./.test(s)&&r(s);return e&&e.length&&e.is(":visible")&&[[e[i]().top+o,s]]||null}).sort(function(t,s){return t[0]-s[0]}).each(function(){t.offsets.push(this[0]),t.targets.push(this[1])})},o.prototype.process=function(){var t,s=this.$scrollElement.scrollTop()+this.options.offset,e=this.getScrollHeight(),i=this.options.offset+e-this.$scrollElement.height(),o=this.offsets,r=this.targets,l=this.activeTarget;if(this.scrollHeight!=e&&this.refresh(),i<=s)return l!=(t=r[r.length-1])&&this.activate(t);if(l&&s=o[t]&&(void 0===o[t+1]||s'))&&w.css("position",n.css("position")),(v=function(){var t,o,i;if(!f)return k=x.height(),t=parseInt(b.css("border-top-width"),10),o=parseInt(b.css("padding-top"),10),l=parseInt(b.css("padding-bottom"),10),a=b.offset().top+t+o,c=b.height(),g&&(h=g=!1,null==V&&(n.insertAfter(w),w.detach()),n.css({position:"",top:"",width:"",bottom:""}).removeClass(j),i=!0),p=n.offset().top-(parseInt(n.css("margin-top"),10)||0)-F,d=n.outerHeight(!0),u=n.css("float"),w&&w.css({width:r(n),height:d,display:n.css("display"),"vertical-align":n.css("vertical-align"),float:u}),i?e():void 0})(),m=void 0,y=F,_=A,e=function(){var t,o,i,e,s,r;if(d!==c&&!f)return i=!1,null!=_&&(_-=1)<=0&&(_=A,v(),i=!0),i||x.height()===k||(v(),i=!0),e=Q.scrollTop(),null!=m&&(o=e-m),m=e,g?(C&&(s=c+a + + + + + + + + VR Payment Zahlungs-Plugin für Shopware 6 + + + + +
+
+

VR Payment Zahlungs-Plugin für Shopware 6

+

Documentation

+ +
+
+
+
+
+
+

+ 1Übersicht

+
+
+
+

Das VR Payment Zahlungs-Plugin integriert moderne Zahlungsabwicklung in Shopware 6 und bietet Funktionen wie iFrame-basierte Zahlungen, Rückerstattungen, Erfassungen und PCI-Konformität. Es unterstützt die nahtlose Integration mit dem VR Payment Portal zur Verwaltung von Transaktionen und Zahlungsmethoden.

+
+

Wichtig: Bitte beachten Sie, dass nur Major- (z.B. 6.x.0.0) und Minor-Updates (z.B. 6.0.x.0) innerhalb von 2 Wochen nach der Veröffentlichung auf Kompatibilität getestet werden.

+
+
+
+

+ 2Anforderungen

+
+
+
+
    +
  • +

    Shopware Version: 6.5.x oder 6.6.x (see Kompatibilitätstabelle).

    +
  • +
  • +

    PHP: Mindestversion, wie von Ihrer Shopware-Installation erforderlich (z.B. 7.4+).

    +
  • +
  • +

    VR Payment Konto: Holen Sie sich die Space ID, User ID und den API Key vom VR Payment Portal (siehe Portal-Startanleitung).

    +
  • +
+
+
+
+

+ 3Kompatibilität

+
+
+ ++++++ + + + + + + + + + + + + + + + + + + + + +

Shopware Version

Plugin Version

PHP Version

Support Until

6.6.x

6.x.x

PHP 8.2 and 8.3

Ongoing

6.5.x - Deprecated

5.x.x

PHP 8.2 and 8.3

October 2024

+
+
+

+ 4Installation

+
+
+
+
+

+ 4.1Via Marketplace

+
+
+
+
    +
  1. +

    Melden Sie sich im Backend Ihres Shopware-Shops an..

    +
  2. +
  3. +

    Navigieren Sie zu Einstellungen → System → Plugins.

    +
  4. +
  5. +

    Klicken Sie auf den Menü-Caret und wählen Sie den Link Installieren des Plugins, um es zu installieren.

    +
    +
    +plugin installation +
    +
    +
  6. +
  7. +

    Aktivieren Sie das VR Payment Zahlungs-Plugin im Plugin-Manager.

    +
  8. +
+
+
+
+
+

+ 5Portal-Startanleitung

+
+
+
+

Gehen Sie zu VR Payment und erstellen Sie ein Konto, falls Sie noch keines haben.

+
+ + + + + +
+
Tip
+
+Wählen Sie das passende Abo aus – es sollte E-Commerce-Transaktionen unterstützen. +
+
+
+

+ 5.1Erstellen Sie den API-Schlüssel:

+
+
+
+
    +
  1. +

    Nach der Aktivierung Ihres Kontos navigieren Sie zu Account → Applikationsbenutzer

    +
    +
    +application users +
    +
    +
  2. +
  3. +

    Klicken Sie auf Applikationsbenutzer erstellen

    +
    +
    +user +
    +
    +
  4. +
  5. +

    Geben Sie einen Namen für diesen API-Schlüssel an – ich werde Test Shop eingeben – und klicken Sie auf Applikationsbenutzer erstellen

    +
    +
    +api key +
    +
    +
  6. +
  7. +

    Ihr Applikationsbenutzer wird dann erstellt. Kopieren Sie die User-ID und den Authentifizierungsschlüssel, da Sie diese benötigen, um Ihren Shop mit dem Portal zu verknüpfen

    +
    +
    +token +
    +
    +
  8. +
  9. +

    Abschliessend müssen Sie diesem Applikationsbenutzer bestimmte Berechtigungen erteilen, damit er mit Ihrem Space kommunizieren kann. Navigieren Sie dazu unter Rolle zu Verwalten.

    +
    +
    +roles +
    +
    +
  10. +
  11. +

    Klicken Sie auf das + Zeichen neben Space-Roles.

    +
    +
    +assign role +
    +
    +
  12. +
  13. +

    Fügen Sie die Rolle “Space-Admin” hinzu und klicken Sie auf Rolle zuweisen.

    +
    + + + + + +
    +
    Tip
    +
    +Bitte beachten Sie, dass das Laden der Rollen einige Sekunden dauern kann. +
    +
    +
    +
    +loading roles +
    +
    +
  14. +
  15. +

    Klicken Sie abschließend auf Rollen speichern; Sie müssen Ihr Passwort zur Bestätigung eingeben.

    +
    +
    +save role +
    +
    +
  16. +
+
+
+
+

+ 5.2Zahlungsmethoden einrichten

+
+
+
+

Sie können nun die gewünschten Zahlungsmethoden einrichten. Für Testzwecke verwenden wir den Bogus-Verarbeiter. Bitte beachten Sie, dass Sie für Produktions Transaktionen entweder unser Payfac-Angebot nutzen oder sich direkt mit einem bestimmten PSP (Worldline; Nexi; …) verbinden können.

+
+
    +
  1. +

    Navigieren Sie zu Space → Einstellungen und klicken Sie auf Verarbeiter.

    +
    +
    +payment settings +
    +
    +
  2. +
  3. +

    Klicken Sie auf Verarbeiter einrichten.

    +
  4. +
  5. +

    Wählen Sie den gewünschten Verarbeiter aus und klicken Sie auf Weiter.

    +
    +
    +bogus processor +
    +
    +
  6. +
  7. +

    Richten Sie den Namen ein, den Sie Ihrem Verarbeiter geben möchten – z.B. Test Verarbeiter – und klicken Sie auf Erstellen.

    +
    +
    +name processor +
    +
    +
  8. +
  9. +

    Wählen Sie alle Konnektoren aus welche Sie nutzen möchten und klicken Sie auf Speichern.

    +
    + + + + + +
    +
    Tip
    +
    +Bitte beachten Sie, dass die Konnektoren doppelt erscheinen, da einer für Zahlungen über physische Terminals und einer für E-Commerce-Zahlungen bestimmt ist. +
    +
    +
    +
    +connectors +
    +
    +
  10. +
+
+

Zahlungsmethoden sind nun im Portal verfügbar.

+
+
+
+
+

+ 6Shop-Startanleitung

+
+
+
+
    +
  1. +

    API-Zugangsdaten

    +
    +
      +
    1. +

      Navigieren Sie zu Shopware Admin → Einstellungen → Erweiterungen → VR Payment Payment und klicken Sie auf Speichern.

      +
    2. +
    3. +

      Geben Sie Ihre Space ID, User ID, und API Key ein.

      +
      +
        +
      1. +

        Die Space-ID finden Sie unter Space – z.B. Space-ID = 76231.

        +
        +
        +plugin configuration +
        +
        +
      2. +
      +
      +
    4. +
    +
    +
  2. +
  3. +

    Zahlungsmethoden

    +
    +

    Die an der Kasse verfügbaren Zahlungsmethoden werden vom VR Payment Portal gesteuert. Wenn Sie eine Zahlungsmethode deaktivieren möchten, müssen Sie diese Zahlungsmethode im Portal deaktivieren.

    +
    +
    + + + + + +
    +
    Note
    +
    +Bitte beachten Sie, dass aufgrund der Synchronisierung zwischen Portal und Shop dasselbe auch im Shop unter Einstellungen → Zahlungsmethoden möglich ist. +
    +
    +
    +
      +
    1. +

      Gehen Sie zu Ihrem Space → Einstellungen → Zahlung → Zahlarten

      +
      +
      +payment methods +
      +
      +
    2. +
    3. +

      Wählen Sie die Zahlungsmethode aus, die Sie deaktivieren möchten. Klicken Sie auf den Schieberegler, um sie zu deaktivieren → sie sollte inaktiv werden.

      +
      +
      +cc enable +
      +
      +
      +
      +cc disable +
      +
      +
      + + + + + +
      +
      Tip
      +
      +Wenn Sie nur einen Konnektor einer Zahlungsmethode deaktivieren möchten (z.B. Mastercard für eine Kreditkarte), gehen Sie zum Abschnitt Konnektor. +
      +
      +
    4. +
    +
    +
  4. +
  5. +

    Optionen

    +
    +
      +
    1. +

      Space View Id: Dieses Feld ermöglicht es Ihnen, benutzerdefinierte Stile auf das Zahlungsformular und die Zahlungsseite anzuwenden. Die Stile werden in Ihren Space-Einstellungen im Portal definiert.

      +
      + + + + + +
      +
      Note
      +
      +Bitte beachten Sie, dass diese Option leer bleiben sollte, wenn Sie die Space View Id nicht verwenden. +
      +
      +
    2. +
    3. +

      Integration: Die Einstellung Integrationsoptionen bestimmt, wie das Zahlungsformular während des Checkout-Prozesses angezeigt wird. Die folgenden Optionen sind verfügbar:

      +
      +
        +
      1. +

        IFrame: Betten Sie das Zahlungsformular direkt in die Shopware-Checkout-Seite ein, um ein nahtloses Erlebnis zu gewährleisten.

        +
      2. +
      3. +

        Lightbox: Öffnet ein sicheres Popup-Fenster, in dem Kunden ihre Zahlung abschließen können, ohne die Checkout-Seite zu verlassen.

        +
      4. +
      5. +

        Payment Page: Leitet Kunden auf eine dedizierte Zahlungsseite weiter, die vom Zahlungsanbieter gehostet wird.

        +
      6. +
      +
      +
    4. +
    5. +

      Line Item Consistency: Shopware berechnet Steuern auf Ebene der Einzelpositionen, was zu geringfügigen Abweichungen (typischerweise einige Cent) zwischen der Gesamtsteuer der Bestellung und dem angezeigten Preis führen kann. Diese Abweichung tritt aufgrund von Rundungsdifferenzen bei der Berechnung einzelner Einzelpositionen auf. Wenn die Einstellung "Konsistenz erzwingen" aktiviert ist, lehnt das Portal automatisch Bestellungen mit solchen Abweichungen ab. Um Probleme bei der Zahlungsabwicklung zu vermeiden, empfehlen wir, diese Einstellung zu deaktivieren, es sei denn, eine strenge Validierung der Gesamtsteuer ist erforderlich.

      +
    6. +
    7. +

      Bestellbestätigungs-E-Mail senden: Aktivieren Sie diese Option, um Bestellbestätigungs-E-Mails direkt von Shopware anstelle des Portals zu senden.

      +
      +
      +order confirmation email +
      +
      +
    8. +
    +
    +
  6. +
+
+
+
+

+ 7Transaktionszustandsdiagramm

+
+
+
+

Der Zahlungsprozess von VR Payment ist für jede von Ihnen verarbeitbare Zahlungsmethode vollständig standardisiert. Dies gibt Ihnen die Möglichkeit, einfach eine Zahlungsmethode oder einen Prozessor hinzuzufügen, ohne Änderungen in Ihrer Shopware-Konfiguration vorzunehmen. Eine Übersicht über die Zustände und die Zahlungsprozesse von VR Payment finden Sie in de Zahlungsdokumentation.

+
+

Im folgenden Abschnitt geben wir Ihnen einen Überblick darüber, wie die VR Payment-Zustände in das Shopware-Zustandsdiagramm für Bestell- und Zahlungszustände abgebildet werden.

+
+
+

+ 7.1Zustandsabbildung von Shopware-Bestellungen

+
+
+
+

Wir ändern derzeit nicht den Bestellstatus. Wir ändern nur den Zahlungsstatus und den Lieferstatus.

+
+
+

+ 7.1.1Allgemeine Anmerkungen zu Bestellstatus

+
+
+
+

Wir empfehlen, dass Sie den Bestellstatus erst ändern, wenn der Zahlungsstatus einen Endzustand erreicht hat.

+
+
+
+
+

+ 7.2Zustandsabbildung des Shopware-Zahlungsstatus

+
+
+
+

Nachfolgend finden Sie ein Diagramm, das den Zustandsprozess von Shopware für den Zahlungsstatus einschließlich zusätzlicher Informationen zu den Zustandsübergängen zeigt

+
+
+shopware 6 stage graph order +
+
+
    +
  1. +

    Wenn die Transaktion in VR Payment Authorized ist, wird der Shopware-Bestellzahlungsstatus als In Progress markiert.

    +
  2. +
  3. +

    Wenn die Transaktion vor oder während des Autorisierungsprozesses fehlschlägt, wird der Shopware-Bestellzahlungsstatus als Failed markiert.

    +
  4. +
  5. +

    Wenn die Transaktion nach der Authorized fehlschlägt, wird der Shopware-Bestellzahlungsstatus als Cancelled markiert.

    +
  6. +
  7. +

    Wenn die Transaktionsrechnung in VR Payment als Paid oder Not Applicable, markiert ist, wird der Shopware-Bestellzahlungsstatus als Paid markiert.

    +
  8. +
+
+
+

+ 7.2.1Allgemeine Anmerkungen zu Zahlungsstatus

+
+
+
+

Wir empfehlen, dass Sie den Zahlungsstatus nicht manuell ändern. Wenn Sie dies tun, kann er vom Plugin erneut geändert werden.

+
+
+
+
+

+ 7.3Zustandsabbildung des Shopware-Lieferstatus

+
+
+
+

Nachfolgend finden Sie ein Diagramm, das den Zustandsprozess des Shopware-Lieferstatus einschließlich zusätzlicher Informationen zu den Zustandsübergängen zeigt

+
+
+shopware 6 stage graph delivery +
+
+
    +
  1. +

    Wenn die Transaktion in VR Payment den Status Confirmed hat, wird der Shopware-Bestelllieferstatus als Hold markiert.

    +
  2. +
  3. +

    Wenn die Transaktion in VR Payment als Fulfill markiert ist, wird der Shopware-Bestelllieferstatus als Open markiert.

    +
  4. +
  5. +

    Wenn die Transaktion Decline, Failed oder Voided, wird der Shopware-Bestelllieferstatus als Cancelled markiert.

    +
  6. +
+
+
+
+
+

+ 8Transaktionsverwaltung

+
+
+
+

Sie können Transaktionen direkt im Shopware-Backend erfassen, stornieren und erstatten. Bitte beachten Sie, dass Ereignisse in Shopware synchronisiert werden, wenn Sie Transaktionen in VR Payment erstatten, ungültig machen oder erfassen. Es gibt jedoch einige Einschränkungen (siehe unten).

+
+
+

+ 8.1Bestellung abschließen (erfassen)

+
+
+
+

Sie haben die Möglichkeit, dass Ihre Transaktionen nach der Bestellung nur autorisiert werden. In der Konfiguration des Konnektors haben Sie die Möglichkeit, sofern die Zahlungsmethode dies unterstützt, festzulegen, ob die Zahlung sofort oder verzögert abgeschlossen werden soll. Um eine Transaktion zu erfassen, öffnen Sie die Bestellung und klicken Sie auf die Schaltfläche "Abschließen".

+
+ + + + + +
+
Note
+
+Wenn der Abschluss in VR Payment ausstehend ist, bleibt die Bestellung im Status "Ausstehend". +
+
+
+capture transaction +
+
+

Verzögerter Zahlungsabschluss

+
+

Händler haben oft den Fall, dass sie Transaktionen nur autorisieren und den Erfüllungsprozess erst starten möchten, wenn alle Artikel versandfähig sind. Dies ist auch mit VR Payment möglich. Es sollten jedoch bestimmte Prozesse befolgt werden. Wenn Sie den Zahlungsabschluss als verzögert konfiguriert haben, sollten Sie die Transaktion erfassen, bevor Sie den Versand einleiten, da ein Abschluss immer fehlschlagen kann. Wenn Sie sicherstellen möchten, dass Sie keine Artikel versenden, für die Sie nicht bezahlt wurden, sollten Sie den Versand verschieben, bis der Erfüllungsstatus erreicht ist. Anfangs befindet sich die Transaktion in VR Payment im Status Authorized und in Shopware im Status In Progress. Wenn Sie den Erfüllungsprozess starten möchten, stellen Sie sicher, dass Sie den Abschlussprozess wie oben beschrieben einleiten. Sobald der Abschluss erfolgreich war, wechselt die Bestellung in VR Payment in den Status Fulfill und in Shopware in den Status Paid. Sie können nun den Erfüllungsprozess starten.

+
+
+
+

+ 8.2Transaktion stornieren

+
+
+
+

Um eine Transaktion zu stornieren machen, öffnen Sie die Bestellung und klicken Sie auf die Schaltfläche Cancel authorization.

+
+ + + + + +
+
Note
+
+Sie können nur Transaktionen stornieren, die noch nicht abgeschlossen sind. +
+
+
+void transaction +
+
+
+
+

+ 8.3Rückerstattung einer Transaktion

+
+
+
+

Sie haben die Möglichkeit, bereits abgeschlossene Transaktionen zurückzuerstatten. Öffnen Sie dazu die erfasste Bestellung. Indem Sie auf die 3 Punkte (…) in einer Einzelposition klicken, können Sie die Einzelposition teilweise (wenn sie eine höhere Menge als 1 hat) oder die gesamte Einzelposition zurückerstatten. Falls die Zahlungsmethode keine Rückerstattungen unterstützt, sehen Sie nicht die Möglichkeit, Online-Rückerstattungen auszustellen.

+
+
+refund transaction +
+
+

Sie können beliebig viele einzelne Rückerstattungen durchführen, bis Sie den Gesamtbetrag der ursprünglichen Bestellung erreicht haben. Der Status der Bestellung wechselt dann automatisch auf "abgeschlossen".

+
+ + + + + +
+
Note
+
+Es kann einige Zeit dauern, bis Sie die Rückerstattung in Shopware sehen. Rückerstattungen sind erst sichtbar, wenn sie erfolgreich verarbeitet wurden. +
+
+
+
+

+ 8.4Bestellungen auf On Hold

+
+
+
+

Die Lieferung sollte nicht erfolgen, solange der Lieferstatus Hold ist. Dies tritt auf, wenn die Transaktion in VR Payment den Erfüllungsstatus nicht erreicht hat.

+
+

Dafür gibt es im Wesentlichen zwei Gründe:

+
+
    +
  • +

    Die Transaktion ist nicht abgeschlossen. In diesem Fall müssen Sie die Transaktion wie oben beschrieben abschließen.

    +
  • +
  • +

    Wir können nicht sagen, ob Sie die Bestellung erfüllen sollen. Die Lieferentscheidung erfolgt automatisch. Wenn dies nicht innerhalb des definierten Zeitrahmens erfolgt, generiert VR Payment eine manuelle Aufgabe, die Sie beachten und deren Anweisungen Sie befolgen sollten.

    +
  • +
+
+

Weitere Informationen zu manuellen Aufgaben finden Sie in unserer Dokumentation zu manuellen Aufgaben..

+
+
+
+

+ 8.5Einschränkungen der Synchronisierung zwischen VR Payment und Shopware

+
+
+
+

Bitte beachten Sie, dass Erfassungen, Stornierungen und Rückerstattungen, die in VR Payment durchgeführt werden, synchronisiert werden. Es gibt jedoch einige Einschränkungen. In VR Payment können Sie den Einzelpreis und die Menge gleichzeitig ändern. Dies ist im Shopware-Backend nicht möglich. Wir empfehlen daher, die Rückerstattungen immer im Shopware-Backend und nicht in VR Payment durchzuführen. Wenn eine Rückerstattung nicht synchronisiert werden kann, wird sie an den Prozessor gesendet, aber es kann sein, dass Sie sie nicht in Ihrem Shopware-Backend sehen.

+
+

Weitere Informationen zu Rückerstattungen in VR Payment finden Sie in unserer Dokumentation zu Rückerstattungen..

+
+
+
+

+ 8.6Tokenisierung

+
+
+
+

Falls die Zahlungsmethode die Tokenisierung unterstützt, können Sie die Zahlungsdetails Ihrer Kunden für zukünftige Einkäufe speichern. Um diese Funktion zu nutzen, stellen Sie sicher, dass der One-Click-Zahlungsmodus in Ihrer Zahlungsmethodenkonfiguration auf allow oder force eingestellt ist.

+
+ + + + + +
+
Note
+
+Die Tokenisierung ist für Gast-Checkouts nicht verfügbar +
+
+
+
+

+ 8.7Hauptfunktionen

+
+
+
+
    +
  • +

    iFrame Integration: Betten Sie Zahlungsformulare direkt in Ihren Checkout ein.

    +
  • +
  • +

    Refunds & Captures: Lösen Sie vollständige/teilweise Rückerstattungen und Erfassungen von Shopware oder dem VR Payment Portal aus.

    +
  • +
  • +

    Multi-Store Support: Verwalten Sie Konfigurationen über mehrere Stores hinweg.

    +
  • +
  • +

    Automatic Updates: Zahlungsmethoden werden dynamisch über die VR Payment API synchronisiert.

    +
  • +
+
+
+
+

+ 8.8Fehlerbehebung

+
+
+
+
    +
  • +

    Logs: Überprüfen Sie Zahlungslogs mit:

    +
    +
    +
    COPY
    +tail -f var/log/whitelabelname_payment*.log
    +
    +
    +
  • +
  • +

    Häufige Probleme:

    +
    +
      +
    • +

      Stellen Sie sicher, dass composer update wallee/shopware-6 nach Updates ausgeführt wird.

      +
    • +
    • +

      Überprüfen Sie, ob die API-Zugangsdaten mit Ihrem VR Payment-Konto übereinstimmen.

      +
    • +
    +
    +
  • +
+
+
+
+

+ 8.9FAQs

+
+
+
+

F: Wie kann ich sicherstellen, dass die Verbindung zwischen Portal und Shop funktioniert?

+
+

A: Sie müssen überprüfen, ob die Webhooks korrekt erstellt wurden. Navigieren Sie dazu in Ihrem Space im Portal zu Einstellungen → Allgemein → Webhook-Listener.

+
+
+webhooks +
+
+
+webhook listeners +
+
+

F: Unterstützt dieses Plugin One-Click-Zahlungen? +A: Ja, über die Tokenisierung im VR Payment Portal.

+
+

F: Wie handhabe ich die PCI-Konformität? +A: Das Plugin verwendet die iFrame-Integration, wodurch die PCI-Anforderungen auf SAQ-A reduziert werden.

+
+

F: Unterstützt das Plugin Apple Pay? +A: Ja, das Plugin unterstützt Wallets wie Apple Pay.

+
+
+
+
+

+ 9Änderungsprotokoll

+
+
+
+

Versionsspezifische Updates finden Sie unter GitHub Releases.

+
+
+
+

+ 10Mitwirken

+
+
+
+

Melden Sie Probleme über GitHub Issues.

+
+

Befolgen Sie den Shopware Shopware Plugin Base Guide für die Entwicklung.

+
+
+
+

+ 11Support

+
+
+
+

Wenn Sie Hilfe benötigen, wenden Sie sich bitte an unseren support.

+
+
+
+
+ +
+
+ + + + + + + + diff --git a/docs/en/assets/base.css b/docs/en/assets/base.css new file mode 100644 index 0000000..bb25826 --- /dev/null +++ b/docs/en/assets/base.css @@ -0,0 +1,692 @@ +* { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} + +*:before, *:after { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} + +html { + font-size: 100%; + font-family: sans-serif; + line-height: 1.15; + -webkit-text-size-adjust: 100%; + -ms-text-size-adjust: 100%; + -ms-overflow-style: scrollbar; + -webkit-tap-highlight-color: transparent; +} + +@-ms-viewport { + width: device-width; +} + +article, aside, dialog, figcaption, figure, footer, header, hgroup, main, nav, section { + display: block; +} + +body { + margin: 0; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; + font-size: 1rem; + font-weight: 300; + line-height: 1.5; + color: #212529; + text-align: left; + background-color: #fff; + padding-right: 0 !important; + position: relative; +} + +html,body { + width: 100%; + height: 100%; +} + +[tabindex="-1"]:focus { + outline: 0 !important; +} + +hr { + box-sizing: content-box; + height: 0; + overflow: visible; +} + +h1, h2, h3, h4, h5, h6 { + margin-top: 0; + margin-bottom: 0.5rem; +} + +h1 { + font-size: 2.5rem; + font-weight: 200; + margin-bottom: 1.875rem; +} + +h2 { + font-size: 1.625rem; + font-weight: 300; + margin-bottom: 1.3rem; +} + +h3 { + font-size: 1.3rem; + font-weight: 300; + margin-top: 1.3rem; +} + +h4 { + font-size: 1.125rem; + font-weight: 400; + margin-top: 1.875rem; + margin-bottom: 1.3rem; +} + +h5 { + font-size: 1rem; + font-weight: bold; + margin-top: 1.875rem; + margin-bottom: 1.3rem; +} + +p { + margin-top: 0; + margin-bottom: 1rem; +} + +abbr[title], abbr[data-original-title] { + text-decoration: underline; + -webkit-text-decoration: underline dotted; + text-decoration: underline dotted; + cursor: help; + border-bottom: 0; +} + +address { + margin-bottom: 1rem; + font-style: normal; + line-height: inherit; +} + +ol, ul, dl { + margin-top: 0; + margin-bottom: 1rem; +} + +ol ol, ul ul, ol ul, ul ol { + margin-bottom: 0; +} + +dt { + font-weight: 700; +} + +dd { + margin-bottom: .5rem; + margin-left: 0; +} + +blockquote { + margin: 0 0 1rem; +} + +dfn { + font-style: italic; +} + +b, strong { + font-weight: bold; +} + +small { + font-size: 80%; +} + +sub, sup { + position: relative; + font-size: 75%; + line-height: 0; + vertical-align: baseline; +} + +sub { + bottom: -.25em; +} + +sup { + top: -.5em; +} + +a { + color: #007bff; + text-decoration: none; + background-color: transparent; + -webkit-text-decoration-skip: objects; +} + +a:hover { + color: #0056b3; + text-decoration: underline; +} + +a:not([href]):not([tabindex]) { + color: inherit; + text-decoration: none; +} + +a:not([href]):not([tabindex]):hover, a:not([href]):not([tabindex]):focus { + color: inherit; + text-decoration: none; +} + +a:not([href]):not([tabindex]):focus { + outline: 0; +} + +pre, code, kbd, samp { + font-family: monospace, monospace; + font-size: 90%; + padding: 2px 4px 2px 4px; + color: #c7254e; + background-color: #f9f2f4; + border-radius: 4px; +} + +pre { + margin-top: 0; + margin-bottom: 1rem; + overflow: auto; + -ms-overflow-style: scrollbar; +} + +figure { + margin: 0 0 1rem; +} + +img { + vertical-align: middle; + border-style: none; +} + +svg:not(:root) { + overflow: hidden; +} + +table { + border-collapse: collapse; + background-color: transparent; +} + +caption { + padding-top: 8px; + padding-bottom: 8px; + color: #a7a7a7; + text-align: left; +} + +th { + text-align: left; +} + +output { + display: inline-block; +} + +summary { + display: list-item; + cursor: pointer; +} + +template { + display: none; +} + +table col[class*="col-"] { + position: static; + float: none; + display: table-column; +} + +table td[class*="col-"],table th[class*="col-"] { + position: static; + float: none; + display: table-cell; +} + +ol.glossary { + counter-reset: glossary-counter; + list-style: none; + padding-left: 40px; +} + +ol.glossary li { + counter-increment: glossary-counter; + position: relative; +} + +ol.glossary li::before { + content: counter(glossary-counter); + position: absolute; + background-color: #73EAA9; + color: #fff; + border-radius: 100px; + width: 24px; + left: -40px; + text-align: center; + font-weight: bold; + line-height: 24px; +} + +.layout-wrapper { + position: relative; + width: 100%; + height: auto; + min-height: 100%; +} + +.layout-title { + padding: 1.875rem 0; + border-bottom: 1px solid #f0f0f0; +} + +.layout-title h1 { + font-size: 3rem; + font-weight: 200; + text-align: center; + margin: 0; +} + +.layout-title h2 { + font-size: 2rem; + font-weight: 200; + text-align: center; + color: #999; + margin-bottom: 0; +} + +.layout-navigation .nav { + padding: 1.875rem 0; + border-bottom: 1px solid #f0f0f0; + text-align: center; + background: #fff; + z-index: 1000; +} + +.layout-navigation .nav > li { + display: inline-block; +} + +.layout-navigation .nav > li > a { + border: 1px solid #007bff; + border-radius: 100px; + padding: 6px 12px; + margin: 0 8px; +} + +.layout-navigation .nav > li > a:hover, .layout-navigation .nav > li > a:active, .layout-navigation .nav > li > a:focus { + border: 1px solid #0056b3; + color: #0056b3; + text-decoration: none; +} + +.layout-content { + position: relative; +} + +.layout-content:before, .layout-content:after { + content: " "; + display: table; +} + +.layout-content:after { + clear: both; +} + +.layout-content .col-right { + width: 25%; + float: right; +} + +.layout-content .col-right-wrapper { + width: 100%; + position: relative; + overflow-x: hidden; + overflow-y: auto; + padding: 2.5rem 2rem 0; +} + +.layout-content .col-body { + width: 75%; + float: left; +} + +.layout-content .col-body:before, .layout-content .col-body:after { + content: " "; + display: table; +} + +.layout-content .col-body:after { + clear: both; +} + +.layout-content .col-body-wrapper { + position: relative; + width: 100%; + padding: 2.5rem 2rem 0; +} + +.nav { + padding-left: 0; + margin-bottom: 0; + list-style: none; + line-height: 2; +} + +.table-of-contents { + padding: 1.25rem 0; +} + +.table-of-contents .nav > li > a { + display: flex; +} + +.table-of-contents .nav > li > a .item-number { + display: none; +} + +.table-of-contents .nav > li > a .item-title { + color: #212529; + overflow: hidden; + text-overflow: ellipsis; + flex-grow: 1; + white-space: nowrap; +} + +.table-of-contents .nav > li > a .item-title:hover { + color: #0056b3; +} + +.table-of-contents .nav > li.extended > a .item-title, .table-of-contents .nav > li.active > a .item-title, .table-of-contents .nav > li.extended > a .item-title:hover, .table-of-contents .nav > li.active > a .item-title:hover { + color: #007bff; +} + +.table-of-contents > .nav > li > .nav { + display: none; + margin-bottom: 0.5rem; +} + +.table-of-contents > .nav > li > .nav > li > a { + padding-left: 1rem; +} + +.table-of-contents > .nav > li > .nav > li > a .item-title { + font-size: 0.875rem; +} + +.table-of-contents > .nav > li > .nav > li > .nav > li > a { + padding-left: 2rem; +} + +.table-of-contents > .nav > li > .nav > li > .nav > li > a .item-title { + font-size: 0.75rem; +} + +.table-of-contents > .nav > li.active > .nav { + display: block; +} + +.chapter { + margin: 0 0 6rem; + font-weight: 300; +} + +.section { + margin-top: 3rem; +} + +.chapter > .chapter-title h1, .chapter > .chapter-title h2, .chapter > .chapter-title h3, .chapter > .chapter-title h4, .chapter > .chapter-title h5, .chapter > .chapter-title h6, .section > .section-title h1, .section > .section-title h2, .section > .section-title h3, .section > .section-title h4, .section > .section-title h5, .section > .section-title h6 { + margin-top: 0; +} + +.chapter > .chapter-title h1 { + border-bottom: 2px solid #eeeeee; + margin-bottom: 1.5rem; + padding-bottom: 0.2em; +} + +.chapter-title .title-number, .section-title .title-number { + display: none; +} + +.paragraph { + line-height: 1.75em; +} + +.paragraph + .paragraph { + margin-top: 1em; +} + +.dlist { + margin-top: 30px; +} + +.dlist dl dt { + float: left; + width: 160px; + clear: left; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.dlist dl dd { + margin-left: 180px; +} + +.ulist { + margin-top: 30px; +} + +.imageblock { + margin: 30px auto; +} + +.imageblock .content img { + max-width: 100%; +} + +.imageblock .title { + padding: 10px 0 0; +} + +.exampleblock, .quoteblock, .literalblock { + background: #f5f4f4; + padding: 20px; + margin: 30px 0; +} + +.exampleblock .title, .quoteblock .title, .literalblock .title { + text-transform: uppercase; + font-size: 0.75em; + font-weight: 400; + color: #979797; + margin-bottom: 10px; +} + +.quoteblock blockquote { + margin: 0; + padding: 0; + border: 0; + font-size: inherit; +} + +.quoteblock blockquote p:last-child, .quoteblock blockquote ul:last-child, .quoteblock blockquote ol:last-child { + margin-bottom: 9px; +} + +.literalblock pre { + border: 0; + padding: 0; + margin: 0; +} + +.listingblock { + margin: 30px 0; +} + +.listingblock pre { + border: 0; + padding: 0; + margin: 0; +} + +.listingblock pre code { + display: block; + padding: 20px; +} + +.admonitionblock { + line-height: 1.8em; + padding: 20px; + margin: 30px 0; +} + +.admonitionblock .icon { + display: none; +} + +.admonitionblock.important { + background: #fce1e1; + border-left: 5px solid #ff6060; +} + +.admonitionblock.note, .admonitionblock.tip { + background: #e0f2fc; + border-left: 5px solid #88d5ff; +} + +.admonitionblock.caution, .admonitionblock.warning { + background: #fdf3d8; + border-left: 5px solid #f1c654; +} + +table.tableblock { + background-color: #fff; + width: 100%; + max-width: 100%; + margin-bottom: 18px; + margin: 30px 0; +} + +table.tableblock > thead > tr > th, table.tableblock > tbody > tr > th, table.tableblock > tfoot > tr > th, table.tableblock > thead > tr > td, table.tableblock > tbody > tr > td, table.tableblock > tfoot > tr > td { + padding: 8px; + line-height: 1.42857143; + vertical-align: top; + border-top: 1px solid #eee; +} + +table.tableblock > thead > tr > th { + vertical-align: bottom; + border-bottom: 2px solid #eee; +} + +table.tableblock > caption + thead > tr:first-child > th, table.tableblock > colgroup + thead > tr:first-child > th, table.tableblock > thead:first-child > tr:first-child > th, table.tableblock > caption + thead > tr:first-child > td, table.tableblock > colgroup + thead > tr:first-child > td, table.tableblock > thead:first-child > tr:first-child > td { + border-top: 0; +} + +table.tableblock > tbody + tbody { + border-top: 2px solid #eee; +} + +table.tableblock .table { + background-color: #fff; +} + +table.tableblock > tbody > tr:nth-of-type(odd) { + background-color: #f7f7f7; +} + +table.tableblock > thead > tr > th p:last-child, table.tableblock > tbody > tr > th p:last-child, table.tableblock > tfoot > tr > th p:last-child, table.tableblock > thead > tr > td p:last-child, table.tableblock > tbody > tr > td p:last-child, table.tableblock > tfoot > tr > td p:last-child { + margin-bottom: 0; +} + +.loaded .table-of-contents .nav .nav { + display: none; +} + +@media (min-width: 1200px) { + .layout-wrapper .layout-title, .layout-wrapper .layout-navigation, .layout-wrapper .layout-content { + max-width: 1200px; + margin-left: auto; + margin-right: auto; + } +} + +@media (max-width: 991px) { + html { + font-size: 90%; + } + + .layout-content .col-right { + display: none; + } + + .layout-content .col-body { + width: 100%; + } +} + +@media print { + body { + color: #000; + font-family: Georgia, "Times New Roman", Times, serif; + } + + a { + color: #000; + } + + h1 { + font-size: 1.6rem; + } + + h2 { + font-size: 1.4rem; + } + + h3 { + font-size: 1.2rem; + } + + h4 { + font-size: 1rem; + } + + h5 { + font-size: 0.9rem; + } + + .layout-title h1 { + font-size: 2rem; + } + + .layout-content .col-right { + display: none; + } + + .layout-content .col-body { + width: 100%; + } + + .chapter { + margin-bottom: 3rem; + } + + .section { + margin-top: 2rem; + } +} diff --git a/docs/en/assets/base.js b/docs/en/assets/base.js new file mode 100644 index 0000000..df5c13f --- /dev/null +++ b/docs/en/assets/base.js @@ -0,0 +1,14 @@ +(function($){ + + hljs.initHighlightingOnLoad(); + + $(document).ready(function(){ + $('.col-right-wrapper').stick_in_parent({ + parent: '.layout-content' + }); + $('body').scrollspy({ + target: '.table-of-contents' + }); + }); + +})(jQuery); diff --git a/docs/en/assets/highlight.js b/docs/en/assets/highlight.js new file mode 100644 index 0000000..9a30d5f --- /dev/null +++ b/docs/en/assets/highlight.js @@ -0,0 +1,6 @@ +/* + Highlight.js 10.0.3 (a4b1bd2d) + License: BSD-3-Clause + Copyright (c) 2006-2020, Ivan Sagalaev +*/ +var hljs=function(){"use strict";function e(n){Object.freeze(n);var t="function"==typeof n;return Object.getOwnPropertyNames(n).forEach((function(r){!n.hasOwnProperty(r)||null===n[r]||"object"!=typeof n[r]&&"function"!=typeof n[r]||t&&("caller"===r||"callee"===r||"arguments"===r)||Object.isFrozen(n[r])||e(n[r])})),n}function n(e){return e.replace(/&/g,"&").replace(//g,">")}function t(e){var n,t={},r=Array.prototype.slice.call(arguments,1);for(n in e)t[n]=e[n];return r.forEach((function(e){for(n in e)t[n]=e[n]})),t}function r(e){return e.nodeName.toLowerCase()}var a=Object.freeze({__proto__:null,escapeHTML:n,inherit:t,nodeStream:function(e){var n=[];return function e(t,a){for(var i=t.firstChild;i;i=i.nextSibling)3===i.nodeType?a+=i.nodeValue.length:1===i.nodeType&&(n.push({event:"start",offset:a,node:i}),a=e(i,a),r(i).match(/br|hr|img|input/)||n.push({event:"stop",offset:a,node:i}));return a}(e,0),n},mergeStreams:function(e,t,a){var i=0,s="",o=[];function l(){return e.length&&t.length?e[0].offset!==t[0].offset?e[0].offset"}function u(e){s+=""}function d(e){("start"===e.event?c:u)(e.node)}for(;e.length||t.length;){var g=l();if(s+=n(a.substring(i,g[0].offset)),i=g[0].offset,g===e){o.reverse().forEach(u);do{d(g.splice(0,1)[0]),g=l()}while(g===e&&g.length&&g[0].offset===i);o.reverse().forEach(c)}else"start"===g[0].event?o.push(g[0].node):o.pop(),d(g.splice(0,1)[0])}return s+n(a.substr(i))}});const i="
",s=e=>!!e.kind;class o{constructor(e,n){this.buffer="",this.classPrefix=n.classPrefix,e.walk(this)}addText(e){this.buffer+=n(e)}openNode(e){if(!s(e))return;let n=e.kind;e.sublanguage||(n=`${this.classPrefix}${n}`),this.span(n)}closeNode(e){s(e)&&(this.buffer+=i)}span(e){this.buffer+=``}value(){return this.buffer}}class l{constructor(){this.rootNode={children:[]},this.stack=[this.rootNode]}get top(){return this.stack[this.stack.length-1]}get root(){return this.rootNode}add(e){this.top.children.push(e)}openNode(e){let n={kind:e,children:[]};this.add(n),this.stack.push(n)}closeNode(){if(this.stack.length>1)return this.stack.pop()}closeAllNodes(){for(;this.closeNode(););}toJSON(){return JSON.stringify(this.rootNode,null,4)}walk(e){return this.constructor._walk(e,this.rootNode)}static _walk(e,n){return"string"==typeof n?e.addText(n):n.children&&(e.openNode(n),n.children.forEach(n=>this._walk(e,n)),e.closeNode(n)),e}static _collapse(e){e.children&&(e.children.every(e=>"string"==typeof e)?(e.text=e.children.join(""),delete e.children):e.children.forEach(e=>{"string"!=typeof e&&l._collapse(e)}))}}class c extends l{constructor(e){super(),this.options=e}addKeyword(e,n){""!==e&&(this.openNode(n),this.addText(e),this.closeNode())}addText(e){""!==e&&this.add(e)}addSublanguage(e,n){let t=e.root;t.kind=n,t.sublanguage=!0,this.add(t)}toHTML(){return new o(this,this.options).value()}finalize(){}}function u(e){return e&&e.source||e}const d="(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)",g={begin:"\\\\[\\s\\S]",relevance:0},h={className:"string",begin:"'",end:"'",illegal:"\\n",contains:[g]},f={className:"string",begin:'"',end:'"',illegal:"\\n",contains:[g]},p={begin:/\b(a|an|the|are|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|they|like|more)\b/},m=function(e,n,r){var a=t({className:"comment",begin:e,end:n,contains:[]},r||{});return a.contains.push(p),a.contains.push({className:"doctag",begin:"(?:TODO|FIXME|NOTE|BUG|XXX):",relevance:0}),a},b=m("//","$"),v=m("/\\*","\\*/"),x=m("#","$");var _=Object.freeze({__proto__:null,IDENT_RE:"[a-zA-Z]\\w*",UNDERSCORE_IDENT_RE:"[a-zA-Z_]\\w*",NUMBER_RE:"\\b\\d+(\\.\\d+)?",C_NUMBER_RE:d,BINARY_NUMBER_RE:"\\b(0b[01]+)",RE_STARTERS_RE:"!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~",BACKSLASH_ESCAPE:g,APOS_STRING_MODE:h,QUOTE_STRING_MODE:f,PHRASAL_WORDS_MODE:p,COMMENT:m,C_LINE_COMMENT_MODE:b,C_BLOCK_COMMENT_MODE:v,HASH_COMMENT_MODE:x,NUMBER_MODE:{className:"number",begin:"\\b\\d+(\\.\\d+)?",relevance:0},C_NUMBER_MODE:{className:"number",begin:d,relevance:0},BINARY_NUMBER_MODE:{className:"number",begin:"\\b(0b[01]+)",relevance:0},CSS_NUMBER_MODE:{className:"number",begin:"\\b\\d+(\\.\\d+)?(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?",relevance:0},REGEXP_MODE:{begin:/(?=\/[^\/\n]*\/)/,contains:[{className:"regexp",begin:/\//,end:/\/[gimuy]*/,illegal:/\n/,contains:[g,{begin:/\[/,end:/\]/,relevance:0,contains:[g]}]}]},TITLE_MODE:{className:"title",begin:"[a-zA-Z]\\w*",relevance:0},UNDERSCORE_TITLE_MODE:{className:"title",begin:"[a-zA-Z_]\\w*",relevance:0},METHOD_GUARD:{begin:"\\.\\s*[a-zA-Z_]\\w*",relevance:0}}),E="of and for in not or if then".split(" ");function R(e,n){return n?+n:(t=e,E.includes(t.toLowerCase())?0:1);var t}const N=n,w=t,{nodeStream:y,mergeStreams:O}=a;return function(n){var r=[],a={},i={},s=[],o=!0,l=/((^(<[^>]+>|\t|)+|(?:\n)))/gm,d="Could not find the language '{}', did you forget to load/include a language module?",g={noHighlightRe:/^(no-?highlight)$/i,languageDetectRe:/\blang(?:uage)?-([\w-]+)\b/i,classPrefix:"hljs-",tabReplace:null,useBR:!1,languages:void 0,__emitter:c};function h(e){return g.noHighlightRe.test(e)}function f(e,n,t,r){var a={code:n,language:e};T("before:highlight",a);var i=a.result?a.result:p(a.language,a.code,t,r);return i.code=a.code,T("after:highlight",i),i}function p(e,n,r,i){var s=n;function l(e,n){var t=v.case_insensitive?n[0].toLowerCase():n[0];return e.keywords.hasOwnProperty(t)&&e.keywords[t]}function c(){null!=_.subLanguage?function(){if(""!==k){var e="string"==typeof _.subLanguage;if(!e||a[_.subLanguage]){var n=e?p(_.subLanguage,k,!0,E[_.subLanguage]):m(k,_.subLanguage.length?_.subLanguage:void 0);_.relevance>0&&(T+=n.relevance),e&&(E[_.subLanguage]=n.top),w.addSublanguage(n.emitter,n.language)}else w.addText(k)}}():function(){var e,n,t,r;if(_.keywords){for(n=0,_.lexemesRe.lastIndex=0,t=_.lexemesRe.exec(k),r="";t;){r+=k.substring(n,t.index);var a=null;(e=l(_,t))?(w.addText(r),r="",T+=e[1],a=e[0],w.addKeyword(t[0],a)):r+=t[0],n=_.lexemesRe.lastIndex,t=_.lexemesRe.exec(k)}r+=k.substr(n),w.addText(r)}else w.addText(k)}(),k=""}function h(e){e.className&&w.openNode(e.className),_=Object.create(e,{parent:{value:_}})}var f={};function b(n,t){var a,i=t&&t[0];if(k+=n,null==i)return c(),0;if("begin"==f.type&&"end"==t.type&&f.index==t.index&&""===i){if(k+=s.slice(t.index,t.index+1),!o)throw(a=Error("0 width match regex")).languageName=e,a.badRule=f.rule,a;return 1}if(f=t,"begin"===t.type)return function(e){var n=e[0],t=e.rule;return t.__onBegin&&(t.__onBegin(e)||{}).ignoreMatch?function(e){return 0===_.matcher.regexIndex?(k+=e[0],1):(B=!0,0)}(n):(t&&t.endSameAsBegin&&(t.endRe=RegExp(n.replace(/[-\/\\^$*+?.()|[\]{}]/g,"\\$&"),"m")),t.skip?k+=n:(t.excludeBegin&&(k+=n),c(),t.returnBegin||t.excludeBegin||(k=n)),h(t),t.returnBegin?0:n.length)}(t);if("illegal"===t.type&&!r)throw(a=Error('Illegal lexeme "'+i+'" for mode "'+(_.className||"")+'"')).mode=_,a;if("end"===t.type){var l=function(e){var n=e[0],t=s.substr(e.index),r=function e(n,t){if(function(e,n){var t=e&&e.exec(n);return t&&0===t.index}(n.endRe,t)){for(;n.endsParent&&n.parent;)n=n.parent;return n}if(n.endsWithParent)return e(n.parent,t)}(_,t);if(r){var a=_;a.skip?k+=n:(a.returnEnd||a.excludeEnd||(k+=n),c(),a.excludeEnd&&(k=n));do{_.className&&w.closeNode(),_.skip||_.subLanguage||(T+=_.relevance),_=_.parent}while(_!==r.parent);return r.starts&&(r.endSameAsBegin&&(r.starts.endRe=r.endRe),h(r.starts)),a.returnEnd?0:n.length}}(t);if(null!=l)return l}if("illegal"===t.type&&""===i)return 1;if(A>1e5&&A>3*t.index)throw Error("potential infinite loop, way more iterations than matches");return k+=i,i.length}var v=M(e);if(!v)throw console.error(d.replace("{}",e)),Error('Unknown language: "'+e+'"');!function(e){function n(n,t){return RegExp(u(n),"m"+(e.case_insensitive?"i":"")+(t?"g":""))}class r{constructor(){this.matchIndexes={},this.regexes=[],this.matchAt=1,this.position=0}addRule(e,n){n.position=this.position++,this.matchIndexes[this.matchAt]=n,this.regexes.push([n,e]),this.matchAt+=function(e){return RegExp(e.toString()+"|").exec("").length-1}(e)+1}compile(){0===this.regexes.length&&(this.exec=()=>null);let e=this.regexes.map(e=>e[1]);this.matcherRe=n(function(e,n){for(var t=/\[(?:[^\\\]]|\\.)*\]|\(\??|\\([1-9][0-9]*)|\\./,r=0,a="",i=0;i0&&(a+="|"),a+="(";o.length>0;){var l=t.exec(o);if(null==l){a+=o;break}a+=o.substring(0,l.index),o=o.substring(l.index+l[0].length),"\\"==l[0][0]&&l[1]?a+="\\"+(+l[1]+s):(a+=l[0],"("==l[0]&&r++)}a+=")"}return a}(e),!0),this.lastIndex=0}exec(e){this.matcherRe.lastIndex=this.lastIndex;let n=this.matcherRe.exec(e);if(!n)return null;let t=n.findIndex((e,n)=>n>0&&null!=e),r=this.matchIndexes[t];return Object.assign(n,r)}}class a{constructor(){this.rules=[],this.multiRegexes=[],this.count=0,this.lastIndex=0,this.regexIndex=0}getMatcher(e){if(this.multiRegexes[e])return this.multiRegexes[e];let n=new r;return this.rules.slice(e).forEach(([e,t])=>n.addRule(e,t)),n.compile(),this.multiRegexes[e]=n,n}considerAll(){this.regexIndex=0}addRule(e,n){this.rules.push([e,n]),"begin"===n.type&&this.count++}exec(e){let n=this.getMatcher(this.regexIndex);n.lastIndex=this.lastIndex;let t=n.exec(e);return t&&(this.regexIndex+=t.position+1,this.regexIndex===this.count&&(this.regexIndex=0)),t}}function i(e){let n=e.input[e.index-1],t=e.input[e.index+e[0].length];if("."===n||"."===t)return{ignoreMatch:!0}}if(e.contains&&e.contains.includes("self"))throw Error("ERR: contains `self` is not supported at the top-level of a language. See documentation.");!function r(s,o){s.compiled||(s.compiled=!0,s.__onBegin=null,s.keywords=s.keywords||s.beginKeywords,s.keywords&&(s.keywords=function(e,n){var t={};return"string"==typeof e?r("keyword",e):Object.keys(e).forEach((function(n){r(n,e[n])})),t;function r(e,r){n&&(r=r.toLowerCase()),r.split(" ").forEach((function(n){var r=n.split("|");t[r[0]]=[e,R(r[0],r[1])]}))}}(s.keywords,e.case_insensitive)),s.lexemesRe=n(s.lexemes||/\w+/,!0),o&&(s.beginKeywords&&(s.begin="\\b("+s.beginKeywords.split(" ").join("|")+")(?=\\b|\\s)",s.__onBegin=i),s.begin||(s.begin=/\B|\b/),s.beginRe=n(s.begin),s.endSameAsBegin&&(s.end=s.begin),s.end||s.endsWithParent||(s.end=/\B|\b/),s.end&&(s.endRe=n(s.end)),s.terminator_end=u(s.end)||"",s.endsWithParent&&o.terminator_end&&(s.terminator_end+=(s.end?"|":"")+o.terminator_end)),s.illegal&&(s.illegalRe=n(s.illegal)),null==s.relevance&&(s.relevance=1),s.contains||(s.contains=[]),s.contains=[].concat(...s.contains.map((function(e){return function(e){return e.variants&&!e.cached_variants&&(e.cached_variants=e.variants.map((function(n){return t(e,{variants:null},n)}))),e.cached_variants?e.cached_variants:function e(n){return!!n&&(n.endsWithParent||e(n.starts))}(e)?t(e,{starts:e.starts?t(e.starts):null}):Object.isFrozen(e)?t(e):e}("self"===e?s:e)}))),s.contains.forEach((function(e){r(e,s)})),s.starts&&r(s.starts,o),s.matcher=function(e){let n=new a;return e.contains.forEach(e=>n.addRule(e.begin,{rule:e,type:"begin"})),e.terminator_end&&n.addRule(e.terminator_end,{type:"end"}),e.illegal&&n.addRule(e.illegal,{type:"illegal"}),n}(s))}(e)}(v);var x,_=i||v,E={},w=new g.__emitter(g);!function(){for(var e=[],n=_;n!==v;n=n.parent)n.className&&e.unshift(n.className);e.forEach(e=>w.openNode(e))}();var y,O,k="",T=0,L=0,A=0,B=!1;try{for(_.matcher.considerAll();A++,B?B=!1:(_.matcher.lastIndex=L,_.matcher.considerAll()),y=_.matcher.exec(s);)O=b(s.substring(L,y.index),y),L=y.index+O;return b(s.substr(L)),w.closeAllNodes(),w.finalize(),x=w.toHTML(),{relevance:T,value:x,language:e,illegal:!1,emitter:w,top:_}}catch(n){if(n.message&&n.message.includes("Illegal"))return{illegal:!0,illegalBy:{msg:n.message,context:s.slice(L-100,L+100),mode:n.mode},sofar:x,relevance:0,value:N(s),emitter:w};if(o)return{relevance:0,value:N(s),emitter:w,language:e,top:_,errorRaised:n};throw n}}function m(e,n){n=n||g.languages||Object.keys(a);var t=function(e){const n={relevance:0,emitter:new g.__emitter(g),value:N(e),illegal:!1,top:E};return n.emitter.addText(e),n}(e),r=t;return n.filter(M).filter(k).forEach((function(n){var a=p(n,e,!1);a.language=n,a.relevance>r.relevance&&(r=a),a.relevance>t.relevance&&(r=t,t=a)})),r.language&&(t.second_best=r),t}function b(e){return g.tabReplace||g.useBR?e.replace(l,(function(e,n){return g.useBR&&"\n"===e?"
":g.tabReplace?n.replace(/\t/g,g.tabReplace):""})):e}function v(e){var n,t,r,a,s,o=function(e){var n,t=e.className+" ";if(t+=e.parentNode?e.parentNode.className:"",n=g.languageDetectRe.exec(t)){var r=M(n[1]);return r||(console.warn(d.replace("{}",n[1])),console.warn("Falling back to no-highlight mode for this block.",e)),r?n[1]:"no-highlight"}return t.split(/\s+/).find(e=>h(e)||M(e))}(e);h(o)||(T("before:highlightBlock",{block:e,language:o}),g.useBR?(n=document.createElement("div")).innerHTML=e.innerHTML.replace(/\n/g,"").replace(//g,"\n"):n=e,s=n.textContent,r=o?f(o,s,!0):m(s),(t=y(n)).length&&((a=document.createElement("div")).innerHTML=r.value,r.value=O(t,y(a),s)),r.value=b(r.value),T("after:highlightBlock",{block:e,result:r}),e.innerHTML=r.value,e.className=function(e,n,t){var r=n?i[n]:t,a=[e.trim()];return e.match(/\bhljs\b/)||a.push("hljs"),e.includes(r)||a.push(r),a.join(" ").trim()}(e.className,o,r.language),e.result={language:r.language,re:r.relevance},r.second_best&&(e.second_best={language:r.second_best.language,re:r.second_best.relevance}))}function x(){if(!x.called){x.called=!0;var e=document.querySelectorAll("pre code");r.forEach.call(e,v)}}const E={disableAutodetect:!0,name:"Plain text"};function M(e){return e=(e||"").toLowerCase(),a[e]||a[i[e]]}function k(e){var n=M(e);return n&&!n.disableAutodetect}function T(e,n){var t=e;s.forEach((function(e){e[t]&&e[t](n)}))}Object.assign(n,{highlight:f,highlightAuto:m,fixMarkup:b,highlightBlock:v,configure:function(e){g=w(g,e)},initHighlighting:x,initHighlightingOnLoad:function(){window.addEventListener("DOMContentLoaded",x,!1)},registerLanguage:function(e,t){var r;try{r=t(n)}catch(n){if(console.error("Language definition for '{}' could not be registered.".replace("{}",e)),!o)throw n;console.error(n),r=E}r.name||(r.name=e),a[e]=r,r.rawDefinition=t.bind(null,n),r.aliases&&r.aliases.forEach((function(n){i[n]=e}))},listLanguages:function(){return Object.keys(a)},getLanguage:M,requireLanguage:function(e){var n=M(e);if(n)return n;throw Error("The '{}' language is required, but not loaded.".replace("{}",e))},autoDetection:k,inherit:w,addPlugin:function(e,n){s.push(e)}}),n.debugMode=function(){o=!1},n.safeMode=function(){o=!0},n.versionString="10.0.3";for(const n in _)"object"==typeof _[n]&&e(_[n]);return Object.assign(n,_),n}({})}();"object"==typeof exports&&"undefined"!=typeof module&&(module.exports=hljs);hljs.registerLanguage("css",function(){"use strict";return function(e){var n={begin:/(?:[A-Z\_\.\-]+|--[a-zA-Z0-9_-]+)\s*:/,returnBegin:!0,end:";",endsWithParent:!0,contains:[{className:"attribute",begin:/\S/,end:":",excludeEnd:!0,starts:{endsWithParent:!0,excludeEnd:!0,contains:[{begin:/[\w-]+\(/,returnBegin:!0,contains:[{className:"built_in",begin:/[\w-]+/},{begin:/\(/,end:/\)/,contains:[e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,e.CSS_NUMBER_MODE]}]},e.CSS_NUMBER_MODE,e.QUOTE_STRING_MODE,e.APOS_STRING_MODE,e.C_BLOCK_COMMENT_MODE,{className:"number",begin:"#[0-9A-Fa-f]+"},{className:"meta",begin:"!important"}]}}]};return{name:"CSS",case_insensitive:!0,illegal:/[=\/|'\$]/,contains:[e.C_BLOCK_COMMENT_MODE,{className:"selector-id",begin:/#[A-Za-z0-9_-]+/},{className:"selector-class",begin:/\.[A-Za-z0-9_-]+/},{className:"selector-attr",begin:/\[/,end:/\]/,illegal:"$",contains:[e.APOS_STRING_MODE,e.QUOTE_STRING_MODE]},{className:"selector-pseudo",begin:/:(:)?[a-zA-Z0-9\_\-\+\(\)"'.]+/},{begin:"@(page|font-face)",lexemes:"@[a-z-]+",keywords:"@page @font-face"},{begin:"@",end:"[{;]",illegal:/:/,returnBegin:!0,contains:[{className:"keyword",begin:/@\-?\w[\w]*(\-\w+)*/},{begin:/\s/,endsWithParent:!0,excludeEnd:!0,relevance:0,keywords:"and or not only",contains:[{begin:/[a-z-]+:/,className:"attribute"},e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,e.CSS_NUMBER_MODE]}]},{className:"selector-tag",begin:"[a-zA-Z-][a-zA-Z0-9_-]*",relevance:0},{begin:"{",end:"}",illegal:/\S/,contains:[e.C_BLOCK_COMMENT_MODE,n]}]}}}());hljs.registerLanguage("bash",function(){"use strict";return function(e){const s={};Object.assign(s,{className:"variable",variants:[{begin:/\$[\w\d#@][\w\d_]*/},{begin:/\$\{/,end:/\}/,contains:[{begin:/:-/,contains:[s]}]}]});const n={className:"subst",begin:/\$\(/,end:/\)/,contains:[e.BACKSLASH_ESCAPE]},t={className:"string",begin:/"/,end:/"/,contains:[e.BACKSLASH_ESCAPE,s,n]};n.contains.push(t);const a={begin:/\$\(\(/,end:/\)\)/,contains:[{begin:/\d+#[0-9a-f]+/,className:"number"},e.NUMBER_MODE,s]};return{name:"Bash",aliases:["sh","zsh"],lexemes:/\b-?[a-z\._]+\b/,keywords:{keyword:"if then else elif fi for while in do done case esac function",literal:"true false",built_in:"break cd continue eval exec exit export getopts hash pwd readonly return shift test times trap umask unset alias bind builtin caller command declare echo enable help let local logout mapfile printf read readarray source type typeset ulimit unalias set shopt autoload bg bindkey bye cap chdir clone comparguments compcall compctl compdescribe compfiles compgroups compquote comptags comptry compvalues dirs disable disown echotc echoti emulate fc fg float functions getcap getln history integer jobs kill limit log noglob popd print pushd pushln rehash sched setcap setopt stat suspend ttyctl unfunction unhash unlimit unsetopt vared wait whence where which zcompile zformat zftp zle zmodload zparseopts zprof zpty zregexparse zsocket zstyle ztcp",_:"-ne -eq -lt -gt -f -d -e -s -l -a"},contains:[{className:"meta",begin:/^#![^\n]+sh\s*$/,relevance:10},{className:"function",begin:/\w[\w\d_]*\s*\(\s*\)\s*\{/,returnBegin:!0,contains:[e.inherit(e.TITLE_MODE,{begin:/\w[\w\d_]*/})],relevance:0},a,e.HASH_COMMENT_MODE,t,{className:"",begin:/\\"/},{className:"string",begin:/'/,end:/'/},s]}}}());hljs.registerLanguage("xml",function(){"use strict";return function(e){var n={className:"symbol",begin:"&[a-z]+;|&#[0-9]+;|&#x[a-f0-9]+;"},a={begin:"\\s",contains:[{className:"meta-keyword",begin:"#?[a-z_][a-z1-9_-]+",illegal:"\\n"}]},s=e.inherit(a,{begin:"\\(",end:"\\)"}),t=e.inherit(e.APOS_STRING_MODE,{className:"meta-string"}),i=e.inherit(e.QUOTE_STRING_MODE,{className:"meta-string"}),c={endsWithParent:!0,illegal:/`]+/}]}]}]};return{name:"HTML, XML",aliases:["html","xhtml","rss","atom","xjb","xsd","xsl","plist","wsf","svg"],case_insensitive:!0,contains:[{className:"meta",begin:"",relevance:10,contains:[a,i,t,s,{begin:"\\[",end:"\\]",contains:[{className:"meta",begin:"",contains:[a,s,i,t]}]}]},e.COMMENT("\x3c!--","--\x3e",{relevance:10}),{begin:"<\\!\\[CDATA\\[",end:"\\]\\]>",relevance:10},n,{className:"meta",begin:/<\?xml/,end:/\?>/,relevance:10},{className:"tag",begin:")",end:">",keywords:{name:"style"},contains:[c],starts:{end:"",returnEnd:!0,subLanguage:["css","xml"]}},{className:"tag",begin:")",end:">",keywords:{name:"script"},contains:[c],starts:{end:"<\/script>",returnEnd:!0,subLanguage:["javascript","handlebars","xml"]}},{className:"tag",begin:"",contains:[{className:"name",begin:/[^\/><\s]+/,relevance:0},c]}]}}}());hljs.registerLanguage("php",function(){"use strict";return function(e){var r={begin:"\\$+[a-zA-Z_-ÿ][a-zA-Z0-9_-ÿ]*"},t={className:"meta",variants:[{begin:/<\?php/,relevance:10},{begin:/<\?[=]?/},{begin:/\?>/}]},a={className:"string",contains:[e.BACKSLASH_ESCAPE,t],variants:[{begin:'b"',end:'"'},{begin:"b'",end:"'"},e.inherit(e.APOS_STRING_MODE,{illegal:null}),e.inherit(e.QUOTE_STRING_MODE,{illegal:null})]},n={variants:[e.BINARY_NUMBER_MODE,e.C_NUMBER_MODE]},i={keyword:"__CLASS__ __DIR__ __FILE__ __FUNCTION__ __LINE__ __METHOD__ __NAMESPACE__ __TRAIT__ die echo exit include include_once print require require_once array abstract and as binary bool boolean break callable case catch class clone const continue declare default do double else elseif empty enddeclare endfor endforeach endif endswitch endwhile eval extends final finally float for foreach from global goto if implements instanceof insteadof int integer interface isset iterable list new object or private protected public real return string switch throw trait try unset use var void while xor yield",literal:"false null true",built_in:"Error|0 AppendIterator ArgumentCountError ArithmeticError ArrayIterator ArrayObject AssertionError BadFunctionCallException BadMethodCallException CachingIterator CallbackFilterIterator CompileError Countable DirectoryIterator DivisionByZeroError DomainException EmptyIterator ErrorException Exception FilesystemIterator FilterIterator GlobIterator InfiniteIterator InvalidArgumentException IteratorIterator LengthException LimitIterator LogicException MultipleIterator NoRewindIterator OutOfBoundsException OutOfRangeException OuterIterator OverflowException ParentIterator ParseError RangeException RecursiveArrayIterator RecursiveCachingIterator RecursiveCallbackFilterIterator RecursiveDirectoryIterator RecursiveFilterIterator RecursiveIterator RecursiveIteratorIterator RecursiveRegexIterator RecursiveTreeIterator RegexIterator RuntimeException SeekableIterator SplDoublyLinkedList SplFileInfo SplFileObject SplFixedArray SplHeap SplMaxHeap SplMinHeap SplObjectStorage SplObserver SplObserver SplPriorityQueue SplQueue SplStack SplSubject SplSubject SplTempFileObject TypeError UnderflowException UnexpectedValueException ArrayAccess Closure Generator Iterator IteratorAggregate Serializable Throwable Traversable WeakReference Directory __PHP_Incomplete_Class parent php_user_filter self static stdClass"};return{aliases:["php","php3","php4","php5","php6","php7"],case_insensitive:!0,keywords:i,contains:[e.HASH_COMMENT_MODE,e.COMMENT("//","$",{contains:[t]}),e.COMMENT("/\\*","\\*/",{contains:[{className:"doctag",begin:"@[A-Za-z]+"}]}),e.COMMENT("__halt_compiler.+?;",!1,{endsWithParent:!0,keywords:"__halt_compiler",lexemes:e.UNDERSCORE_IDENT_RE}),{className:"string",begin:/<<<['"]?\w+['"]?$/,end:/^\w+;?$/,contains:[e.BACKSLASH_ESCAPE,{className:"subst",variants:[{begin:/\$\w+/},{begin:/\{\$/,end:/\}/}]}]},t,{className:"keyword",begin:/\$this\b/},r,{begin:/(::|->)+[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/},{className:"function",beginKeywords:"fn function",end:/[;{]/,excludeEnd:!0,illegal:"[$%\\[]",contains:[e.UNDERSCORE_TITLE_MODE,{className:"params",begin:"\\(",end:"\\)",excludeBegin:!0,excludeEnd:!0,keywords:i,contains:["self",r,e.C_BLOCK_COMMENT_MODE,a,n]}]},{className:"class",beginKeywords:"class interface",end:"{",excludeEnd:!0,illegal:/[:\(\$"]/,contains:[{beginKeywords:"extends implements"},e.UNDERSCORE_TITLE_MODE]},{beginKeywords:"namespace",end:";",illegal:/[\.']/,contains:[e.UNDERSCORE_TITLE_MODE]},{beginKeywords:"use",end:";",contains:[e.UNDERSCORE_TITLE_MODE]},{begin:"=>"},a,n]}}}());hljs.registerLanguage("php-template",function(){"use strict";return function(n){return{name:"PHP template",subLanguage:"xml",contains:[{begin:/<\?(php|=)?/,end:/\?>/,subLanguage:"php",contains:[{begin:"/\\*",end:"\\*/",skip:!0},{begin:'b"',end:'"',skip:!0},{begin:"b'",end:"'",skip:!0},n.inherit(n.APOS_STRING_MODE,{illegal:null,className:null,contains:null,skip:!0}),n.inherit(n.QUOTE_STRING_MODE,{illegal:null,className:null,contains:null,skip:!0})]}]}}}());hljs.registerLanguage("sql",function(){"use strict";return function(e){var t=e.COMMENT("--","$");return{name:"SQL",case_insensitive:!0,illegal:/[<>{}*]/,contains:[{beginKeywords:"begin end start commit rollback savepoint lock alter create drop rename call delete do handler insert load replace select truncate update set show pragma grant merge describe use explain help declare prepare execute deallocate release unlock purge reset change stop analyze cache flush optimize repair kill install uninstall checksum restore check backup revoke comment values with",end:/;/,endsWithParent:!0,lexemes:/[\w\.]+/,keywords:{keyword:"as abort abs absolute acc acce accep accept access accessed accessible account acos action activate add addtime admin administer advanced advise aes_decrypt aes_encrypt after agent aggregate ali alia alias all allocate allow alter always analyze ancillary and anti any anydata anydataset anyschema anytype apply archive archived archivelog are as asc ascii asin assembly assertion associate asynchronous at atan atn2 attr attri attrib attribu attribut attribute attributes audit authenticated authentication authid authors auto autoallocate autodblink autoextend automatic availability avg backup badfile basicfile before begin beginning benchmark between bfile bfile_base big bigfile bin binary_double binary_float binlog bit_and bit_count bit_length bit_or bit_xor bitmap blob_base block blocksize body both bound bucket buffer_cache buffer_pool build bulk by byte byteordermark bytes cache caching call calling cancel capacity cascade cascaded case cast catalog category ceil ceiling chain change changed char_base char_length character_length characters characterset charindex charset charsetform charsetid check checksum checksum_agg child choose chr chunk class cleanup clear client clob clob_base clone close cluster_id cluster_probability cluster_set clustering coalesce coercibility col collate collation collect colu colum column column_value columns columns_updated comment commit compact compatibility compiled complete composite_limit compound compress compute concat concat_ws concurrent confirm conn connec connect connect_by_iscycle connect_by_isleaf connect_by_root connect_time connection consider consistent constant constraint constraints constructor container content contents context contributors controlfile conv convert convert_tz corr corr_k corr_s corresponding corruption cos cost count count_big counted covar_pop covar_samp cpu_per_call cpu_per_session crc32 create creation critical cross cube cume_dist curdate current current_date current_time current_timestamp current_user cursor curtime customdatum cycle data database databases datafile datafiles datalength date_add date_cache date_format date_sub dateadd datediff datefromparts datename datepart datetime2fromparts day day_to_second dayname dayofmonth dayofweek dayofyear days db_role_change dbtimezone ddl deallocate declare decode decompose decrement decrypt deduplicate def defa defau defaul default defaults deferred defi defin define degrees delayed delegate delete delete_all delimited demand dense_rank depth dequeue des_decrypt des_encrypt des_key_file desc descr descri describ describe descriptor deterministic diagnostics difference dimension direct_load directory disable disable_all disallow disassociate discardfile disconnect diskgroup distinct distinctrow distribute distributed div do document domain dotnet double downgrade drop dumpfile duplicate duration each edition editionable editions element ellipsis else elsif elt empty enable enable_all enclosed encode encoding encrypt end end-exec endian enforced engine engines enqueue enterprise entityescaping eomonth error errors escaped evalname evaluate event eventdata events except exception exceptions exchange exclude excluding execu execut execute exempt exists exit exp expire explain explode export export_set extended extent external external_1 external_2 externally extract failed failed_login_attempts failover failure far fast feature_set feature_value fetch field fields file file_name_convert filesystem_like_logging final finish first first_value fixed flash_cache flashback floor flush following follows for forall force foreign form forma format found found_rows freelist freelists freepools fresh from from_base64 from_days ftp full function general generated get get_format get_lock getdate getutcdate global global_name globally go goto grant grants greatest group group_concat group_id grouping grouping_id groups gtid_subtract guarantee guard handler hash hashkeys having hea head headi headin heading heap help hex hierarchy high high_priority hosts hour hours http id ident_current ident_incr ident_seed identified identity idle_time if ifnull ignore iif ilike ilm immediate import in include including increment index indexes indexing indextype indicator indices inet6_aton inet6_ntoa inet_aton inet_ntoa infile initial initialized initially initrans inmemory inner innodb input insert install instance instantiable instr interface interleaved intersect into invalidate invisible is is_free_lock is_ipv4 is_ipv4_compat is_not is_not_null is_used_lock isdate isnull isolation iterate java join json json_exists keep keep_duplicates key keys kill language large last last_day last_insert_id last_value lateral lax lcase lead leading least leaves left len lenght length less level levels library like like2 like4 likec limit lines link list listagg little ln load load_file lob lobs local localtime localtimestamp locate locator lock locked log log10 log2 logfile logfiles logging logical logical_reads_per_call logoff logon logs long loop low low_priority lower lpad lrtrim ltrim main make_set makedate maketime managed management manual map mapping mask master master_pos_wait match matched materialized max maxextents maximize maxinstances maxlen maxlogfiles maxloghistory maxlogmembers maxsize maxtrans md5 measures median medium member memcompress memory merge microsecond mid migration min minextents minimum mining minus minute minutes minvalue missing mod mode model modification modify module monitoring month months mount move movement multiset mutex name name_const names nan national native natural nav nchar nclob nested never new newline next nextval no no_write_to_binlog noarchivelog noaudit nobadfile nocheck nocompress nocopy nocycle nodelay nodiscardfile noentityescaping noguarantee nokeep nologfile nomapping nomaxvalue nominimize nominvalue nomonitoring none noneditionable nonschema noorder nopr nopro noprom nopromp noprompt norely noresetlogs noreverse normal norowdependencies noschemacheck noswitch not nothing notice notnull notrim novalidate now nowait nth_value nullif nulls num numb numbe nvarchar nvarchar2 object ocicoll ocidate ocidatetime ociduration ociinterval ociloblocator ocinumber ociref ocirefcursor ocirowid ocistring ocitype oct octet_length of off offline offset oid oidindex old on online only opaque open operations operator optimal optimize option optionally or oracle oracle_date oradata ord ordaudio orddicom orddoc order ordimage ordinality ordvideo organization orlany orlvary out outer outfile outline output over overflow overriding package pad parallel parallel_enable parameters parent parse partial partition partitions pascal passing password password_grace_time password_lock_time password_reuse_max password_reuse_time password_verify_function patch path patindex pctincrease pctthreshold pctused pctversion percent percent_rank percentile_cont percentile_disc performance period period_add period_diff permanent physical pi pipe pipelined pivot pluggable plugin policy position post_transaction pow power pragma prebuilt precedes preceding precision prediction prediction_cost prediction_details prediction_probability prediction_set prepare present preserve prior priority private private_sga privileges procedural procedure procedure_analyze processlist profiles project prompt protection public publishingservername purge quarter query quick quiesce quota quotename radians raise rand range rank raw read reads readsize rebuild record records recover recovery recursive recycle redo reduced ref reference referenced references referencing refresh regexp_like register regr_avgx regr_avgy regr_count regr_intercept regr_r2 regr_slope regr_sxx regr_sxy reject rekey relational relative relaylog release release_lock relies_on relocate rely rem remainder rename repair repeat replace replicate replication required reset resetlogs resize resource respect restore restricted result result_cache resumable resume retention return returning returns reuse reverse revoke right rlike role roles rollback rolling rollup round row row_count rowdependencies rowid rownum rows rtrim rules safe salt sample save savepoint sb1 sb2 sb4 scan schema schemacheck scn scope scroll sdo_georaster sdo_topo_geometry search sec_to_time second seconds section securefile security seed segment select self semi sequence sequential serializable server servererror session session_user sessions_per_user set sets settings sha sha1 sha2 share shared shared_pool short show shrink shutdown si_averagecolor si_colorhistogram si_featurelist si_positionalcolor si_stillimage si_texture siblings sid sign sin size size_t sizes skip slave sleep smalldatetimefromparts smallfile snapshot some soname sort soundex source space sparse spfile split sql sql_big_result sql_buffer_result sql_cache sql_calc_found_rows sql_small_result sql_variant_property sqlcode sqldata sqlerror sqlname sqlstate sqrt square standalone standby start starting startup statement static statistics stats_binomial_test stats_crosstab stats_ks_test stats_mode stats_mw_test stats_one_way_anova stats_t_test_ stats_t_test_indep stats_t_test_one stats_t_test_paired stats_wsr_test status std stddev stddev_pop stddev_samp stdev stop storage store stored str str_to_date straight_join strcmp strict string struct stuff style subdate subpartition subpartitions substitutable substr substring subtime subtring_index subtype success sum suspend switch switchoffset switchover sync synchronous synonym sys sys_xmlagg sysasm sysaux sysdate sysdatetimeoffset sysdba sysoper system system_user sysutcdatetime table tables tablespace tablesample tan tdo template temporary terminated tertiary_weights test than then thread through tier ties time time_format time_zone timediff timefromparts timeout timestamp timestampadd timestampdiff timezone_abbr timezone_minute timezone_region to to_base64 to_date to_days to_seconds todatetimeoffset trace tracking transaction transactional translate translation treat trigger trigger_nestlevel triggers trim truncate try_cast try_convert try_parse type ub1 ub2 ub4 ucase unarchived unbounded uncompress under undo unhex unicode uniform uninstall union unique unix_timestamp unknown unlimited unlock unnest unpivot unrecoverable unsafe unsigned until untrusted unusable unused update updated upgrade upped upper upsert url urowid usable usage use use_stored_outlines user user_data user_resources users using utc_date utc_timestamp uuid uuid_short validate validate_password_strength validation valist value values var var_samp varcharc vari varia variab variabl variable variables variance varp varraw varrawc varray verify version versions view virtual visible void wait wallet warning warnings week weekday weekofyear wellformed when whene whenev wheneve whenever where while whitespace window with within without work wrapped xdb xml xmlagg xmlattributes xmlcast xmlcolattval xmlelement xmlexists xmlforest xmlindex xmlnamespaces xmlpi xmlquery xmlroot xmlschema xmlserialize xmltable xmltype xor year year_to_month years yearweek",literal:"true false null unknown",built_in:"array bigint binary bit blob bool boolean char character date dec decimal float int int8 integer interval number numeric real record serial serial8 smallint text time timestamp tinyint varchar varchar2 varying void"},contains:[{className:"string",begin:"'",end:"'",contains:[{begin:"''"}]},{className:"string",begin:'"',end:'"',contains:[{begin:'""'}]},{className:"string",begin:"`",end:"`"},e.C_NUMBER_MODE,e.C_BLOCK_COMMENT_MODE,t,e.HASH_COMMENT_MODE]},e.C_BLOCK_COMMENT_MODE,t,e.HASH_COMMENT_MODE]}}}());hljs.registerLanguage("python",function(){"use strict";return function(e){var n={keyword:"and elif is global as in if from raise for except finally print import pass return exec else break not with class assert yield try while continue del or def lambda async await nonlocal|10",built_in:"Ellipsis NotImplemented",literal:"False None True"},a={className:"meta",begin:/^(>>>|\.\.\.) /},i={className:"subst",begin:/\{/,end:/\}/,keywords:n,illegal:/#/},s={begin:/\{\{/,relevance:0},r={className:"string",contains:[e.BACKSLASH_ESCAPE],variants:[{begin:/(u|b)?r?'''/,end:/'''/,contains:[e.BACKSLASH_ESCAPE,a],relevance:10},{begin:/(u|b)?r?"""/,end:/"""/,contains:[e.BACKSLASH_ESCAPE,a],relevance:10},{begin:/(fr|rf|f)'''/,end:/'''/,contains:[e.BACKSLASH_ESCAPE,a,s,i]},{begin:/(fr|rf|f)"""/,end:/"""/,contains:[e.BACKSLASH_ESCAPE,a,s,i]},{begin:/(u|r|ur)'/,end:/'/,relevance:10},{begin:/(u|r|ur)"/,end:/"/,relevance:10},{begin:/(b|br)'/,end:/'/},{begin:/(b|br)"/,end:/"/},{begin:/(fr|rf|f)'/,end:/'/,contains:[e.BACKSLASH_ESCAPE,s,i]},{begin:/(fr|rf|f)"/,end:/"/,contains:[e.BACKSLASH_ESCAPE,s,i]},e.APOS_STRING_MODE,e.QUOTE_STRING_MODE]},l={className:"number",relevance:0,variants:[{begin:e.BINARY_NUMBER_RE+"[lLjJ]?"},{begin:"\\b(0o[0-7]+)[lLjJ]?"},{begin:e.C_NUMBER_RE+"[lLjJ]?"}]},t={className:"params",variants:[{begin:/\(\s*\)/,skip:!0,className:null},{begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,contains:["self",a,l,r,e.HASH_COMMENT_MODE]}]};return i.contains=[r,l,a],{name:"Python",aliases:["py","gyp","ipython"],keywords:n,illegal:/(<\/|->|\?)|=>/,contains:[a,l,{beginKeywords:"if",relevance:0},r,e.HASH_COMMENT_MODE,{variants:[{className:"function",beginKeywords:"def"},{className:"class",beginKeywords:"class"}],end:/:/,illegal:/[${=;\n,]/,contains:[e.UNDERSCORE_TITLE_MODE,t,{begin:/->/,endsWithParent:!0,keywords:"None"}]},{className:"meta",begin:/^[\t ]*@/,end:/$/},{begin:/\b(print|exec)\(/}]}}}());hljs.registerLanguage("plaintext",function(){"use strict";return function(t){return{name:"Plain text",aliases:["text","txt"],disableAutodetect:!0}}}());hljs.registerLanguage("diff",function(){"use strict";return function(e){return{name:"Diff",aliases:["patch"],contains:[{className:"meta",relevance:10,variants:[{begin:/^@@ +\-\d+,\d+ +\+\d+,\d+ +@@$/},{begin:/^\*\*\* +\d+,\d+ +\*\*\*\*$/},{begin:/^\-\-\- +\d+,\d+ +\-\-\-\-$/}]},{className:"comment",variants:[{begin:/Index: /,end:/$/},{begin:/={3,}/,end:/$/},{begin:/^\-{3}/,end:/$/},{begin:/^\*{3} /,end:/$/},{begin:/^\+{3}/,end:/$/},{begin:/^\*{15}$/}]},{className:"addition",begin:"^\\+",end:"$"},{className:"deletion",begin:"^\\-",end:"$"},{className:"addition",begin:"^\\!",end:"$"}]}}}());hljs.registerLanguage("csharp",function(){"use strict";return function(e){var n={keyword:"abstract as base bool break byte case catch char checked const continue decimal default delegate do double enum event explicit extern finally fixed float for foreach goto if implicit in int interface internal is lock long object operator out override params private protected public readonly ref sbyte sealed short sizeof stackalloc static string struct switch this try typeof uint ulong unchecked unsafe ushort using virtual void volatile while add alias ascending async await by descending dynamic equals from get global group into join let nameof on orderby partial remove select set value var when where yield",literal:"null false true"},i=e.inherit(e.TITLE_MODE,{begin:"[a-zA-Z](\\.?\\w)*"}),a={className:"number",variants:[{begin:"\\b(0b[01']+)"},{begin:"(-?)\\b([\\d']+(\\.[\\d']*)?|\\.[\\d']+)(u|U|l|L|ul|UL|f|F|b|B)"},{begin:"(-?)(\\b0[xX][a-fA-F0-9']+|(\\b[\\d']+(\\.[\\d']*)?|\\.[\\d']+)([eE][-+]?[\\d']+)?)"}],relevance:0},s={className:"string",begin:'@"',end:'"',contains:[{begin:'""'}]},t=e.inherit(s,{illegal:/\n/}),l={className:"subst",begin:"{",end:"}",keywords:n},r=e.inherit(l,{illegal:/\n/}),c={className:"string",begin:/\$"/,end:'"',illegal:/\n/,contains:[{begin:"{{"},{begin:"}}"},e.BACKSLASH_ESCAPE,r]},o={className:"string",begin:/\$@"/,end:'"',contains:[{begin:"{{"},{begin:"}}"},{begin:'""'},l]},g=e.inherit(o,{illegal:/\n/,contains:[{begin:"{{"},{begin:"}}"},{begin:'""'},r]});l.contains=[o,c,s,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,a,e.C_BLOCK_COMMENT_MODE],r.contains=[g,c,t,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,a,e.inherit(e.C_BLOCK_COMMENT_MODE,{illegal:/\n/})];var d={variants:[o,c,s,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE]},E=e.IDENT_RE+"(<"+e.IDENT_RE+"(\\s*,\\s*"+e.IDENT_RE+")*>)?(\\[\\])?",_={begin:"@"+e.IDENT_RE,relevance:0};return{name:"C#",aliases:["cs","c#"],keywords:n,illegal:/::/,contains:[e.COMMENT("///","$",{returnBegin:!0,contains:[{className:"doctag",variants:[{begin:"///",relevance:0},{begin:"\x3c!--|--\x3e"},{begin:""}]}]}),e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,{className:"meta",begin:"#",end:"$",keywords:{"meta-keyword":"if else elif endif define undef warning error line region endregion pragma checksum"}},d,a,{beginKeywords:"class interface",end:/[{;=]/,illegal:/[^\s:,]/,contains:[{beginKeywords:"where class"},i,{begin:"<",end:">",keywords:"in out"},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},{beginKeywords:"namespace",end:/[{;=]/,illegal:/[^\s:]/,contains:[i,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},{className:"meta",begin:"^\\s*\\[",excludeBegin:!0,end:"\\]",excludeEnd:!0,contains:[{className:"meta-string",begin:/"/,end:/"/}]},{beginKeywords:"new return throw await else",relevance:0},{className:"function",begin:"("+E+"\\s+)+"+e.IDENT_RE+"\\s*\\(",returnBegin:!0,end:/\s*[{;=]/,excludeEnd:!0,keywords:n,contains:[{begin:e.IDENT_RE+"\\s*\\(",returnBegin:!0,contains:[e.TITLE_MODE],relevance:0},{className:"params",begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:n,relevance:0,contains:[d,a,e.C_BLOCK_COMMENT_MODE]},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},_]}}}());hljs.registerLanguage("ruby",function(){"use strict";return function(e){var n="[a-zA-Z_]\\w*[!?=]?|[-+~]\\@|<<|>>|=~|===?|<=>|[<>]=?|\\*\\*|[-/+%^&*~`|]|\\[\\]=?",a={keyword:"and then defined module in return redo if BEGIN retry end for self when next until do begin unless END rescue else break undef not super class case require yield alias while ensure elsif or include attr_reader attr_writer attr_accessor",literal:"true false nil"},s={className:"doctag",begin:"@[A-Za-z]+"},i={begin:"#<",end:">"},r=[e.COMMENT("#","$",{contains:[s]}),e.COMMENT("^\\=begin","^\\=end",{contains:[s],relevance:10}),e.COMMENT("^__END__","\\n$")],c={className:"subst",begin:"#\\{",end:"}",keywords:a},t={className:"string",contains:[e.BACKSLASH_ESCAPE,c],variants:[{begin:/'/,end:/'/},{begin:/"/,end:/"/},{begin:/`/,end:/`/},{begin:"%[qQwWx]?\\(",end:"\\)"},{begin:"%[qQwWx]?\\[",end:"\\]"},{begin:"%[qQwWx]?{",end:"}"},{begin:"%[qQwWx]?<",end:">"},{begin:"%[qQwWx]?/",end:"/"},{begin:"%[qQwWx]?%",end:"%"},{begin:"%[qQwWx]?-",end:"-"},{begin:"%[qQwWx]?\\|",end:"\\|"},{begin:/\B\?(\\\d{1,3}|\\x[A-Fa-f0-9]{1,2}|\\u[A-Fa-f0-9]{4}|\\?\S)\b/},{begin:/<<[-~]?'?(\w+)(?:.|\n)*?\n\s*\1\b/,returnBegin:!0,contains:[{begin:/<<[-~]?'?/},{begin:/\w+/,endSameAsBegin:!0,contains:[e.BACKSLASH_ESCAPE,c]}]}]},b={className:"params",begin:"\\(",end:"\\)",endsParent:!0,keywords:a},d=[t,i,{className:"class",beginKeywords:"class module",end:"$|;",illegal:/=/,contains:[e.inherit(e.TITLE_MODE,{begin:"[A-Za-z_]\\w*(::\\w+)*(\\?|\\!)?"}),{begin:"<\\s*",contains:[{begin:"("+e.IDENT_RE+"::)?"+e.IDENT_RE}]}].concat(r)},{className:"function",beginKeywords:"def",end:"$|;",contains:[e.inherit(e.TITLE_MODE,{begin:n}),b].concat(r)},{begin:e.IDENT_RE+"::"},{className:"symbol",begin:e.UNDERSCORE_IDENT_RE+"(\\!|\\?)?:",relevance:0},{className:"symbol",begin:":(?!\\s)",contains:[t,{begin:n}],relevance:0},{className:"number",begin:"(\\b0[0-7_]+)|(\\b0x[0-9a-fA-F_]+)|(\\b[1-9][0-9_]*(\\.[0-9_]+)?)|[0_]\\b",relevance:0},{begin:"(\\$\\W)|((\\$|\\@\\@?)(\\w+))"},{className:"params",begin:/\|/,end:/\|/,keywords:a},{begin:"("+e.RE_STARTERS_RE+"|unless)\\s*",keywords:"unless",contains:[i,{className:"regexp",contains:[e.BACKSLASH_ESCAPE,c],illegal:/\n/,variants:[{begin:"/",end:"/[a-z]*"},{begin:"%r{",end:"}[a-z]*"},{begin:"%r\\(",end:"\\)[a-z]*"},{begin:"%r!",end:"![a-z]*"},{begin:"%r\\[",end:"\\][a-z]*"}]}].concat(r),relevance:0}].concat(r);c.contains=d,b.contains=d;var g=[{begin:/^\s*=>/,starts:{end:"$",contains:d}},{className:"meta",begin:"^([>?]>|[\\w#]+\\(\\w+\\):\\d+:\\d+>|(\\w+-)?\\d+\\.\\d+\\.\\d(p\\d+)?[^>]+>)",starts:{end:"$",contains:d}}];return{name:"Ruby",aliases:["rb","gemspec","podspec","thor","irb"],keywords:a,illegal:/\/\*/,contains:r.concat(g).concat(d)}}}());hljs.registerLanguage("scss",function(){"use strict";return function(e){var t={className:"variable",begin:"(\\$[a-zA-Z-][a-zA-Z0-9_-]*)\\b"},i={className:"number",begin:"#[0-9A-Fa-f]+"};return e.CSS_NUMBER_MODE,e.QUOTE_STRING_MODE,e.APOS_STRING_MODE,e.C_BLOCK_COMMENT_MODE,{name:"SCSS",case_insensitive:!0,illegal:"[=/|']",contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,{className:"selector-id",begin:"\\#[A-Za-z0-9_-]+",relevance:0},{className:"selector-class",begin:"\\.[A-Za-z0-9_-]+",relevance:0},{className:"selector-attr",begin:"\\[",end:"\\]",illegal:"$"},{className:"selector-tag",begin:"\\b(a|abbr|acronym|address|area|article|aside|audio|b|base|big|blockquote|body|br|button|canvas|caption|cite|code|col|colgroup|command|datalist|dd|del|details|dfn|div|dl|dt|em|embed|fieldset|figcaption|figure|footer|form|frame|frameset|(h[1-6])|head|header|hgroup|hr|html|i|iframe|img|input|ins|kbd|keygen|label|legend|li|link|map|mark|meta|meter|nav|noframes|noscript|object|ol|optgroup|option|output|p|param|pre|progress|q|rp|rt|ruby|samp|script|section|select|small|span|strike|strong|style|sub|sup|table|tbody|td|textarea|tfoot|th|thead|time|title|tr|tt|ul|var|video)\\b",relevance:0},{className:"selector-pseudo",begin:":(visited|valid|root|right|required|read-write|read-only|out-range|optional|only-of-type|only-child|nth-of-type|nth-last-of-type|nth-last-child|nth-child|not|link|left|last-of-type|last-child|lang|invalid|indeterminate|in-range|hover|focus|first-of-type|first-line|first-letter|first-child|first|enabled|empty|disabled|default|checked|before|after|active)"},{className:"selector-pseudo",begin:"::(after|before|choices|first-letter|first-line|repeat-index|repeat-item|selection|value)"},t,{className:"attribute",begin:"\\b(src|z-index|word-wrap|word-spacing|word-break|width|widows|white-space|visibility|vertical-align|unicode-bidi|transition-timing-function|transition-property|transition-duration|transition-delay|transition|transform-style|transform-origin|transform|top|text-underline-position|text-transform|text-shadow|text-rendering|text-overflow|text-indent|text-decoration-style|text-decoration-line|text-decoration-color|text-decoration|text-align-last|text-align|tab-size|table-layout|right|resize|quotes|position|pointer-events|perspective-origin|perspective|page-break-inside|page-break-before|page-break-after|padding-top|padding-right|padding-left|padding-bottom|padding|overflow-y|overflow-x|overflow-wrap|overflow|outline-width|outline-style|outline-offset|outline-color|outline|orphans|order|opacity|object-position|object-fit|normal|none|nav-up|nav-right|nav-left|nav-index|nav-down|min-width|min-height|max-width|max-height|mask|marks|margin-top|margin-right|margin-left|margin-bottom|margin|list-style-type|list-style-position|list-style-image|list-style|line-height|letter-spacing|left|justify-content|initial|inherit|ime-mode|image-orientation|image-resolution|image-rendering|icon|hyphens|height|font-weight|font-variant-ligatures|font-variant|font-style|font-stretch|font-size-adjust|font-size|font-language-override|font-kerning|font-feature-settings|font-family|font|float|flex-wrap|flex-shrink|flex-grow|flex-flow|flex-direction|flex-basis|flex|filter|empty-cells|display|direction|cursor|counter-reset|counter-increment|content|column-width|column-span|column-rule-width|column-rule-style|column-rule-color|column-rule|column-gap|column-fill|column-count|columns|color|clip-path|clip|clear|caption-side|break-inside|break-before|break-after|box-sizing|box-shadow|box-decoration-break|bottom|border-width|border-top-width|border-top-style|border-top-right-radius|border-top-left-radius|border-top-color|border-top|border-style|border-spacing|border-right-width|border-right-style|border-right-color|border-right|border-radius|border-left-width|border-left-style|border-left-color|border-left|border-image-width|border-image-source|border-image-slice|border-image-repeat|border-image-outset|border-image|border-color|border-collapse|border-bottom-width|border-bottom-style|border-bottom-right-radius|border-bottom-left-radius|border-bottom-color|border-bottom|border|background-size|background-repeat|background-position|background-origin|background-image|background-color|background-clip|background-attachment|background-blend-mode|background|backface-visibility|auto|animation-timing-function|animation-play-state|animation-name|animation-iteration-count|animation-fill-mode|animation-duration|animation-direction|animation-delay|animation|align-self|align-items|align-content)\\b",illegal:"[^\\s]"},{begin:"\\b(whitespace|wait|w-resize|visible|vertical-text|vertical-ideographic|uppercase|upper-roman|upper-alpha|underline|transparent|top|thin|thick|text|text-top|text-bottom|tb-rl|table-header-group|table-footer-group|sw-resize|super|strict|static|square|solid|small-caps|separate|se-resize|scroll|s-resize|rtl|row-resize|ridge|right|repeat|repeat-y|repeat-x|relative|progress|pointer|overline|outside|outset|oblique|nowrap|not-allowed|normal|none|nw-resize|no-repeat|no-drop|newspaper|ne-resize|n-resize|move|middle|medium|ltr|lr-tb|lowercase|lower-roman|lower-alpha|loose|list-item|line|line-through|line-edge|lighter|left|keep-all|justify|italic|inter-word|inter-ideograph|inside|inset|inline|inline-block|inherit|inactive|ideograph-space|ideograph-parenthesis|ideograph-numeric|ideograph-alpha|horizontal|hidden|help|hand|groove|fixed|ellipsis|e-resize|double|dotted|distribute|distribute-space|distribute-letter|distribute-all-lines|disc|disabled|default|decimal|dashed|crosshair|collapse|col-resize|circle|char|center|capitalize|break-word|break-all|bottom|both|bolder|bold|block|bidi-override|below|baseline|auto|always|all-scroll|absolute|table|table-cell)\\b"},{begin:":",end:";",contains:[t,i,e.CSS_NUMBER_MODE,e.QUOTE_STRING_MODE,e.APOS_STRING_MODE,{className:"meta",begin:"!important"}]},{begin:"@(page|font-face)",lexemes:"@[a-z-]+",keywords:"@page @font-face"},{begin:"@",end:"[{;]",returnBegin:!0,keywords:"and or not only",contains:[{begin:"@[a-z-]+",className:"keyword"},t,e.QUOTE_STRING_MODE,e.APOS_STRING_MODE,i,e.CSS_NUMBER_MODE]}]}}}());hljs.registerLanguage("http",function(){"use strict";return function(e){var n="HTTP/[0-9\\.]+";return{name:"HTTP",aliases:["https"],illegal:"\\S",contains:[{begin:"^"+n,end:"$",contains:[{className:"number",begin:"\\b\\d{3}\\b"}]},{begin:"^[A-Z]+ (.*?) "+n+"$",returnBegin:!0,end:"$",contains:[{className:"string",begin:" ",end:" ",excludeBegin:!0,excludeEnd:!0},{begin:n},{className:"keyword",begin:"[A-Z]+"}]},{className:"attribute",begin:"^\\w",end:": ",excludeEnd:!0,illegal:"\\n|\\s|=",starts:{end:"$",relevance:0}},{begin:"\\n\\n",starts:{subLanguage:[],endsWithParent:!0}}]}}}());hljs.registerLanguage("java",function(){"use strict";return function(e){var a="false synchronized int abstract float private char boolean var static null if const for true while long strictfp finally protected import native final void enum else break transient catch instanceof byte super volatile case assert short package default double public try this switch continue throws protected public private module requires exports do",n={className:"meta",begin:"@[À-ʸa-zA-Z_$][À-ʸa-zA-Z_$0-9]*",contains:[{begin:/\(/,end:/\)/,contains:["self"]}]};return{name:"Java",aliases:["jsp"],keywords:a,illegal:/<\/|#/,contains:[e.COMMENT("/\\*\\*","\\*/",{relevance:0,contains:[{begin:/\w+@/,relevance:0},{className:"doctag",begin:"@[A-Za-z]+"}]}),e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,{className:"class",beginKeywords:"class interface",end:/[{;=]/,excludeEnd:!0,keywords:"class interface",illegal:/[:"\[\]]/,contains:[{beginKeywords:"extends implements"},e.UNDERSCORE_TITLE_MODE]},{beginKeywords:"new throw return else",relevance:0},{className:"function",begin:"([À-ʸa-zA-Z_$][À-ʸa-zA-Z_$0-9]*(<[À-ʸa-zA-Z_$][À-ʸa-zA-Z_$0-9]*(\\s*,\\s*[À-ʸa-zA-Z_$][À-ʸa-zA-Z_$0-9]*)*>)?\\s+)+"+e.UNDERSCORE_IDENT_RE+"\\s*\\(",returnBegin:!0,end:/[{;=]/,excludeEnd:!0,keywords:a,contains:[{begin:e.UNDERSCORE_IDENT_RE+"\\s*\\(",returnBegin:!0,relevance:0,contains:[e.UNDERSCORE_TITLE_MODE]},{className:"params",begin:/\(/,end:/\)/,keywords:a,relevance:0,contains:[n,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,e.C_NUMBER_MODE,e.C_BLOCK_COMMENT_MODE]},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},{className:"number",begin:"\\b(0[bB]([01]+[01_]+[01]+|[01]+)|0[xX]([a-fA-F0-9]+[a-fA-F0-9_]+[a-fA-F0-9]+|[a-fA-F0-9]+)|(([\\d]+[\\d_]+[\\d]+|[\\d]+)(\\.([\\d]+[\\d_]+[\\d]+|[\\d]+))?|\\.([\\d]+[\\d_]+[\\d]+|[\\d]+))([eE][-+]?\\d+)?)[lLfF]?",relevance:0},n]}}}());hljs.registerLanguage("swift",function(){"use strict";return function(e){var i={keyword:"#available #colorLiteral #column #else #elseif #endif #file #fileLiteral #function #if #imageLiteral #line #selector #sourceLocation _ __COLUMN__ __FILE__ __FUNCTION__ __LINE__ Any as as! as? associatedtype associativity break case catch class continue convenience default defer deinit didSet do dynamic dynamicType else enum extension fallthrough false fileprivate final for func get guard if import in indirect infix init inout internal is lazy left let mutating nil none nonmutating open operator optional override postfix precedence prefix private protocol Protocol public repeat required rethrows return right self Self set static struct subscript super switch throw throws true try try! try? Type typealias unowned var weak where while willSet",literal:"true false nil",built_in:"abs advance alignof alignofValue anyGenerator assert assertionFailure bridgeFromObjectiveC bridgeFromObjectiveCUnconditional bridgeToObjectiveC bridgeToObjectiveCUnconditional c compactMap contains count countElements countLeadingZeros debugPrint debugPrintln distance dropFirst dropLast dump encodeBitsAsWords enumerate equal fatalError filter find getBridgedObjectiveCType getVaList indices insertionSort isBridgedToObjectiveC isBridgedVerbatimToObjectiveC isUniquelyReferenced isUniquelyReferencedNonObjC join lazy lexicographicalCompare map max maxElement min minElement numericCast overlaps partition posix precondition preconditionFailure print println quickSort readLine reduce reflect reinterpretCast reverse roundUpToAlignment sizeof sizeofValue sort split startsWith stride strideof strideofValue swap toString transcode underestimateCount unsafeAddressOf unsafeBitCast unsafeDowncast unsafeUnwrap unsafeReflect withExtendedLifetime withObjectAtPlusZero withUnsafePointer withUnsafePointerToObject withUnsafeMutablePointer withUnsafeMutablePointers withUnsafePointer withUnsafePointers withVaList zip"},n=e.COMMENT("/\\*","\\*/",{contains:["self"]}),t={className:"subst",begin:/\\\(/,end:"\\)",keywords:i,contains:[]},a={className:"string",contains:[e.BACKSLASH_ESCAPE,t],variants:[{begin:/"""/,end:/"""/},{begin:/"/,end:/"/}]},r={className:"number",begin:"\\b([\\d_]+(\\.[\\deE_]+)?|0x[a-fA-F0-9_]+(\\.[a-fA-F0-9p_]+)?|0b[01_]+|0o[0-7_]+)\\b",relevance:0};return t.contains=[r],{name:"Swift",keywords:i,contains:[a,e.C_LINE_COMMENT_MODE,n,{className:"type",begin:"\\b[A-Z][\\wÀ-ʸ']*[!?]"},{className:"type",begin:"\\b[A-Z][\\wÀ-ʸ']*",relevance:0},r,{className:"function",beginKeywords:"func",end:"{",excludeEnd:!0,contains:[e.inherit(e.TITLE_MODE,{begin:/[A-Za-z$_][0-9A-Za-z$_]*/}),{begin://},{className:"params",begin:/\(/,end:/\)/,endsParent:!0,keywords:i,contains:["self",r,a,e.C_BLOCK_COMMENT_MODE,{begin:":"}],illegal:/["']/}],illegal:/\[|%/},{className:"class",beginKeywords:"struct protocol class extension enum",keywords:i,end:"\\{",excludeEnd:!0,contains:[e.inherit(e.TITLE_MODE,{begin:/[A-Za-z$_][\u00C0-\u02B80-9A-Za-z$_]*/})]},{className:"meta",begin:"(@discardableResult|@warn_unused_result|@exported|@lazy|@noescape|@NSCopying|@NSManaged|@objc|@objcMembers|@convention|@required|@noreturn|@IBAction|@IBDesignable|@IBInspectable|@IBOutlet|@infix|@prefix|@postfix|@autoclosure|@testable|@available|@nonobjc|@NSApplicationMain|@UIApplicationMain|@dynamicMemberLookup|@propertyWrapper)"},{beginKeywords:"import",end:/$/,contains:[e.C_LINE_COMMENT_MODE,n]}]}}}());hljs.registerLanguage("less",function(){"use strict";return function(e){var n="([\\w-]+|@{[\\w-]+})",a=[],s=[],t=function(e){return{className:"string",begin:"~?"+e+".*?"+e}},r=function(e,n,a){return{className:e,begin:n,relevance:a}},i={begin:"\\(",end:"\\)",contains:s,relevance:0};s.push(e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,t("'"),t('"'),e.CSS_NUMBER_MODE,{begin:"(url|data-uri)\\(",starts:{className:"string",end:"[\\)\\n]",excludeEnd:!0}},r("number","#[0-9A-Fa-f]+\\b"),i,r("variable","@@?[\\w-]+",10),r("variable","@{[\\w-]+}"),r("built_in","~?`[^`]*?`"),{className:"attribute",begin:"[\\w-]+\\s*:",end:":",returnBegin:!0,excludeEnd:!0},{className:"meta",begin:"!important"});var c=s.concat({begin:"{",end:"}",contains:a}),l={beginKeywords:"when",endsWithParent:!0,contains:[{beginKeywords:"and not"}].concat(s)},o={begin:n+"\\s*:",returnBegin:!0,end:"[;}]",relevance:0,contains:[{className:"attribute",begin:n,end:":",excludeEnd:!0,starts:{endsWithParent:!0,illegal:"[<=$]",relevance:0,contains:s}}]},g={className:"keyword",begin:"@(import|media|charset|font-face|(-[a-z]+-)?keyframes|supports|document|namespace|page|viewport|host)\\b",starts:{end:"[;{}]",returnEnd:!0,contains:s,relevance:0}},d={className:"variable",variants:[{begin:"@[\\w-]+\\s*:",relevance:15},{begin:"@[\\w-]+"}],starts:{end:"[;}]",returnEnd:!0,contains:c}},b={variants:[{begin:"[\\.#:&\\[>]",end:"[;{}]"},{begin:n,end:"{"}],returnBegin:!0,returnEnd:!0,illegal:"[<='$\"]",relevance:0,contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,l,r("keyword","all\\b"),r("variable","@{[\\w-]+}"),r("selector-tag",n+"%?",0),r("selector-id","#"+n),r("selector-class","\\."+n,0),r("selector-tag","&",0),{className:"selector-attr",begin:"\\[",end:"\\]"},{className:"selector-pseudo",begin:/:(:)?[a-zA-Z0-9\_\-\+\(\)"'.]+/},{begin:"\\(",end:"\\)",contains:c},{begin:"!important"}]};return a.push(e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,g,d,o,b),{name:"Less",case_insensitive:!0,illegal:"[=>'/<($\"]",contains:a}}}());hljs.registerLanguage("json",function(){"use strict";return function(n){var e={literal:"true false null"},i=[n.C_LINE_COMMENT_MODE,n.C_BLOCK_COMMENT_MODE],t=[n.QUOTE_STRING_MODE,n.C_NUMBER_MODE],a={end:",",endsWithParent:!0,excludeEnd:!0,contains:t,keywords:e},l={begin:"{",end:"}",contains:[{className:"attr",begin:/"/,end:/"/,contains:[n.BACKSLASH_ESCAPE],illegal:"\\n"},n.inherit(a,{begin:/:/})].concat(i),illegal:"\\S"},s={begin:"\\[",end:"\\]",contains:[n.inherit(a)],illegal:"\\S"};return t.push(l,s),i.forEach((function(n){t.push(n)})),{name:"JSON",contains:t,keywords:e,illegal:"\\S"}}}());hljs.registerLanguage("typescript",function(){"use strict";return function(e){var n={keyword:"in if for while finally var new function do return void else break catch instanceof with throw case default try this switch continue typeof delete let yield const class public private protected get set super static implements enum export import declare type namespace abstract as from extends async await",literal:"true false null undefined NaN Infinity",built_in:"eval isFinite isNaN parseFloat parseInt decodeURI decodeURIComponent encodeURI encodeURIComponent escape unescape Object Function Boolean Error EvalError InternalError RangeError ReferenceError StopIteration SyntaxError TypeError URIError Number Math Date String RegExp Array Float32Array Float64Array Int16Array Int32Array Int8Array Uint16Array Uint32Array Uint8Array Uint8ClampedArray ArrayBuffer DataView JSON Intl arguments require module console window document any number boolean string void Promise"},r={className:"meta",begin:"@[A-Za-z$_][0-9A-Za-z$_]*"},a={begin:"\\(",end:/\)/,keywords:n,contains:["self",e.QUOTE_STRING_MODE,e.APOS_STRING_MODE,e.NUMBER_MODE]},t={className:"params",begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:n,contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,r,a]},s={className:"number",variants:[{begin:"\\b(0[bB][01]+)n?"},{begin:"\\b(0[oO][0-7]+)n?"},{begin:e.C_NUMBER_RE+"n?"}],relevance:0},i={className:"subst",begin:"\\$\\{",end:"\\}",keywords:n,contains:[]},o={begin:"html`",end:"",starts:{end:"`",returnEnd:!1,contains:[e.BACKSLASH_ESCAPE,i],subLanguage:"xml"}},c={begin:"css`",end:"",starts:{end:"`",returnEnd:!1,contains:[e.BACKSLASH_ESCAPE,i],subLanguage:"css"}},E={className:"string",begin:"`",end:"`",contains:[e.BACKSLASH_ESCAPE,i]};return i.contains=[e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,o,c,E,s,e.REGEXP_MODE],{name:"TypeScript",aliases:["ts"],keywords:n,contains:[{className:"meta",begin:/^\s*['"]use strict['"]/},e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,o,c,E,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,s,{begin:"("+e.RE_STARTERS_RE+"|\\b(case|return|throw)\\b)\\s*",keywords:"return throw case",contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,e.REGEXP_MODE,{className:"function",begin:"(\\(.*?\\)|"+e.IDENT_RE+")\\s*=>",returnBegin:!0,end:"\\s*=>",contains:[{className:"params",variants:[{begin:e.IDENT_RE},{begin:/\(\s*\)/},{begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:n,contains:["self",e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]}]}]}],relevance:0},{className:"function",beginKeywords:"function",end:/[\{;]/,excludeEnd:!0,keywords:n,contains:["self",e.inherit(e.TITLE_MODE,{begin:"[A-Za-z$_][0-9A-Za-z$_]*"}),t],illegal:/%/,relevance:0},{beginKeywords:"constructor",end:/[\{;]/,excludeEnd:!0,contains:["self",t]},{begin:/module\./,keywords:{built_in:"module"},relevance:0},{beginKeywords:"module",end:/\{/,excludeEnd:!0},{beginKeywords:"interface",end:/\{/,excludeEnd:!0,keywords:"interface extends"},{begin:/\$[(.]/},{begin:"\\."+e.IDENT_RE,relevance:0},r,a]}}}());hljs.registerLanguage("c-like",function(){"use strict";return function(e){function t(e){return"(?:"+e+")?"}var n="(decltype\\(auto\\)|"+t("[a-zA-Z_]\\w*::")+"[a-zA-Z_]\\w*"+t("<.*?>")+")",r={className:"keyword",begin:"\\b[a-z\\d_]*_t\\b"},a={className:"string",variants:[{begin:'(u8?|U|L)?"',end:'"',illegal:"\\n",contains:[e.BACKSLASH_ESCAPE]},{begin:"(u8?|U|L)?'(\\\\(x[0-9A-Fa-f]{2}|u[0-9A-Fa-f]{4,8}|[0-7]{3}|\\S)|.)",end:"'",illegal:"."},{begin:/(?:u8?|U|L)?R"([^()\\ ]{0,16})\((?:.|\n)*?\)\1"/}]},s={className:"number",variants:[{begin:"\\b(0b[01']+)"},{begin:"(-?)\\b([\\d']+(\\.[\\d']*)?|\\.[\\d']+)(u|U|l|L|ul|UL|f|F|b|B)"},{begin:"(-?)(\\b0[xX][a-fA-F0-9']+|(\\b[\\d']+(\\.[\\d']*)?|\\.[\\d']+)([eE][-+]?[\\d']+)?)"}],relevance:0},i={className:"meta",begin:/#\s*[a-z]+\b/,end:/$/,keywords:{"meta-keyword":"if else elif endif define undef warning error line pragma _Pragma ifdef ifndef include"},contains:[{begin:/\\\n/,relevance:0},e.inherit(a,{className:"meta-string"}),{className:"meta-string",begin:/<.*?>/,end:/$/,illegal:"\\n"},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},c={className:"title",begin:t("[a-zA-Z_]\\w*::")+e.IDENT_RE,relevance:0},o=t("[a-zA-Z_]\\w*::")+e.IDENT_RE+"\\s*\\(",l={keyword:"int float while private char char8_t char16_t char32_t catch import module export virtual operator sizeof dynamic_cast|10 typedef const_cast|10 const for static_cast|10 union namespace unsigned long volatile static protected bool template mutable if public friend do goto auto void enum else break extern using asm case typeid wchar_t short reinterpret_cast|10 default double register explicit signed typename try this switch continue inline delete alignas alignof constexpr consteval constinit decltype concept co_await co_return co_yield requires noexcept static_assert thread_local restrict final override atomic_bool atomic_char atomic_schar atomic_uchar atomic_short atomic_ushort atomic_int atomic_uint atomic_long atomic_ulong atomic_llong atomic_ullong new throw return and and_eq bitand bitor compl not not_eq or or_eq xor xor_eq",built_in:"std string wstring cin cout cerr clog stdin stdout stderr stringstream istringstream ostringstream auto_ptr deque list queue stack vector map set bitset multiset multimap unordered_set unordered_map unordered_multiset unordered_multimap array shared_ptr abort terminate abs acos asin atan2 atan calloc ceil cosh cos exit exp fabs floor fmod fprintf fputs free frexp fscanf future isalnum isalpha iscntrl isdigit isgraph islower isprint ispunct isspace isupper isxdigit tolower toupper labs ldexp log10 log malloc realloc memchr memcmp memcpy memset modf pow printf putchar puts scanf sinh sin snprintf sprintf sqrt sscanf strcat strchr strcmp strcpy strcspn strlen strncat strncmp strncpy strpbrk strrchr strspn strstr tanh tan vfprintf vprintf vsprintf endl initializer_list unique_ptr _Bool complex _Complex imaginary _Imaginary",literal:"true false nullptr NULL"},d=[r,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,s,a],_={variants:[{begin:/=/,end:/;/},{begin:/\(/,end:/\)/},{beginKeywords:"new throw return else",end:/;/}],keywords:l,contains:d.concat([{begin:/\(/,end:/\)/,keywords:l,contains:d.concat(["self"]),relevance:0}]),relevance:0},u={className:"function",begin:"("+n+"[\\*&\\s]+)+"+o,returnBegin:!0,end:/[{;=]/,excludeEnd:!0,keywords:l,illegal:/[^\w\s\*&:<>]/,contains:[{begin:"decltype\\(auto\\)",keywords:l,relevance:0},{begin:o,returnBegin:!0,contains:[c],relevance:0},{className:"params",begin:/\(/,end:/\)/,keywords:l,relevance:0,contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,a,s,r,{begin:/\(/,end:/\)/,keywords:l,relevance:0,contains:["self",e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,a,s,r]}]},r,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,i]};return{aliases:["c","cc","h","c++","h++","hpp","hh","hxx","cxx"],keywords:l,disableAutodetect:!0,illegal:"",keywords:l,contains:["self",r]},{begin:e.IDENT_RE+"::",keywords:l},{className:"class",beginKeywords:"class struct",end:/[{;:]/,contains:[{begin://,contains:["self"]},e.TITLE_MODE]}]),exports:{preprocessor:i,strings:a,keywords:l}}}}());hljs.registerLanguage("cpp",function(){"use strict";return function(e){var t=e.getLanguage("c-like").rawDefinition();return t.disableAutodetect=!1,t.name="C++",t.aliases=["cc","c++","h++","hpp","hh","hxx","cxx"],t}}());hljs.registerLanguage("properties",function(){"use strict";return function(e){var n="[ \\t\\f]*",t="("+n+"[:=]"+n+"|[ \\t\\f]+)",a="([^\\\\:= \\t\\f\\n]|\\\\.)+",s={end:t,relevance:0,starts:{className:"string",end:/$/,relevance:0,contains:[{begin:"\\\\\\n"}]}};return{name:".properties",case_insensitive:!0,illegal:/\S/,contains:[e.COMMENT("^\\s*[!#]","$"),{begin:"([^\\\\\\W:= \\t\\f\\n]|\\\\.)+"+t,returnBegin:!0,contains:[{className:"attr",begin:"([^\\\\\\W:= \\t\\f\\n]|\\\\.)+",endsParent:!0,relevance:0}],starts:s},{begin:a+t,returnBegin:!0,relevance:0,contains:[{className:"meta",begin:a,endsParent:!0,relevance:0}],starts:s},{className:"attr",relevance:0,begin:a+n+"$"}]}}}());hljs.registerLanguage("twig",function(){"use strict";return function(e){var a="attribute block constant cycle date dump include max min parent random range source template_from_string",n={beginKeywords:a,keywords:{name:a},relevance:0,contains:[{className:"params",begin:"\\(",end:"\\)"}]},t={begin:/\|[A-Za-z_]+:?/,keywords:"abs batch capitalize column convert_encoding date date_modify default escape filter first format inky_to_html inline_css join json_encode keys last length lower map markdown merge nl2br number_format raw reduce replace reverse round slice sort spaceless split striptags title trim upper url_encode",contains:[n]},s="apply autoescape block deprecated do embed extends filter flush for from if import include macro sandbox set use verbatim with";return s=s+" "+s.split(" ").map((function(e){return"end"+e})).join(" "),{name:"Twig",aliases:["craftcms"],case_insensitive:!0,subLanguage:"xml",contains:[e.COMMENT(/\{#/,/#}/),{className:"template-tag",begin:/\{%/,end:/%}/,contains:[{className:"name",begin:/\w+/,keywords:s,starts:{endsWithParent:!0,contains:[t,n],relevance:0}}]},{className:"template-variable",begin:/\{\{/,end:/}}/,contains:["self",t,n]}]}}}());hljs.registerLanguage("javascript",function(){"use strict";return function(e){var n={begin:/<[A-Za-z0-9\\._:-]+/,end:/\/[A-Za-z0-9\\._:-]+>|\/>/},a="[A-Za-z$_][0-9A-Za-z$_]*",s={keyword:"in of if for while finally var new function do return void else break catch instanceof with throw case default try this switch continue typeof delete let yield const export super debugger as async await static import from as",literal:"true false null undefined NaN Infinity",built_in:"eval isFinite isNaN parseFloat parseInt decodeURI decodeURIComponent encodeURI encodeURIComponent escape unescape Object Function Boolean Error EvalError InternalError RangeError ReferenceError StopIteration SyntaxError TypeError URIError Number Math Date String RegExp Array Float32Array Float64Array Int16Array Int32Array Int8Array Uint16Array Uint32Array Uint8Array Uint8ClampedArray ArrayBuffer DataView JSON Intl arguments require module console window document Symbol Set Map WeakSet WeakMap Proxy Reflect Promise"},r={className:"number",variants:[{begin:"\\b(0[bB][01]+)n?"},{begin:"\\b(0[oO][0-7]+)n?"},{begin:e.C_NUMBER_RE+"n?"}],relevance:0},i={className:"subst",begin:"\\$\\{",end:"\\}",keywords:s,contains:[]},t={begin:"html`",end:"",starts:{end:"`",returnEnd:!1,contains:[e.BACKSLASH_ESCAPE,i],subLanguage:"xml"}},c={begin:"css`",end:"",starts:{end:"`",returnEnd:!1,contains:[e.BACKSLASH_ESCAPE,i],subLanguage:"css"}},o={className:"string",begin:"`",end:"`",contains:[e.BACKSLASH_ESCAPE,i]};i.contains=[e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,t,c,o,r,e.REGEXP_MODE];var l=i.contains.concat([e.C_BLOCK_COMMENT_MODE,e.C_LINE_COMMENT_MODE]),d={className:"params",begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,contains:l};return{name:"JavaScript",aliases:["js","jsx","mjs","cjs"],keywords:s,contains:[{className:"meta",relevance:10,begin:/^\s*['"]use (strict|asm)['"]/},{className:"meta",begin:/^#!/,end:/$/},e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,t,c,o,e.C_LINE_COMMENT_MODE,e.COMMENT("/\\*\\*","\\*/",{relevance:0,contains:[{className:"doctag",begin:"@[A-Za-z]+",contains:[{className:"type",begin:"\\{",end:"\\}",relevance:0},{className:"variable",begin:a+"(?=\\s*(-)|$)",endsParent:!0,relevance:0},{begin:/(?=[^\n])\s/,relevance:0}]}]}),e.C_BLOCK_COMMENT_MODE,r,{begin:/[{,\n]\s*/,relevance:0,contains:[{begin:a+"\\s*:",returnBegin:!0,relevance:0,contains:[{className:"attr",begin:a,relevance:0}]}]},{begin:"("+e.RE_STARTERS_RE+"|\\b(case|return|throw)\\b)\\s*",keywords:"return throw case",contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,e.REGEXP_MODE,{className:"function",begin:"(\\(.*?\\)|"+a+")\\s*=>",returnBegin:!0,end:"\\s*=>",contains:[{className:"params",variants:[{begin:a},{begin:/\(\s*\)/},{begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:s,contains:l}]}]},{begin:/,/,relevance:0},{className:"",begin:/\s/,end:/\s*/,skip:!0},{variants:[{begin:"<>",end:""},{begin:n.begin,end:n.end}],subLanguage:"xml",contains:[{begin:n.begin,end:n.end,skip:!0,contains:["self"]}]}],relevance:0},{className:"function",beginKeywords:"function",end:/\{/,excludeEnd:!0,contains:[e.inherit(e.TITLE_MODE,{begin:a}),d],illegal:/\[|%/},{begin:/\$[(.]/},e.METHOD_GUARD,{className:"class",beginKeywords:"class",end:/[{;=]/,excludeEnd:!0,illegal:/[:"\[\]]/,contains:[{beginKeywords:"extends"},e.UNDERSCORE_TITLE_MODE]},{beginKeywords:"constructor",end:/\{/,excludeEnd:!0},{begin:"(get|set)\\s+(?="+a+"\\()",end:/{/,keywords:"get set",contains:[e.inherit(e.TITLE_MODE,{begin:a}),{begin:/\(\)/},d]}],illegal:/#(?!!)/}}}()); \ No newline at end of file diff --git a/docs/en/assets/jquery.js b/docs/en/assets/jquery.js new file mode 100644 index 0000000..9fd22ca --- /dev/null +++ b/docs/en/assets/jquery.js @@ -0,0 +1,2 @@ +/*! jQuery v3.5.0 | (c) JS Foundation and other contributors | jquery.org/license */ +!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(C,e){"use strict";var t=[],r=Object.getPrototypeOf,s=t.slice,g=t.flat?function(e){return t.flat.call(e)}:function(e){return t.concat.apply([],e)},u=t.push,i=t.indexOf,n={},o=n.toString,v=n.hasOwnProperty,a=v.toString,l=a.call(Object),y={},m=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType},x=function(e){return null!=e&&e===e.window},E=C.document,c={type:!0,src:!0,nonce:!0,noModule:!0};function b(e,t,n){var r,i,o=(n=n||E).createElement("script");if(o.text=e,t)for(r in c)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function w(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[o.call(e)]||"object":typeof e}var f="3.5.0",S=function(e,t){return new S.fn.init(e,t)};function p(e){var t=!!e&&"length"in e&&e.length,n=w(e);return!m(e)&&!x(e)&&("array"===n||0===t||"number"==typeof t&&0+~]|"+M+")"+M+"*"),U=new RegExp(M+"|>"),X=new RegExp(F),V=new RegExp("^"+I+"$"),G={ID:new RegExp("^#("+I+")"),CLASS:new RegExp("^\\.("+I+")"),TAG:new RegExp("^("+I+"|[*])"),ATTR:new RegExp("^"+W),PSEUDO:new RegExp("^"+F),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+R+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/HTML$/i,Q=/^(?:input|select|textarea|button)$/i,J=/^h\d$/i,K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\[\\da-fA-F]{1,6}"+M+"?|\\\\([^\\r\\n\\f])","g"),ne=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},re=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){T()},ae=be(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{H.apply(t=O.call(p.childNodes),p.childNodes),t[p.childNodes.length].nodeType}catch(e){H={apply:t.length?function(e,t){L.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&(T(e),e=e||C,E)){if(11!==p&&(u=Z.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&y(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return H.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&d.getElementsByClassName&&e.getElementsByClassName)return H.apply(n,e.getElementsByClassName(i)),n}if(d.qsa&&!N[t+" "]&&(!v||!v.test(t))&&(1!==p||"object"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===p&&(U.test(t)||z.test(t))){(f=ee.test(t)&&ye(e.parentNode)||e)===e&&d.scope||((s=e.getAttribute("id"))?s=s.replace(re,ie):e.setAttribute("id",s=S)),o=(l=h(t)).length;while(o--)l[o]=(s?"#"+s:":scope")+" "+xe(l[o]);c=l.join(",")}try{return H.apply(n,f.querySelectorAll(c)),n}catch(e){N(t,!0)}finally{s===S&&e.removeAttribute("id")}}}return g(t.replace($,"$1"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function le(e){return e[S]=!0,e}function ce(e){var t=C.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){var n=e.split("|"),r=n.length;while(r--)b.attrHandle[n[r]]=t}function pe(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function de(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return("input"===t||"button"===t)&&e.type===n}}function ge(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ve(a){return le(function(o){return o=+o,le(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ye(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}for(e in d=se.support={},i=se.isXML=function(e){var t=e.namespaceURI,n=(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||"HTML")},T=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:p;return r!=C&&9===r.nodeType&&r.documentElement&&(a=(C=r).documentElement,E=!i(C),p!=C&&(n=C.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",oe,!1):n.attachEvent&&n.attachEvent("onunload",oe)),d.scope=ce(function(e){return a.appendChild(e).appendChild(C.createElement("div")),"undefined"!=typeof e.querySelectorAll&&!e.querySelectorAll(":scope fieldset div").length}),d.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),d.getElementsByTagName=ce(function(e){return e.appendChild(C.createComment("")),!e.getElementsByTagName("*").length}),d.getElementsByClassName=K.test(C.getElementsByClassName),d.getById=ce(function(e){return a.appendChild(e).id=S,!C.getElementsByName||!C.getElementsByName(S).length}),d.getById?(b.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=d.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):d.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},b.find.CLASS=d.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],v=[],(d.qsa=K.test(C.querySelectorAll))&&(ce(function(e){var t;a.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&v.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||v.push("\\["+M+"*(?:value|"+R+")"),e.querySelectorAll("[id~="+S+"-]").length||v.push("~="),(t=C.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||v.push("\\["+M+"*name"+M+"*="+M+"*(?:''|\"\")"),e.querySelectorAll(":checked").length||v.push(":checked"),e.querySelectorAll("a#"+S+"+*").length||v.push(".#.+[+~]"),e.querySelectorAll("\\\f"),v.push("[\\r\\n\\f]")}),ce(function(e){e.innerHTML="";var t=C.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&v.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&v.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&v.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),v.push(",.*:")})),(d.matchesSelector=K.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){d.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",F)}),v=v.length&&new RegExp(v.join("|")),s=s.length&&new RegExp(s.join("|")),t=K.test(a.compareDocumentPosition),y=t||K.test(a.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},D=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!d.sortDetached&&t.compareDocumentPosition(e)===n?e==C||e.ownerDocument==p&&y(p,e)?-1:t==C||t.ownerDocument==p&&y(p,t)?1:u?P(u,e)-P(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e==C?-1:t==C?1:i?-1:o?1:u?P(u,e)-P(u,t):0;if(i===o)return pe(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?pe(a[r],s[r]):a[r]==p?-1:s[r]==p?1:0}),C},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if(T(e),d.matchesSelector&&E&&!N[t+" "]&&(!s||!s.test(t))&&(!v||!v.test(t)))try{var n=c.call(e,t);if(n||d.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){N(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return G.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=h(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=m[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&m(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function D(e,n,r){return m(n)?S.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?S.grep(e,function(e){return e===n!==r}):"string"!=typeof n?S.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(S.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||j,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:q.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof S?t[0]:t,S.merge(this,S.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:E,!0)),N.test(r[1])&&S.isPlainObject(t))for(r in t)m(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=E.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):m(e)?void 0!==n.ready?n.ready(e):e(S):S.makeArray(e,this)}).prototype=S.fn,j=S(E);var L=/^(?:parents|prev(?:Until|All))/,H={children:!0,contents:!0,next:!0,prev:!0};function O(e,t){while((e=e[t])&&1!==e.nodeType);return e}S.fn.extend({has:function(e){var t=S(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,he=/^$|^module$|\/(?:java|ecma)script/i;ce=E.createDocumentFragment().appendChild(E.createElement("div")),(fe=E.createElement("input")).setAttribute("type","radio"),fe.setAttribute("checked","checked"),fe.setAttribute("name","t"),ce.appendChild(fe),y.checkClone=ce.cloneNode(!0).cloneNode(!0).lastChild.checked,ce.innerHTML="",y.noCloneChecked=!!ce.cloneNode(!0).lastChild.defaultValue,ce.innerHTML="",y.option=!!ce.lastChild;var ge={thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};function ve(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&A(e,t)?S.merge([e],n):n}function ye(e,t){for(var n=0,r=e.length;n",""]);var me=/<|&#?\w+;/;function xe(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d\s*$/g;function qe(e,t){return A(e,"table")&&A(11!==t.nodeType?t:t.firstChild,"tr")&&S(e).children("tbody")[0]||e}function Le(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function He(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Oe(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(Y.hasData(e)&&(s=Y.get(e).events))for(i in Y.remove(t,"handle events"),s)for(n=0,r=s[i].length;n").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),E.head.appendChild(r[0])},abort:function(){i&&i()}}});var Ut,Xt=[],Vt=/(=)\?(?=&|$)|\?\?/;S.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=Xt.pop()||S.expando+"_"+Ct.guid++;return this[e]=!0,e}}),S.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Vt.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Vt.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=m(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Vt,"$1"+r):!1!==e.jsonp&&(e.url+=(Et.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||S.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=C[r],C[r]=function(){o=arguments},n.always(function(){void 0===i?S(C).removeProp(r):C[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,Xt.push(r)),o&&m(i)&&i(o[0]),o=i=void 0}),"script"}),y.createHTMLDocument=((Ut=E.implementation.createHTMLDocument("").body).innerHTML="
",2===Ut.childNodes.length),S.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(y.createHTMLDocument?((r=(t=E.implementation.createHTMLDocument("")).createElement("base")).href=E.location.href,t.head.appendChild(r)):t=E),o=!n&&[],(i=N.exec(e))?[t.createElement(i[1])]:(i=xe([e],t,o),o&&o.length&&S(o).remove(),S.merge([],i.childNodes)));var r,i,o},S.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(S.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},S.expr.pseudos.animated=function(t){return S.grep(S.timers,function(e){return t===e.elem}).length},S.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=S.css(e,"position"),c=S(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=S.css(e,"top"),u=S.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),m(t)&&(t=t.call(e,n,S.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):("number"==typeof f.top&&(f.top+="px"),"number"==typeof f.left&&(f.left+="px"),c.css(f))}},S.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){S.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===S.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===S.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=S(e).offset()).top+=S.css(e,"borderTopWidth",!0),i.left+=S.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-S.css(r,"marginTop",!0),left:t.left-i.left-S.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===S.css(e,"position"))e=e.offsetParent;return e||re})}}),S.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;S.fn[t]=function(e){return $(this,function(e,t,n){var r;if(x(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),S.each(["top","left"],function(e,n){S.cssHooks[n]=$e(y.pixelPosition,function(e,t){if(t)return t=Be(e,n),Me.test(t)?S(e).position()[n]+"px":t})}),S.each({Height:"height",Width:"width"},function(a,s){S.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){S.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return $(this,function(e,t,n){var r;return x(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?S.css(e,t,i):S.style(e,t,n,i)},s,n?e:void 0,n)}})}),S.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){S.fn[t]=function(e){return this.on(t,e)}}),S.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),S.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){S.fn[n]=function(e,t){return 0 a",this.offsets=[],this.targets=[],this.activeTarget=null,this.scrollHeight=0,this.$scrollElement.on("scroll.bs.scrollspy",r.proxy(this.process,this)),this.refresh(),this.process()}function s(i){return this.each(function(){var t=r(this),s=t.data("bs.scrollspy"),e="object"==typeof i&&i;s||t.data("bs.scrollspy",s=new o(this,e)),"string"==typeof i&&s[i]()})}o.VERSION="3.3.7",o.DEFAULTS={offset:10},o.prototype.getScrollHeight=function(){return this.$scrollElement[0].scrollHeight||Math.max(this.$body[0].scrollHeight,document.documentElement.scrollHeight)},o.prototype.refresh=function(){var t=this,i="offset",o=0;this.offsets=[],this.targets=[],this.scrollHeight=this.getScrollHeight(),r.isWindow(this.$scrollElement[0])||(i="position",o=this.$scrollElement.scrollTop()),this.$body.find(this.selector).map(function(){var t=r(this),s=t.data("target")||t.attr("href"),e=/^#./.test(s)&&r(s);return e&&e.length&&e.is(":visible")&&[[e[i]().top+o,s]]||null}).sort(function(t,s){return t[0]-s[0]}).each(function(){t.offsets.push(this[0]),t.targets.push(this[1])})},o.prototype.process=function(){var t,s=this.$scrollElement.scrollTop()+this.options.offset,e=this.getScrollHeight(),i=this.options.offset+e-this.$scrollElement.height(),o=this.offsets,r=this.targets,l=this.activeTarget;if(this.scrollHeight!=e&&this.refresh(),i<=s)return l!=(t=r[r.length-1])&&this.activate(t);if(l&&s=o[t]&&(void 0===o[t+1]||s'))&&w.css("position",n.css("position")),(v=function(){var t,o,i;if(!f)return k=x.height(),t=parseInt(b.css("border-top-width"),10),o=parseInt(b.css("padding-top"),10),l=parseInt(b.css("padding-bottom"),10),a=b.offset().top+t+o,c=b.height(),g&&(h=g=!1,null==V&&(n.insertAfter(w),w.detach()),n.css({position:"",top:"",width:"",bottom:""}).removeClass(j),i=!0),p=n.offset().top-(parseInt(n.css("margin-top"),10)||0)-F,d=n.outerHeight(!0),u=n.css("float"),w&&w.css({width:r(n),height:d,display:n.css("display"),"vertical-align":n.css("vertical-align"),float:u}),i?e():void 0})(),m=void 0,y=F,_=A,e=function(){var t,o,i,e,s,r;if(d!==c&&!f)return i=!1,null!=_&&(_-=1)<=0&&(_=A,v(),i=!0),i||x.height()===k||(v(),i=!0),e=Q.scrollTop(),null!=m&&(o=e-m),m=e,g?(C&&(s=c+a + + + + + + + + VR Payment Shopware 6 Documentation + + + + +
+
+

VR Payment Shopware 6 Documentation

+

Documentation

+ +
+
+
+
+
+
+

+ 1Overview

+
+
+
+

The VR Payment Payment Plugin integrates modern payment processing into Shopware 6, offering features like iFrame-based payments, refunds, captures, and PCI compliance. It supports seamless integration with the VR Payment Portal for managing transactions and payment methods.

+
+

Important: Please note that only Major (e.g. 6.x.0.0) and Minor (e.g. 6.0.x.0) update will be tested for compatibility within a 2 weeks after release.

+
+
+
+

+ 2Requirements

+
+
+
+

Shopware Version: 6.5.x or 6.6.x (see compatibility table).

+
+

PHP: Minimum version as required by your Shopware installation (e.g., 7.4+).

+
+

VR Payment Account: Obtain Space ID, User ID, and API Key from VR Payment Portal (see Portal Start-up Guide).

+
+
+
+

+ 3Compatibility

+
+
+ ++++++ + + + + + + + + + + + + + + + + + + + + +

Shopware Version

Plugin Version

PHP Version

Support Until

6.6.x

6.x.x

PHP 8.2 and 8.3

Ongoing

6.5.x - Deprecated

5.x.x

PHP 8.2 and 8.3

October 2024

+
+
+

+ 4Installation

+
+
+
+
+

+ 4.1Via Marketplace

+
+
+
+
    +
  1. +

    Log in to the backend of your Shopware store.

    +
  2. +
  3. +

    Navigate to Settings → System → Plugins.

    +
  4. +
  5. +

    Click on the menu caret and select the Install link of the plugin to install

    +
    +
    +plugin installation +
    +
    +
  6. +
  7. +

    Activate the VR Payment Payment plugin from the Plugin Manager.

    +
  8. +
+
+
+
+

+ 4.3Manual Installation

+
+
+
+
    +
  1. +

    Download the latest release.

    +
  2. +
  3. +

    Extract the ZIP into custom/plugins/

    +
  4. +
  5. +

    Run

    +
    +
    +
    php bin/console plugin:refresh
    +php bin/console plugin:install --activate --clearCache VRPaymentPayment
    +
    +
    +
  6. +
+
+
+
+
+

+ 5Portal Startup Guide

+
+
+
+

Go to VR Payment and create an Account if you do not already have one.

+
+ + + + + +
+
Tip
+
+Please select the proper subscription plan - it should support ecommerce transaction. +
+
+
+

+ 5.1Create the API key:

+
+
+
+
    +
  1. +

    Once your account is activated; please navigate to Account → Application Users

    +
    +
    +application users +
    +
    +
  2. +
  3. +

    Click on Create application user

    +
    +
    +user +
    +
    +
  4. +
  5. +

    Specify a name for this API Key - I will put Test Shop and click on Create application user

    +
    +
    +api key +
    +
    +
  6. +
  7. +

    Your Application User will then be created. Please copy the User ID and Authentication Key as you will need them to link your shop to the portal.

    +
    +
    +token +
    +
    +
  8. +
  9. +

    Finally; you will need to allow certain permission to this Application User so it can communicate with your space. To do so; under Role; navigate to Manage.

    +
    +
    +roles +
    +
    +
  10. +
  11. +

    Click on the + sign next to Space Roles

    +
    +
    +assign role +
    +
    +
  12. +
  13. +

    Please add the role Space Admin and click on Assign Role.

    +
    + + + + + +
    +
    Tip
    +
    +Please note that Roles might be loading for few seconds +
    +
    +
    +
    +loading roles +
    +
    +
  14. +
  15. +

    Finally click on Save Roles; you will need to enter your password to confirm.

    +
    +
    +save role +
    +
    +
  16. +
+
+
+
+

+ 5.2Setup Payment Methods

+
+
+
+

You can now setup the payment methods you would like to have; for testing purpose we will use the Bogus Processor. Please note that if you would like to run production transaction; you can use either our Payfac offering or connect directly to a specific PSP (Worldline; ACI; …​)

+
+
    +
  1. +

    Navigate to Space → Settings and click on Processors

    +
    +
    +payment settings +
    +
    +
  2. +
  3. +

    Click on Configure processor

    +
  4. +
  5. +

    Select Bogus Processor and click on Continue

    +
    +
    +bogus processor +
    +
    +
  6. +
  7. +

    Set up the name you would like to give to your processor - e.g. Test Processor - and click on Create

    +
    +
    +name processor +
    +
    +
  8. +
  9. +

    Select all the Connectors that apply and click on Save

    +
    + + + + + +
    +
    Tip
    +
    +Please note that the connectors seems duplicated but it because one is for Physical Terminal payment and one for Ecommerce Payment +
    +
    +
    +
    +connectors +
    +
    +
  10. +
+
+

Payments methods are now available in the portal

+
+
+
+
+

+ 6Shop Startup Guide

+
+
+
+
    +
  1. +

    API Credentials

    +
    +
      +
    1. +

      Navigate to Shopware AdminSettingsExtensionsVR Payment Payment and click on Save.

      +
    2. +
    3. +

      Enter your Space ID, User ID, and API Key

      +
      +
        +
      1. +

        For the Space id; you can navigate to Space and it will be provided - e.g. Space id = 76231

        +
        +
        +plugin configuration +
        +
        +
      2. +
      +
      +
    4. +
    +
    +
  2. +
  3. +

    Payment Methods

    +
    +

    The payment methods available at checkout are driven by the VR Payment Portal. If you would like to disable a payment method; you will need to disable such payment method from the portal.

    +
    +
    + + + + + +
    +
    Note
    +
    +Please note that as there is a synchronisation between the portal and shop; you can do the same from the shop under Settings → Payment Methods +
    +
    +
    +
      +
    1. +

      Go to your SpaceSettingsPaymentsPayment Methods

      +
      +
      +payment methods +
      +
      +
    2. +
    3. +

      Select the payment method you would like to disable. Click on the slider to disable it → it should become inactive.

      +
      +
      +cc enable +
      +
      +
      +
      +cc disable +
      +
      +
      + + + + + +
      +
      Tip
      +
      +If you would like to disable only a Connector from a Payment Method (e.g. Mastercard for a Credit Card); please go to the Connector section. +
      +
      +
    4. +
    +
    +
  4. +
  5. +

    Options

    +
    +
      +
    1. +

      Space View Id: This field allows you to apply custom styling to the payment form and payment page. The styling is defined in your Space settings in the Portal.

      +
      + + + + + +
      +
      Note
      +
      +Please note that if you do not use the Space View Id; this option should stay empty +
      +
      +
    2. +
    3. +

      Integration: The Integration Options setting determines how the payment form is displayed during the checkout process. The following options are available:

      +
      +
        +
      1. +

        IFrame: Embeds the payment form directly within the WooCommerce checkout page for a seamless experience.

        +
      2. +
      3. +

        Lightbox: Opens a secure popup window for customers to complete their payment without leaving the checkout page.

        +
      4. +
      5. +

        Payment Page: Redirects customers to a dedicated payment page hosted by the payment provider.

        +
      6. +
      +
      +
    4. +
    5. +

      Line Item Consistency: Shopware calculates taxes at the line-item level, which may result in minor discrepancies (typically a few cents) between the order’s total tax and the displayed price. This discrepancy occurs due to rounding differences during individual line-item calculations. If the "Enforce consistency" setting is enabled, the portal will automatically reject orders with such discrepancies. To avoid payment processing issues, we recommend disabling this setting unless strict tax total validation is required.

      +
    6. +
    7. +

      Send Order Confirmation Email: Enable this option to send order confirmation emails directly from Shopware instead of the Portal.

      +
      +
      +order confirmation email +
      +
      +
    8. +
    +
    +
  6. +
+
+
+
+

+ 7Transaction State graph

+
+
+
+

The Payment Process of VR Payment is completely standardized for every payment method you can process. This gives you the ability to simply add +a payment method or processor without changes inside of your Shopware configuration. An overview about the states and the payment processes of VR Payment +can be found in the Payment Documentation.

+
+

In the following section we provide you an overview about how the VR Payment states are mapped into the Shopware State graph for orders and payment states.

+
+
+

+ 7.1State mapping of Shopware orders

+
+
+
+

When the order gets abandoned also Order status goes to "cancel" after approx. 40 minutes. We also change the Payment status, and the Delivery status.

+
+
+

+ 7.1.1General remarks regarding order statuses

+
+
+
+

We recommend that you only change the Order status once the Payment status has reached a final state.

+
+
+
+
+

+ 7.2State mapping of Shopware payment status

+
+
+
+

Below you can find a diagram that shows the state machine of Shopware for payment status including additional information for the state transitions.

+
+
+shopware 6 stage graph order +
+
+
    +
  1. +

    If the transaction is Authorized in VR Payment, the Shopware order payment status is marked as In Progress.

    +
  2. +
  3. +

    If the transaction fails before or during the authorization process, the Shopware order payment status is marked as Failed.

    +
  4. +
  5. +

    If the transaction fails after the authorization, the Shopware order payment status is marked as Cancelled.

    +
  6. +
  7. +

    If the transaction invoice in VR Payment is marked as Paid or Not Applicable, the Shopware order payment status is marked as Paid.

    +
  8. +
+
+
+

+ 7.2.1General remarks regarding payment statuses

+
+
+
+

We recommend that you do not change the payment status manually. If you do so, it may be changed again by the plugin.

+
+
+
+
+

+ 7.3State mapping of Shopware delivery status

+
+
+
+

Below you find a diagram that shows the state machine of Shopware delivery status including additional information for the state transitions.

+
+
+shopware 6 stage graph delivery +
+
+
    +
  1. +

    If the transaction is Confirmed status in VR Payment, the Shopware order delivery status is marked as Hold.

    +
  2. +
  3. +

    If the transaction in VR Payment is marked as Fulfill, the Shopware order delivery status is marked as Open.

    +
  4. +
  5. +

    If the transaction is in Decline, Failed or Voided, the Shopware order delivery status is marked as Cancelled.

    +
  6. +
+
+
+
+
+

+ 8Transaction management

+
+
+
+

You can capture, cancel and refund transactions directly from within the Shopware backend. Please note +if you refund, void or capture transactions inside VR Payment the events will be synchronized into +Shopware. However, there are some limitations (see below).

+
+
+

+ 8.1Complete (capture) an order

+
+
+
+

You have the possibility for your transactions to have the payment only authorized after the order is placed. Inside the connector configuration you have the option, if the payment method supports it, to define whether the payment should be completed immediately or deferred.

+
+

In order to capture a transaction, open the order and click on the Complete button.

+
+ + + + + +
+
Note
+
+When the completion is pending in VR Payment the order will stay in pending state. +
+
+
+capture transaction +
+
+

Deferred payment completion

+
+

Retailers often have the case that they want to authorize transactions only and start the fulfillment process once all items are shippable. This is also possible with VR Payment.

+
+

However, certain processes should be followed. If you have configured payment completion to be deferred you should capture the transaction before you initiate the shipment +as it can always happen that a completion fails. If you want to be sure that you do not ship items for which you have not been paid you should postpone the shipment until +the fulfill state is reached. Initially the transaction will be in the Authorized state in VR Payment and In Progress in Shopware. If you want to start the fulfillment process make sure you initiate the completion process as described above. Once the completion was successful the order will switch into the Fulfill state in VR Payment and into Paid state in Shopware. You can now start the fulfillment process.

+
+
+
+

+ 8.2Void a transaction

+
+
+
+

In order to void a transaction, open the order and click on the Cancel authorization button.

+
+ + + + + +
+
Note
+
+You can only void transactions that are not yet completed. +
+
+
+void transaction +
+
+
+
+

+ 8.3Refund of a transaction

+
+
+
+

You have the possibility to refund already completed transactions. In order to do so, open the captured order. By clicking on the 3 dots (…​) on a line-item, you can refund the line-item partially (if it has a higher quantity than 1), or you can refund the whole line-item. In case the payment method does not support refunds, you will not see the possibility to issue online refunds.

+
+
+refund transaction +
+
+

You can carry out as many individual refunds as you wish until you have reached the total amount of the original order. +The status of the order then automatically switches to complete.

+
+ + + + + +
+
Note
+
+It can take some time until you see the refund in Shopware. Refunds will only be visible once they have been processed successfully. +
+
+
+
+

+ 8.4On hold orders

+
+
+
+

The delivery should not be done whilst the delivery state is Hold. This happens when the transaction in VR Payment +has not reached the fulfill state.

+
+

There are essentially two reasons why this can happen:

+
+
    +
  • +

    The transaction is not completed. In this case you have to complete the transaction as written above.

    +
  • +
  • +

    We are not able to tell if you should fulfill the order. The delivery decision is done automatically. If this does not happen +within the defined time frame, VR Payment will generate a manual task which you should observe and follow the instructions.

    +
  • +
+
+

You can find more information about manual tasks in our Manual Task Documentation.

+
+
+
+

+ 8.5Limitations of the synchronization between VR Payment and Shopware

+
+
+
+

Please note that captures, voids and refunds done in VR Payment are synchronized. However, there are some +limitations. Inside VR Payment you are able to change the unit price and the quantity at once. This can not +be done in the Shopware backend. We therefore recommend that you +perform the refunds always inside the Shopware backend and not inside VR Payment. If a refund +cannot be synchronized it will be sent to the processor but it could be that you do not see it inside +your Shopware backend.

+
+

You can find more information about Refunds in VR Payment in our Refund Documentation.

+
+
+
+

+ 8.6Tokenization

+
+
+
+

In case the payment method supports tokenization you can store the payment details of your customer for future purchases. +In order to use this feature make sure that the One-Click-Payment Mode in your payment method configuration is set to allow or force storage.

+
+ + + + + +
+
Note
+
+Tokenization is not available for guest checkouts. +
+
+
+
+

+ 8.7Key Features

+
+
+
+
    +
  • +

    iFrame Integration: Embed payment forms directly into your checkout.

    +
  • +
  • +

    Refunds & Captures: Trigger full/partial refunds and captures from Shopware or the VR Payment Portal.

    +
  • +
  • +

    Multi-Store Support: Manage configurations across multiple stores.

    +
  • +
  • +

    Automatic Updates: Payment methods sync dynamically via the VR Payment API.

    +
  • +
+
+
+
+

+ 8.8Troubleshooting

+
+
+
+
    +
  • +

    Logs: Check payment logs with:

    +
    +
    +
    tail -f var/log/whitelabelname_payment*.log
    +
    +
    +
  • +
  • +

    Common Issues:

    +
    +
      +
    • +

      Ensure composer update whitelabelname/shopware-6 is run after updates.

      +
    • +
    • +

      Verify API credentials match your VR Payment account.

      +
    • +
    +
    +
  • +
+
+
+
+

+ 8.9FAQs

+
+
+
+

Q: How to be sure that the connection between the portal and the shop is working?

+
+

A: You will need to see if the webhooks were correctly created. To do so; please navigate to your space in the portal, go to Settings → General → Webhook Listeners

+
+
+webhooks +
+
+
+webhook listeners +
+
+

Q: Does this plugin support one-click payments?

+
+

A: Yes, via tokenization in the VR Payment Portal.

+
+

Q: How do I handle PCI compliance?

+
+

A: The plugin uses iFrame integration, reducing PCI requirements to SAQ-A.

+
+

Q: Does the plugin support Apple Pay?

+
+

A: Yes, the plugin support wallet like Apple Pay.

+
+
+
+
+

+ 9Changelog

+
+
+
+

For version-specific updates, see the GitHub Releases.

+
+
+
+

+ 10Contributing

+
+
+
+

Report issues via GitHub Issues.

+
+

Follow the Shopware Plugin Base Guide for development.

+
+
+
+

+ 11Support

+
+
+
+

If you need help, feel free to contact our support.

+
+
+
+
+ +
+
+ + + + + + + + diff --git a/docs/fr/assets/base.css b/docs/fr/assets/base.css new file mode 100644 index 0000000..bb25826 --- /dev/null +++ b/docs/fr/assets/base.css @@ -0,0 +1,692 @@ +* { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} + +*:before, *:after { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} + +html { + font-size: 100%; + font-family: sans-serif; + line-height: 1.15; + -webkit-text-size-adjust: 100%; + -ms-text-size-adjust: 100%; + -ms-overflow-style: scrollbar; + -webkit-tap-highlight-color: transparent; +} + +@-ms-viewport { + width: device-width; +} + +article, aside, dialog, figcaption, figure, footer, header, hgroup, main, nav, section { + display: block; +} + +body { + margin: 0; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; + font-size: 1rem; + font-weight: 300; + line-height: 1.5; + color: #212529; + text-align: left; + background-color: #fff; + padding-right: 0 !important; + position: relative; +} + +html,body { + width: 100%; + height: 100%; +} + +[tabindex="-1"]:focus { + outline: 0 !important; +} + +hr { + box-sizing: content-box; + height: 0; + overflow: visible; +} + +h1, h2, h3, h4, h5, h6 { + margin-top: 0; + margin-bottom: 0.5rem; +} + +h1 { + font-size: 2.5rem; + font-weight: 200; + margin-bottom: 1.875rem; +} + +h2 { + font-size: 1.625rem; + font-weight: 300; + margin-bottom: 1.3rem; +} + +h3 { + font-size: 1.3rem; + font-weight: 300; + margin-top: 1.3rem; +} + +h4 { + font-size: 1.125rem; + font-weight: 400; + margin-top: 1.875rem; + margin-bottom: 1.3rem; +} + +h5 { + font-size: 1rem; + font-weight: bold; + margin-top: 1.875rem; + margin-bottom: 1.3rem; +} + +p { + margin-top: 0; + margin-bottom: 1rem; +} + +abbr[title], abbr[data-original-title] { + text-decoration: underline; + -webkit-text-decoration: underline dotted; + text-decoration: underline dotted; + cursor: help; + border-bottom: 0; +} + +address { + margin-bottom: 1rem; + font-style: normal; + line-height: inherit; +} + +ol, ul, dl { + margin-top: 0; + margin-bottom: 1rem; +} + +ol ol, ul ul, ol ul, ul ol { + margin-bottom: 0; +} + +dt { + font-weight: 700; +} + +dd { + margin-bottom: .5rem; + margin-left: 0; +} + +blockquote { + margin: 0 0 1rem; +} + +dfn { + font-style: italic; +} + +b, strong { + font-weight: bold; +} + +small { + font-size: 80%; +} + +sub, sup { + position: relative; + font-size: 75%; + line-height: 0; + vertical-align: baseline; +} + +sub { + bottom: -.25em; +} + +sup { + top: -.5em; +} + +a { + color: #007bff; + text-decoration: none; + background-color: transparent; + -webkit-text-decoration-skip: objects; +} + +a:hover { + color: #0056b3; + text-decoration: underline; +} + +a:not([href]):not([tabindex]) { + color: inherit; + text-decoration: none; +} + +a:not([href]):not([tabindex]):hover, a:not([href]):not([tabindex]):focus { + color: inherit; + text-decoration: none; +} + +a:not([href]):not([tabindex]):focus { + outline: 0; +} + +pre, code, kbd, samp { + font-family: monospace, monospace; + font-size: 90%; + padding: 2px 4px 2px 4px; + color: #c7254e; + background-color: #f9f2f4; + border-radius: 4px; +} + +pre { + margin-top: 0; + margin-bottom: 1rem; + overflow: auto; + -ms-overflow-style: scrollbar; +} + +figure { + margin: 0 0 1rem; +} + +img { + vertical-align: middle; + border-style: none; +} + +svg:not(:root) { + overflow: hidden; +} + +table { + border-collapse: collapse; + background-color: transparent; +} + +caption { + padding-top: 8px; + padding-bottom: 8px; + color: #a7a7a7; + text-align: left; +} + +th { + text-align: left; +} + +output { + display: inline-block; +} + +summary { + display: list-item; + cursor: pointer; +} + +template { + display: none; +} + +table col[class*="col-"] { + position: static; + float: none; + display: table-column; +} + +table td[class*="col-"],table th[class*="col-"] { + position: static; + float: none; + display: table-cell; +} + +ol.glossary { + counter-reset: glossary-counter; + list-style: none; + padding-left: 40px; +} + +ol.glossary li { + counter-increment: glossary-counter; + position: relative; +} + +ol.glossary li::before { + content: counter(glossary-counter); + position: absolute; + background-color: #73EAA9; + color: #fff; + border-radius: 100px; + width: 24px; + left: -40px; + text-align: center; + font-weight: bold; + line-height: 24px; +} + +.layout-wrapper { + position: relative; + width: 100%; + height: auto; + min-height: 100%; +} + +.layout-title { + padding: 1.875rem 0; + border-bottom: 1px solid #f0f0f0; +} + +.layout-title h1 { + font-size: 3rem; + font-weight: 200; + text-align: center; + margin: 0; +} + +.layout-title h2 { + font-size: 2rem; + font-weight: 200; + text-align: center; + color: #999; + margin-bottom: 0; +} + +.layout-navigation .nav { + padding: 1.875rem 0; + border-bottom: 1px solid #f0f0f0; + text-align: center; + background: #fff; + z-index: 1000; +} + +.layout-navigation .nav > li { + display: inline-block; +} + +.layout-navigation .nav > li > a { + border: 1px solid #007bff; + border-radius: 100px; + padding: 6px 12px; + margin: 0 8px; +} + +.layout-navigation .nav > li > a:hover, .layout-navigation .nav > li > a:active, .layout-navigation .nav > li > a:focus { + border: 1px solid #0056b3; + color: #0056b3; + text-decoration: none; +} + +.layout-content { + position: relative; +} + +.layout-content:before, .layout-content:after { + content: " "; + display: table; +} + +.layout-content:after { + clear: both; +} + +.layout-content .col-right { + width: 25%; + float: right; +} + +.layout-content .col-right-wrapper { + width: 100%; + position: relative; + overflow-x: hidden; + overflow-y: auto; + padding: 2.5rem 2rem 0; +} + +.layout-content .col-body { + width: 75%; + float: left; +} + +.layout-content .col-body:before, .layout-content .col-body:after { + content: " "; + display: table; +} + +.layout-content .col-body:after { + clear: both; +} + +.layout-content .col-body-wrapper { + position: relative; + width: 100%; + padding: 2.5rem 2rem 0; +} + +.nav { + padding-left: 0; + margin-bottom: 0; + list-style: none; + line-height: 2; +} + +.table-of-contents { + padding: 1.25rem 0; +} + +.table-of-contents .nav > li > a { + display: flex; +} + +.table-of-contents .nav > li > a .item-number { + display: none; +} + +.table-of-contents .nav > li > a .item-title { + color: #212529; + overflow: hidden; + text-overflow: ellipsis; + flex-grow: 1; + white-space: nowrap; +} + +.table-of-contents .nav > li > a .item-title:hover { + color: #0056b3; +} + +.table-of-contents .nav > li.extended > a .item-title, .table-of-contents .nav > li.active > a .item-title, .table-of-contents .nav > li.extended > a .item-title:hover, .table-of-contents .nav > li.active > a .item-title:hover { + color: #007bff; +} + +.table-of-contents > .nav > li > .nav { + display: none; + margin-bottom: 0.5rem; +} + +.table-of-contents > .nav > li > .nav > li > a { + padding-left: 1rem; +} + +.table-of-contents > .nav > li > .nav > li > a .item-title { + font-size: 0.875rem; +} + +.table-of-contents > .nav > li > .nav > li > .nav > li > a { + padding-left: 2rem; +} + +.table-of-contents > .nav > li > .nav > li > .nav > li > a .item-title { + font-size: 0.75rem; +} + +.table-of-contents > .nav > li.active > .nav { + display: block; +} + +.chapter { + margin: 0 0 6rem; + font-weight: 300; +} + +.section { + margin-top: 3rem; +} + +.chapter > .chapter-title h1, .chapter > .chapter-title h2, .chapter > .chapter-title h3, .chapter > .chapter-title h4, .chapter > .chapter-title h5, .chapter > .chapter-title h6, .section > .section-title h1, .section > .section-title h2, .section > .section-title h3, .section > .section-title h4, .section > .section-title h5, .section > .section-title h6 { + margin-top: 0; +} + +.chapter > .chapter-title h1 { + border-bottom: 2px solid #eeeeee; + margin-bottom: 1.5rem; + padding-bottom: 0.2em; +} + +.chapter-title .title-number, .section-title .title-number { + display: none; +} + +.paragraph { + line-height: 1.75em; +} + +.paragraph + .paragraph { + margin-top: 1em; +} + +.dlist { + margin-top: 30px; +} + +.dlist dl dt { + float: left; + width: 160px; + clear: left; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.dlist dl dd { + margin-left: 180px; +} + +.ulist { + margin-top: 30px; +} + +.imageblock { + margin: 30px auto; +} + +.imageblock .content img { + max-width: 100%; +} + +.imageblock .title { + padding: 10px 0 0; +} + +.exampleblock, .quoteblock, .literalblock { + background: #f5f4f4; + padding: 20px; + margin: 30px 0; +} + +.exampleblock .title, .quoteblock .title, .literalblock .title { + text-transform: uppercase; + font-size: 0.75em; + font-weight: 400; + color: #979797; + margin-bottom: 10px; +} + +.quoteblock blockquote { + margin: 0; + padding: 0; + border: 0; + font-size: inherit; +} + +.quoteblock blockquote p:last-child, .quoteblock blockquote ul:last-child, .quoteblock blockquote ol:last-child { + margin-bottom: 9px; +} + +.literalblock pre { + border: 0; + padding: 0; + margin: 0; +} + +.listingblock { + margin: 30px 0; +} + +.listingblock pre { + border: 0; + padding: 0; + margin: 0; +} + +.listingblock pre code { + display: block; + padding: 20px; +} + +.admonitionblock { + line-height: 1.8em; + padding: 20px; + margin: 30px 0; +} + +.admonitionblock .icon { + display: none; +} + +.admonitionblock.important { + background: #fce1e1; + border-left: 5px solid #ff6060; +} + +.admonitionblock.note, .admonitionblock.tip { + background: #e0f2fc; + border-left: 5px solid #88d5ff; +} + +.admonitionblock.caution, .admonitionblock.warning { + background: #fdf3d8; + border-left: 5px solid #f1c654; +} + +table.tableblock { + background-color: #fff; + width: 100%; + max-width: 100%; + margin-bottom: 18px; + margin: 30px 0; +} + +table.tableblock > thead > tr > th, table.tableblock > tbody > tr > th, table.tableblock > tfoot > tr > th, table.tableblock > thead > tr > td, table.tableblock > tbody > tr > td, table.tableblock > tfoot > tr > td { + padding: 8px; + line-height: 1.42857143; + vertical-align: top; + border-top: 1px solid #eee; +} + +table.tableblock > thead > tr > th { + vertical-align: bottom; + border-bottom: 2px solid #eee; +} + +table.tableblock > caption + thead > tr:first-child > th, table.tableblock > colgroup + thead > tr:first-child > th, table.tableblock > thead:first-child > tr:first-child > th, table.tableblock > caption + thead > tr:first-child > td, table.tableblock > colgroup + thead > tr:first-child > td, table.tableblock > thead:first-child > tr:first-child > td { + border-top: 0; +} + +table.tableblock > tbody + tbody { + border-top: 2px solid #eee; +} + +table.tableblock .table { + background-color: #fff; +} + +table.tableblock > tbody > tr:nth-of-type(odd) { + background-color: #f7f7f7; +} + +table.tableblock > thead > tr > th p:last-child, table.tableblock > tbody > tr > th p:last-child, table.tableblock > tfoot > tr > th p:last-child, table.tableblock > thead > tr > td p:last-child, table.tableblock > tbody > tr > td p:last-child, table.tableblock > tfoot > tr > td p:last-child { + margin-bottom: 0; +} + +.loaded .table-of-contents .nav .nav { + display: none; +} + +@media (min-width: 1200px) { + .layout-wrapper .layout-title, .layout-wrapper .layout-navigation, .layout-wrapper .layout-content { + max-width: 1200px; + margin-left: auto; + margin-right: auto; + } +} + +@media (max-width: 991px) { + html { + font-size: 90%; + } + + .layout-content .col-right { + display: none; + } + + .layout-content .col-body { + width: 100%; + } +} + +@media print { + body { + color: #000; + font-family: Georgia, "Times New Roman", Times, serif; + } + + a { + color: #000; + } + + h1 { + font-size: 1.6rem; + } + + h2 { + font-size: 1.4rem; + } + + h3 { + font-size: 1.2rem; + } + + h4 { + font-size: 1rem; + } + + h5 { + font-size: 0.9rem; + } + + .layout-title h1 { + font-size: 2rem; + } + + .layout-content .col-right { + display: none; + } + + .layout-content .col-body { + width: 100%; + } + + .chapter { + margin-bottom: 3rem; + } + + .section { + margin-top: 2rem; + } +} diff --git a/docs/fr/assets/base.js b/docs/fr/assets/base.js new file mode 100644 index 0000000..df5c13f --- /dev/null +++ b/docs/fr/assets/base.js @@ -0,0 +1,14 @@ +(function($){ + + hljs.initHighlightingOnLoad(); + + $(document).ready(function(){ + $('.col-right-wrapper').stick_in_parent({ + parent: '.layout-content' + }); + $('body').scrollspy({ + target: '.table-of-contents' + }); + }); + +})(jQuery); diff --git a/docs/fr/assets/highlight.js b/docs/fr/assets/highlight.js new file mode 100644 index 0000000..9a30d5f --- /dev/null +++ b/docs/fr/assets/highlight.js @@ -0,0 +1,6 @@ +/* + Highlight.js 10.0.3 (a4b1bd2d) + License: BSD-3-Clause + Copyright (c) 2006-2020, Ivan Sagalaev +*/ +var hljs=function(){"use strict";function e(n){Object.freeze(n);var t="function"==typeof n;return Object.getOwnPropertyNames(n).forEach((function(r){!n.hasOwnProperty(r)||null===n[r]||"object"!=typeof n[r]&&"function"!=typeof n[r]||t&&("caller"===r||"callee"===r||"arguments"===r)||Object.isFrozen(n[r])||e(n[r])})),n}function n(e){return e.replace(/&/g,"&").replace(//g,">")}function t(e){var n,t={},r=Array.prototype.slice.call(arguments,1);for(n in e)t[n]=e[n];return r.forEach((function(e){for(n in e)t[n]=e[n]})),t}function r(e){return e.nodeName.toLowerCase()}var a=Object.freeze({__proto__:null,escapeHTML:n,inherit:t,nodeStream:function(e){var n=[];return function e(t,a){for(var i=t.firstChild;i;i=i.nextSibling)3===i.nodeType?a+=i.nodeValue.length:1===i.nodeType&&(n.push({event:"start",offset:a,node:i}),a=e(i,a),r(i).match(/br|hr|img|input/)||n.push({event:"stop",offset:a,node:i}));return a}(e,0),n},mergeStreams:function(e,t,a){var i=0,s="",o=[];function l(){return e.length&&t.length?e[0].offset!==t[0].offset?e[0].offset"}function u(e){s+=""}function d(e){("start"===e.event?c:u)(e.node)}for(;e.length||t.length;){var g=l();if(s+=n(a.substring(i,g[0].offset)),i=g[0].offset,g===e){o.reverse().forEach(u);do{d(g.splice(0,1)[0]),g=l()}while(g===e&&g.length&&g[0].offset===i);o.reverse().forEach(c)}else"start"===g[0].event?o.push(g[0].node):o.pop(),d(g.splice(0,1)[0])}return s+n(a.substr(i))}});const i="
",s=e=>!!e.kind;class o{constructor(e,n){this.buffer="",this.classPrefix=n.classPrefix,e.walk(this)}addText(e){this.buffer+=n(e)}openNode(e){if(!s(e))return;let n=e.kind;e.sublanguage||(n=`${this.classPrefix}${n}`),this.span(n)}closeNode(e){s(e)&&(this.buffer+=i)}span(e){this.buffer+=``}value(){return this.buffer}}class l{constructor(){this.rootNode={children:[]},this.stack=[this.rootNode]}get top(){return this.stack[this.stack.length-1]}get root(){return this.rootNode}add(e){this.top.children.push(e)}openNode(e){let n={kind:e,children:[]};this.add(n),this.stack.push(n)}closeNode(){if(this.stack.length>1)return this.stack.pop()}closeAllNodes(){for(;this.closeNode(););}toJSON(){return JSON.stringify(this.rootNode,null,4)}walk(e){return this.constructor._walk(e,this.rootNode)}static _walk(e,n){return"string"==typeof n?e.addText(n):n.children&&(e.openNode(n),n.children.forEach(n=>this._walk(e,n)),e.closeNode(n)),e}static _collapse(e){e.children&&(e.children.every(e=>"string"==typeof e)?(e.text=e.children.join(""),delete e.children):e.children.forEach(e=>{"string"!=typeof e&&l._collapse(e)}))}}class c extends l{constructor(e){super(),this.options=e}addKeyword(e,n){""!==e&&(this.openNode(n),this.addText(e),this.closeNode())}addText(e){""!==e&&this.add(e)}addSublanguage(e,n){let t=e.root;t.kind=n,t.sublanguage=!0,this.add(t)}toHTML(){return new o(this,this.options).value()}finalize(){}}function u(e){return e&&e.source||e}const d="(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)",g={begin:"\\\\[\\s\\S]",relevance:0},h={className:"string",begin:"'",end:"'",illegal:"\\n",contains:[g]},f={className:"string",begin:'"',end:'"',illegal:"\\n",contains:[g]},p={begin:/\b(a|an|the|are|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|they|like|more)\b/},m=function(e,n,r){var a=t({className:"comment",begin:e,end:n,contains:[]},r||{});return a.contains.push(p),a.contains.push({className:"doctag",begin:"(?:TODO|FIXME|NOTE|BUG|XXX):",relevance:0}),a},b=m("//","$"),v=m("/\\*","\\*/"),x=m("#","$");var _=Object.freeze({__proto__:null,IDENT_RE:"[a-zA-Z]\\w*",UNDERSCORE_IDENT_RE:"[a-zA-Z_]\\w*",NUMBER_RE:"\\b\\d+(\\.\\d+)?",C_NUMBER_RE:d,BINARY_NUMBER_RE:"\\b(0b[01]+)",RE_STARTERS_RE:"!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~",BACKSLASH_ESCAPE:g,APOS_STRING_MODE:h,QUOTE_STRING_MODE:f,PHRASAL_WORDS_MODE:p,COMMENT:m,C_LINE_COMMENT_MODE:b,C_BLOCK_COMMENT_MODE:v,HASH_COMMENT_MODE:x,NUMBER_MODE:{className:"number",begin:"\\b\\d+(\\.\\d+)?",relevance:0},C_NUMBER_MODE:{className:"number",begin:d,relevance:0},BINARY_NUMBER_MODE:{className:"number",begin:"\\b(0b[01]+)",relevance:0},CSS_NUMBER_MODE:{className:"number",begin:"\\b\\d+(\\.\\d+)?(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?",relevance:0},REGEXP_MODE:{begin:/(?=\/[^\/\n]*\/)/,contains:[{className:"regexp",begin:/\//,end:/\/[gimuy]*/,illegal:/\n/,contains:[g,{begin:/\[/,end:/\]/,relevance:0,contains:[g]}]}]},TITLE_MODE:{className:"title",begin:"[a-zA-Z]\\w*",relevance:0},UNDERSCORE_TITLE_MODE:{className:"title",begin:"[a-zA-Z_]\\w*",relevance:0},METHOD_GUARD:{begin:"\\.\\s*[a-zA-Z_]\\w*",relevance:0}}),E="of and for in not or if then".split(" ");function R(e,n){return n?+n:(t=e,E.includes(t.toLowerCase())?0:1);var t}const N=n,w=t,{nodeStream:y,mergeStreams:O}=a;return function(n){var r=[],a={},i={},s=[],o=!0,l=/((^(<[^>]+>|\t|)+|(?:\n)))/gm,d="Could not find the language '{}', did you forget to load/include a language module?",g={noHighlightRe:/^(no-?highlight)$/i,languageDetectRe:/\blang(?:uage)?-([\w-]+)\b/i,classPrefix:"hljs-",tabReplace:null,useBR:!1,languages:void 0,__emitter:c};function h(e){return g.noHighlightRe.test(e)}function f(e,n,t,r){var a={code:n,language:e};T("before:highlight",a);var i=a.result?a.result:p(a.language,a.code,t,r);return i.code=a.code,T("after:highlight",i),i}function p(e,n,r,i){var s=n;function l(e,n){var t=v.case_insensitive?n[0].toLowerCase():n[0];return e.keywords.hasOwnProperty(t)&&e.keywords[t]}function c(){null!=_.subLanguage?function(){if(""!==k){var e="string"==typeof _.subLanguage;if(!e||a[_.subLanguage]){var n=e?p(_.subLanguage,k,!0,E[_.subLanguage]):m(k,_.subLanguage.length?_.subLanguage:void 0);_.relevance>0&&(T+=n.relevance),e&&(E[_.subLanguage]=n.top),w.addSublanguage(n.emitter,n.language)}else w.addText(k)}}():function(){var e,n,t,r;if(_.keywords){for(n=0,_.lexemesRe.lastIndex=0,t=_.lexemesRe.exec(k),r="";t;){r+=k.substring(n,t.index);var a=null;(e=l(_,t))?(w.addText(r),r="",T+=e[1],a=e[0],w.addKeyword(t[0],a)):r+=t[0],n=_.lexemesRe.lastIndex,t=_.lexemesRe.exec(k)}r+=k.substr(n),w.addText(r)}else w.addText(k)}(),k=""}function h(e){e.className&&w.openNode(e.className),_=Object.create(e,{parent:{value:_}})}var f={};function b(n,t){var a,i=t&&t[0];if(k+=n,null==i)return c(),0;if("begin"==f.type&&"end"==t.type&&f.index==t.index&&""===i){if(k+=s.slice(t.index,t.index+1),!o)throw(a=Error("0 width match regex")).languageName=e,a.badRule=f.rule,a;return 1}if(f=t,"begin"===t.type)return function(e){var n=e[0],t=e.rule;return t.__onBegin&&(t.__onBegin(e)||{}).ignoreMatch?function(e){return 0===_.matcher.regexIndex?(k+=e[0],1):(B=!0,0)}(n):(t&&t.endSameAsBegin&&(t.endRe=RegExp(n.replace(/[-\/\\^$*+?.()|[\]{}]/g,"\\$&"),"m")),t.skip?k+=n:(t.excludeBegin&&(k+=n),c(),t.returnBegin||t.excludeBegin||(k=n)),h(t),t.returnBegin?0:n.length)}(t);if("illegal"===t.type&&!r)throw(a=Error('Illegal lexeme "'+i+'" for mode "'+(_.className||"")+'"')).mode=_,a;if("end"===t.type){var l=function(e){var n=e[0],t=s.substr(e.index),r=function e(n,t){if(function(e,n){var t=e&&e.exec(n);return t&&0===t.index}(n.endRe,t)){for(;n.endsParent&&n.parent;)n=n.parent;return n}if(n.endsWithParent)return e(n.parent,t)}(_,t);if(r){var a=_;a.skip?k+=n:(a.returnEnd||a.excludeEnd||(k+=n),c(),a.excludeEnd&&(k=n));do{_.className&&w.closeNode(),_.skip||_.subLanguage||(T+=_.relevance),_=_.parent}while(_!==r.parent);return r.starts&&(r.endSameAsBegin&&(r.starts.endRe=r.endRe),h(r.starts)),a.returnEnd?0:n.length}}(t);if(null!=l)return l}if("illegal"===t.type&&""===i)return 1;if(A>1e5&&A>3*t.index)throw Error("potential infinite loop, way more iterations than matches");return k+=i,i.length}var v=M(e);if(!v)throw console.error(d.replace("{}",e)),Error('Unknown language: "'+e+'"');!function(e){function n(n,t){return RegExp(u(n),"m"+(e.case_insensitive?"i":"")+(t?"g":""))}class r{constructor(){this.matchIndexes={},this.regexes=[],this.matchAt=1,this.position=0}addRule(e,n){n.position=this.position++,this.matchIndexes[this.matchAt]=n,this.regexes.push([n,e]),this.matchAt+=function(e){return RegExp(e.toString()+"|").exec("").length-1}(e)+1}compile(){0===this.regexes.length&&(this.exec=()=>null);let e=this.regexes.map(e=>e[1]);this.matcherRe=n(function(e,n){for(var t=/\[(?:[^\\\]]|\\.)*\]|\(\??|\\([1-9][0-9]*)|\\./,r=0,a="",i=0;i0&&(a+="|"),a+="(";o.length>0;){var l=t.exec(o);if(null==l){a+=o;break}a+=o.substring(0,l.index),o=o.substring(l.index+l[0].length),"\\"==l[0][0]&&l[1]?a+="\\"+(+l[1]+s):(a+=l[0],"("==l[0]&&r++)}a+=")"}return a}(e),!0),this.lastIndex=0}exec(e){this.matcherRe.lastIndex=this.lastIndex;let n=this.matcherRe.exec(e);if(!n)return null;let t=n.findIndex((e,n)=>n>0&&null!=e),r=this.matchIndexes[t];return Object.assign(n,r)}}class a{constructor(){this.rules=[],this.multiRegexes=[],this.count=0,this.lastIndex=0,this.regexIndex=0}getMatcher(e){if(this.multiRegexes[e])return this.multiRegexes[e];let n=new r;return this.rules.slice(e).forEach(([e,t])=>n.addRule(e,t)),n.compile(),this.multiRegexes[e]=n,n}considerAll(){this.regexIndex=0}addRule(e,n){this.rules.push([e,n]),"begin"===n.type&&this.count++}exec(e){let n=this.getMatcher(this.regexIndex);n.lastIndex=this.lastIndex;let t=n.exec(e);return t&&(this.regexIndex+=t.position+1,this.regexIndex===this.count&&(this.regexIndex=0)),t}}function i(e){let n=e.input[e.index-1],t=e.input[e.index+e[0].length];if("."===n||"."===t)return{ignoreMatch:!0}}if(e.contains&&e.contains.includes("self"))throw Error("ERR: contains `self` is not supported at the top-level of a language. See documentation.");!function r(s,o){s.compiled||(s.compiled=!0,s.__onBegin=null,s.keywords=s.keywords||s.beginKeywords,s.keywords&&(s.keywords=function(e,n){var t={};return"string"==typeof e?r("keyword",e):Object.keys(e).forEach((function(n){r(n,e[n])})),t;function r(e,r){n&&(r=r.toLowerCase()),r.split(" ").forEach((function(n){var r=n.split("|");t[r[0]]=[e,R(r[0],r[1])]}))}}(s.keywords,e.case_insensitive)),s.lexemesRe=n(s.lexemes||/\w+/,!0),o&&(s.beginKeywords&&(s.begin="\\b("+s.beginKeywords.split(" ").join("|")+")(?=\\b|\\s)",s.__onBegin=i),s.begin||(s.begin=/\B|\b/),s.beginRe=n(s.begin),s.endSameAsBegin&&(s.end=s.begin),s.end||s.endsWithParent||(s.end=/\B|\b/),s.end&&(s.endRe=n(s.end)),s.terminator_end=u(s.end)||"",s.endsWithParent&&o.terminator_end&&(s.terminator_end+=(s.end?"|":"")+o.terminator_end)),s.illegal&&(s.illegalRe=n(s.illegal)),null==s.relevance&&(s.relevance=1),s.contains||(s.contains=[]),s.contains=[].concat(...s.contains.map((function(e){return function(e){return e.variants&&!e.cached_variants&&(e.cached_variants=e.variants.map((function(n){return t(e,{variants:null},n)}))),e.cached_variants?e.cached_variants:function e(n){return!!n&&(n.endsWithParent||e(n.starts))}(e)?t(e,{starts:e.starts?t(e.starts):null}):Object.isFrozen(e)?t(e):e}("self"===e?s:e)}))),s.contains.forEach((function(e){r(e,s)})),s.starts&&r(s.starts,o),s.matcher=function(e){let n=new a;return e.contains.forEach(e=>n.addRule(e.begin,{rule:e,type:"begin"})),e.terminator_end&&n.addRule(e.terminator_end,{type:"end"}),e.illegal&&n.addRule(e.illegal,{type:"illegal"}),n}(s))}(e)}(v);var x,_=i||v,E={},w=new g.__emitter(g);!function(){for(var e=[],n=_;n!==v;n=n.parent)n.className&&e.unshift(n.className);e.forEach(e=>w.openNode(e))}();var y,O,k="",T=0,L=0,A=0,B=!1;try{for(_.matcher.considerAll();A++,B?B=!1:(_.matcher.lastIndex=L,_.matcher.considerAll()),y=_.matcher.exec(s);)O=b(s.substring(L,y.index),y),L=y.index+O;return b(s.substr(L)),w.closeAllNodes(),w.finalize(),x=w.toHTML(),{relevance:T,value:x,language:e,illegal:!1,emitter:w,top:_}}catch(n){if(n.message&&n.message.includes("Illegal"))return{illegal:!0,illegalBy:{msg:n.message,context:s.slice(L-100,L+100),mode:n.mode},sofar:x,relevance:0,value:N(s),emitter:w};if(o)return{relevance:0,value:N(s),emitter:w,language:e,top:_,errorRaised:n};throw n}}function m(e,n){n=n||g.languages||Object.keys(a);var t=function(e){const n={relevance:0,emitter:new g.__emitter(g),value:N(e),illegal:!1,top:E};return n.emitter.addText(e),n}(e),r=t;return n.filter(M).filter(k).forEach((function(n){var a=p(n,e,!1);a.language=n,a.relevance>r.relevance&&(r=a),a.relevance>t.relevance&&(r=t,t=a)})),r.language&&(t.second_best=r),t}function b(e){return g.tabReplace||g.useBR?e.replace(l,(function(e,n){return g.useBR&&"\n"===e?"
":g.tabReplace?n.replace(/\t/g,g.tabReplace):""})):e}function v(e){var n,t,r,a,s,o=function(e){var n,t=e.className+" ";if(t+=e.parentNode?e.parentNode.className:"",n=g.languageDetectRe.exec(t)){var r=M(n[1]);return r||(console.warn(d.replace("{}",n[1])),console.warn("Falling back to no-highlight mode for this block.",e)),r?n[1]:"no-highlight"}return t.split(/\s+/).find(e=>h(e)||M(e))}(e);h(o)||(T("before:highlightBlock",{block:e,language:o}),g.useBR?(n=document.createElement("div")).innerHTML=e.innerHTML.replace(/\n/g,"").replace(//g,"\n"):n=e,s=n.textContent,r=o?f(o,s,!0):m(s),(t=y(n)).length&&((a=document.createElement("div")).innerHTML=r.value,r.value=O(t,y(a),s)),r.value=b(r.value),T("after:highlightBlock",{block:e,result:r}),e.innerHTML=r.value,e.className=function(e,n,t){var r=n?i[n]:t,a=[e.trim()];return e.match(/\bhljs\b/)||a.push("hljs"),e.includes(r)||a.push(r),a.join(" ").trim()}(e.className,o,r.language),e.result={language:r.language,re:r.relevance},r.second_best&&(e.second_best={language:r.second_best.language,re:r.second_best.relevance}))}function x(){if(!x.called){x.called=!0;var e=document.querySelectorAll("pre code");r.forEach.call(e,v)}}const E={disableAutodetect:!0,name:"Plain text"};function M(e){return e=(e||"").toLowerCase(),a[e]||a[i[e]]}function k(e){var n=M(e);return n&&!n.disableAutodetect}function T(e,n){var t=e;s.forEach((function(e){e[t]&&e[t](n)}))}Object.assign(n,{highlight:f,highlightAuto:m,fixMarkup:b,highlightBlock:v,configure:function(e){g=w(g,e)},initHighlighting:x,initHighlightingOnLoad:function(){window.addEventListener("DOMContentLoaded",x,!1)},registerLanguage:function(e,t){var r;try{r=t(n)}catch(n){if(console.error("Language definition for '{}' could not be registered.".replace("{}",e)),!o)throw n;console.error(n),r=E}r.name||(r.name=e),a[e]=r,r.rawDefinition=t.bind(null,n),r.aliases&&r.aliases.forEach((function(n){i[n]=e}))},listLanguages:function(){return Object.keys(a)},getLanguage:M,requireLanguage:function(e){var n=M(e);if(n)return n;throw Error("The '{}' language is required, but not loaded.".replace("{}",e))},autoDetection:k,inherit:w,addPlugin:function(e,n){s.push(e)}}),n.debugMode=function(){o=!1},n.safeMode=function(){o=!0},n.versionString="10.0.3";for(const n in _)"object"==typeof _[n]&&e(_[n]);return Object.assign(n,_),n}({})}();"object"==typeof exports&&"undefined"!=typeof module&&(module.exports=hljs);hljs.registerLanguage("css",function(){"use strict";return function(e){var n={begin:/(?:[A-Z\_\.\-]+|--[a-zA-Z0-9_-]+)\s*:/,returnBegin:!0,end:";",endsWithParent:!0,contains:[{className:"attribute",begin:/\S/,end:":",excludeEnd:!0,starts:{endsWithParent:!0,excludeEnd:!0,contains:[{begin:/[\w-]+\(/,returnBegin:!0,contains:[{className:"built_in",begin:/[\w-]+/},{begin:/\(/,end:/\)/,contains:[e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,e.CSS_NUMBER_MODE]}]},e.CSS_NUMBER_MODE,e.QUOTE_STRING_MODE,e.APOS_STRING_MODE,e.C_BLOCK_COMMENT_MODE,{className:"number",begin:"#[0-9A-Fa-f]+"},{className:"meta",begin:"!important"}]}}]};return{name:"CSS",case_insensitive:!0,illegal:/[=\/|'\$]/,contains:[e.C_BLOCK_COMMENT_MODE,{className:"selector-id",begin:/#[A-Za-z0-9_-]+/},{className:"selector-class",begin:/\.[A-Za-z0-9_-]+/},{className:"selector-attr",begin:/\[/,end:/\]/,illegal:"$",contains:[e.APOS_STRING_MODE,e.QUOTE_STRING_MODE]},{className:"selector-pseudo",begin:/:(:)?[a-zA-Z0-9\_\-\+\(\)"'.]+/},{begin:"@(page|font-face)",lexemes:"@[a-z-]+",keywords:"@page @font-face"},{begin:"@",end:"[{;]",illegal:/:/,returnBegin:!0,contains:[{className:"keyword",begin:/@\-?\w[\w]*(\-\w+)*/},{begin:/\s/,endsWithParent:!0,excludeEnd:!0,relevance:0,keywords:"and or not only",contains:[{begin:/[a-z-]+:/,className:"attribute"},e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,e.CSS_NUMBER_MODE]}]},{className:"selector-tag",begin:"[a-zA-Z-][a-zA-Z0-9_-]*",relevance:0},{begin:"{",end:"}",illegal:/\S/,contains:[e.C_BLOCK_COMMENT_MODE,n]}]}}}());hljs.registerLanguage("bash",function(){"use strict";return function(e){const s={};Object.assign(s,{className:"variable",variants:[{begin:/\$[\w\d#@][\w\d_]*/},{begin:/\$\{/,end:/\}/,contains:[{begin:/:-/,contains:[s]}]}]});const n={className:"subst",begin:/\$\(/,end:/\)/,contains:[e.BACKSLASH_ESCAPE]},t={className:"string",begin:/"/,end:/"/,contains:[e.BACKSLASH_ESCAPE,s,n]};n.contains.push(t);const a={begin:/\$\(\(/,end:/\)\)/,contains:[{begin:/\d+#[0-9a-f]+/,className:"number"},e.NUMBER_MODE,s]};return{name:"Bash",aliases:["sh","zsh"],lexemes:/\b-?[a-z\._]+\b/,keywords:{keyword:"if then else elif fi for while in do done case esac function",literal:"true false",built_in:"break cd continue eval exec exit export getopts hash pwd readonly return shift test times trap umask unset alias bind builtin caller command declare echo enable help let local logout mapfile printf read readarray source type typeset ulimit unalias set shopt autoload bg bindkey bye cap chdir clone comparguments compcall compctl compdescribe compfiles compgroups compquote comptags comptry compvalues dirs disable disown echotc echoti emulate fc fg float functions getcap getln history integer jobs kill limit log noglob popd print pushd pushln rehash sched setcap setopt stat suspend ttyctl unfunction unhash unlimit unsetopt vared wait whence where which zcompile zformat zftp zle zmodload zparseopts zprof zpty zregexparse zsocket zstyle ztcp",_:"-ne -eq -lt -gt -f -d -e -s -l -a"},contains:[{className:"meta",begin:/^#![^\n]+sh\s*$/,relevance:10},{className:"function",begin:/\w[\w\d_]*\s*\(\s*\)\s*\{/,returnBegin:!0,contains:[e.inherit(e.TITLE_MODE,{begin:/\w[\w\d_]*/})],relevance:0},a,e.HASH_COMMENT_MODE,t,{className:"",begin:/\\"/},{className:"string",begin:/'/,end:/'/},s]}}}());hljs.registerLanguage("xml",function(){"use strict";return function(e){var n={className:"symbol",begin:"&[a-z]+;|&#[0-9]+;|&#x[a-f0-9]+;"},a={begin:"\\s",contains:[{className:"meta-keyword",begin:"#?[a-z_][a-z1-9_-]+",illegal:"\\n"}]},s=e.inherit(a,{begin:"\\(",end:"\\)"}),t=e.inherit(e.APOS_STRING_MODE,{className:"meta-string"}),i=e.inherit(e.QUOTE_STRING_MODE,{className:"meta-string"}),c={endsWithParent:!0,illegal:/`]+/}]}]}]};return{name:"HTML, XML",aliases:["html","xhtml","rss","atom","xjb","xsd","xsl","plist","wsf","svg"],case_insensitive:!0,contains:[{className:"meta",begin:"",relevance:10,contains:[a,i,t,s,{begin:"\\[",end:"\\]",contains:[{className:"meta",begin:"",contains:[a,s,i,t]}]}]},e.COMMENT("\x3c!--","--\x3e",{relevance:10}),{begin:"<\\!\\[CDATA\\[",end:"\\]\\]>",relevance:10},n,{className:"meta",begin:/<\?xml/,end:/\?>/,relevance:10},{className:"tag",begin:")",end:">",keywords:{name:"style"},contains:[c],starts:{end:"",returnEnd:!0,subLanguage:["css","xml"]}},{className:"tag",begin:")",end:">",keywords:{name:"script"},contains:[c],starts:{end:"<\/script>",returnEnd:!0,subLanguage:["javascript","handlebars","xml"]}},{className:"tag",begin:"",contains:[{className:"name",begin:/[^\/><\s]+/,relevance:0},c]}]}}}());hljs.registerLanguage("php",function(){"use strict";return function(e){var r={begin:"\\$+[a-zA-Z_-ÿ][a-zA-Z0-9_-ÿ]*"},t={className:"meta",variants:[{begin:/<\?php/,relevance:10},{begin:/<\?[=]?/},{begin:/\?>/}]},a={className:"string",contains:[e.BACKSLASH_ESCAPE,t],variants:[{begin:'b"',end:'"'},{begin:"b'",end:"'"},e.inherit(e.APOS_STRING_MODE,{illegal:null}),e.inherit(e.QUOTE_STRING_MODE,{illegal:null})]},n={variants:[e.BINARY_NUMBER_MODE,e.C_NUMBER_MODE]},i={keyword:"__CLASS__ __DIR__ __FILE__ __FUNCTION__ __LINE__ __METHOD__ __NAMESPACE__ __TRAIT__ die echo exit include include_once print require require_once array abstract and as binary bool boolean break callable case catch class clone const continue declare default do double else elseif empty enddeclare endfor endforeach endif endswitch endwhile eval extends final finally float for foreach from global goto if implements instanceof insteadof int integer interface isset iterable list new object or private protected public real return string switch throw trait try unset use var void while xor yield",literal:"false null true",built_in:"Error|0 AppendIterator ArgumentCountError ArithmeticError ArrayIterator ArrayObject AssertionError BadFunctionCallException BadMethodCallException CachingIterator CallbackFilterIterator CompileError Countable DirectoryIterator DivisionByZeroError DomainException EmptyIterator ErrorException Exception FilesystemIterator FilterIterator GlobIterator InfiniteIterator InvalidArgumentException IteratorIterator LengthException LimitIterator LogicException MultipleIterator NoRewindIterator OutOfBoundsException OutOfRangeException OuterIterator OverflowException ParentIterator ParseError RangeException RecursiveArrayIterator RecursiveCachingIterator RecursiveCallbackFilterIterator RecursiveDirectoryIterator RecursiveFilterIterator RecursiveIterator RecursiveIteratorIterator RecursiveRegexIterator RecursiveTreeIterator RegexIterator RuntimeException SeekableIterator SplDoublyLinkedList SplFileInfo SplFileObject SplFixedArray SplHeap SplMaxHeap SplMinHeap SplObjectStorage SplObserver SplObserver SplPriorityQueue SplQueue SplStack SplSubject SplSubject SplTempFileObject TypeError UnderflowException UnexpectedValueException ArrayAccess Closure Generator Iterator IteratorAggregate Serializable Throwable Traversable WeakReference Directory __PHP_Incomplete_Class parent php_user_filter self static stdClass"};return{aliases:["php","php3","php4","php5","php6","php7"],case_insensitive:!0,keywords:i,contains:[e.HASH_COMMENT_MODE,e.COMMENT("//","$",{contains:[t]}),e.COMMENT("/\\*","\\*/",{contains:[{className:"doctag",begin:"@[A-Za-z]+"}]}),e.COMMENT("__halt_compiler.+?;",!1,{endsWithParent:!0,keywords:"__halt_compiler",lexemes:e.UNDERSCORE_IDENT_RE}),{className:"string",begin:/<<<['"]?\w+['"]?$/,end:/^\w+;?$/,contains:[e.BACKSLASH_ESCAPE,{className:"subst",variants:[{begin:/\$\w+/},{begin:/\{\$/,end:/\}/}]}]},t,{className:"keyword",begin:/\$this\b/},r,{begin:/(::|->)+[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/},{className:"function",beginKeywords:"fn function",end:/[;{]/,excludeEnd:!0,illegal:"[$%\\[]",contains:[e.UNDERSCORE_TITLE_MODE,{className:"params",begin:"\\(",end:"\\)",excludeBegin:!0,excludeEnd:!0,keywords:i,contains:["self",r,e.C_BLOCK_COMMENT_MODE,a,n]}]},{className:"class",beginKeywords:"class interface",end:"{",excludeEnd:!0,illegal:/[:\(\$"]/,contains:[{beginKeywords:"extends implements"},e.UNDERSCORE_TITLE_MODE]},{beginKeywords:"namespace",end:";",illegal:/[\.']/,contains:[e.UNDERSCORE_TITLE_MODE]},{beginKeywords:"use",end:";",contains:[e.UNDERSCORE_TITLE_MODE]},{begin:"=>"},a,n]}}}());hljs.registerLanguage("php-template",function(){"use strict";return function(n){return{name:"PHP template",subLanguage:"xml",contains:[{begin:/<\?(php|=)?/,end:/\?>/,subLanguage:"php",contains:[{begin:"/\\*",end:"\\*/",skip:!0},{begin:'b"',end:'"',skip:!0},{begin:"b'",end:"'",skip:!0},n.inherit(n.APOS_STRING_MODE,{illegal:null,className:null,contains:null,skip:!0}),n.inherit(n.QUOTE_STRING_MODE,{illegal:null,className:null,contains:null,skip:!0})]}]}}}());hljs.registerLanguage("sql",function(){"use strict";return function(e){var t=e.COMMENT("--","$");return{name:"SQL",case_insensitive:!0,illegal:/[<>{}*]/,contains:[{beginKeywords:"begin end start commit rollback savepoint lock alter create drop rename call delete do handler insert load replace select truncate update set show pragma grant merge describe use explain help declare prepare execute deallocate release unlock purge reset change stop analyze cache flush optimize repair kill install uninstall checksum restore check backup revoke comment values with",end:/;/,endsWithParent:!0,lexemes:/[\w\.]+/,keywords:{keyword:"as abort abs absolute acc acce accep accept access accessed accessible account acos action activate add addtime admin administer advanced advise aes_decrypt aes_encrypt after agent aggregate ali alia alias all allocate allow alter always analyze ancillary and anti any anydata anydataset anyschema anytype apply archive archived archivelog are as asc ascii asin assembly assertion associate asynchronous at atan atn2 attr attri attrib attribu attribut attribute attributes audit authenticated authentication authid authors auto autoallocate autodblink autoextend automatic availability avg backup badfile basicfile before begin beginning benchmark between bfile bfile_base big bigfile bin binary_double binary_float binlog bit_and bit_count bit_length bit_or bit_xor bitmap blob_base block blocksize body both bound bucket buffer_cache buffer_pool build bulk by byte byteordermark bytes cache caching call calling cancel capacity cascade cascaded case cast catalog category ceil ceiling chain change changed char_base char_length character_length characters characterset charindex charset charsetform charsetid check checksum checksum_agg child choose chr chunk class cleanup clear client clob clob_base clone close cluster_id cluster_probability cluster_set clustering coalesce coercibility col collate collation collect colu colum column column_value columns columns_updated comment commit compact compatibility compiled complete composite_limit compound compress compute concat concat_ws concurrent confirm conn connec connect connect_by_iscycle connect_by_isleaf connect_by_root connect_time connection consider consistent constant constraint constraints constructor container content contents context contributors controlfile conv convert convert_tz corr corr_k corr_s corresponding corruption cos cost count count_big counted covar_pop covar_samp cpu_per_call cpu_per_session crc32 create creation critical cross cube cume_dist curdate current current_date current_time current_timestamp current_user cursor curtime customdatum cycle data database databases datafile datafiles datalength date_add date_cache date_format date_sub dateadd datediff datefromparts datename datepart datetime2fromparts day day_to_second dayname dayofmonth dayofweek dayofyear days db_role_change dbtimezone ddl deallocate declare decode decompose decrement decrypt deduplicate def defa defau defaul default defaults deferred defi defin define degrees delayed delegate delete delete_all delimited demand dense_rank depth dequeue des_decrypt des_encrypt des_key_file desc descr descri describ describe descriptor deterministic diagnostics difference dimension direct_load directory disable disable_all disallow disassociate discardfile disconnect diskgroup distinct distinctrow distribute distributed div do document domain dotnet double downgrade drop dumpfile duplicate duration each edition editionable editions element ellipsis else elsif elt empty enable enable_all enclosed encode encoding encrypt end end-exec endian enforced engine engines enqueue enterprise entityescaping eomonth error errors escaped evalname evaluate event eventdata events except exception exceptions exchange exclude excluding execu execut execute exempt exists exit exp expire explain explode export export_set extended extent external external_1 external_2 externally extract failed failed_login_attempts failover failure far fast feature_set feature_value fetch field fields file file_name_convert filesystem_like_logging final finish first first_value fixed flash_cache flashback floor flush following follows for forall force foreign form forma format found found_rows freelist freelists freepools fresh from from_base64 from_days ftp full function general generated get get_format get_lock getdate getutcdate global global_name globally go goto grant grants greatest group group_concat group_id grouping grouping_id groups gtid_subtract guarantee guard handler hash hashkeys having hea head headi headin heading heap help hex hierarchy high high_priority hosts hour hours http id ident_current ident_incr ident_seed identified identity idle_time if ifnull ignore iif ilike ilm immediate import in include including increment index indexes indexing indextype indicator indices inet6_aton inet6_ntoa inet_aton inet_ntoa infile initial initialized initially initrans inmemory inner innodb input insert install instance instantiable instr interface interleaved intersect into invalidate invisible is is_free_lock is_ipv4 is_ipv4_compat is_not is_not_null is_used_lock isdate isnull isolation iterate java join json json_exists keep keep_duplicates key keys kill language large last last_day last_insert_id last_value lateral lax lcase lead leading least leaves left len lenght length less level levels library like like2 like4 likec limit lines link list listagg little ln load load_file lob lobs local localtime localtimestamp locate locator lock locked log log10 log2 logfile logfiles logging logical logical_reads_per_call logoff logon logs long loop low low_priority lower lpad lrtrim ltrim main make_set makedate maketime managed management manual map mapping mask master master_pos_wait match matched materialized max maxextents maximize maxinstances maxlen maxlogfiles maxloghistory maxlogmembers maxsize maxtrans md5 measures median medium member memcompress memory merge microsecond mid migration min minextents minimum mining minus minute minutes minvalue missing mod mode model modification modify module monitoring month months mount move movement multiset mutex name name_const names nan national native natural nav nchar nclob nested never new newline next nextval no no_write_to_binlog noarchivelog noaudit nobadfile nocheck nocompress nocopy nocycle nodelay nodiscardfile noentityescaping noguarantee nokeep nologfile nomapping nomaxvalue nominimize nominvalue nomonitoring none noneditionable nonschema noorder nopr nopro noprom nopromp noprompt norely noresetlogs noreverse normal norowdependencies noschemacheck noswitch not nothing notice notnull notrim novalidate now nowait nth_value nullif nulls num numb numbe nvarchar nvarchar2 object ocicoll ocidate ocidatetime ociduration ociinterval ociloblocator ocinumber ociref ocirefcursor ocirowid ocistring ocitype oct octet_length of off offline offset oid oidindex old on online only opaque open operations operator optimal optimize option optionally or oracle oracle_date oradata ord ordaudio orddicom orddoc order ordimage ordinality ordvideo organization orlany orlvary out outer outfile outline output over overflow overriding package pad parallel parallel_enable parameters parent parse partial partition partitions pascal passing password password_grace_time password_lock_time password_reuse_max password_reuse_time password_verify_function patch path patindex pctincrease pctthreshold pctused pctversion percent percent_rank percentile_cont percentile_disc performance period period_add period_diff permanent physical pi pipe pipelined pivot pluggable plugin policy position post_transaction pow power pragma prebuilt precedes preceding precision prediction prediction_cost prediction_details prediction_probability prediction_set prepare present preserve prior priority private private_sga privileges procedural procedure procedure_analyze processlist profiles project prompt protection public publishingservername purge quarter query quick quiesce quota quotename radians raise rand range rank raw read reads readsize rebuild record records recover recovery recursive recycle redo reduced ref reference referenced references referencing refresh regexp_like register regr_avgx regr_avgy regr_count regr_intercept regr_r2 regr_slope regr_sxx regr_sxy reject rekey relational relative relaylog release release_lock relies_on relocate rely rem remainder rename repair repeat replace replicate replication required reset resetlogs resize resource respect restore restricted result result_cache resumable resume retention return returning returns reuse reverse revoke right rlike role roles rollback rolling rollup round row row_count rowdependencies rowid rownum rows rtrim rules safe salt sample save savepoint sb1 sb2 sb4 scan schema schemacheck scn scope scroll sdo_georaster sdo_topo_geometry search sec_to_time second seconds section securefile security seed segment select self semi sequence sequential serializable server servererror session session_user sessions_per_user set sets settings sha sha1 sha2 share shared shared_pool short show shrink shutdown si_averagecolor si_colorhistogram si_featurelist si_positionalcolor si_stillimage si_texture siblings sid sign sin size size_t sizes skip slave sleep smalldatetimefromparts smallfile snapshot some soname sort soundex source space sparse spfile split sql sql_big_result sql_buffer_result sql_cache sql_calc_found_rows sql_small_result sql_variant_property sqlcode sqldata sqlerror sqlname sqlstate sqrt square standalone standby start starting startup statement static statistics stats_binomial_test stats_crosstab stats_ks_test stats_mode stats_mw_test stats_one_way_anova stats_t_test_ stats_t_test_indep stats_t_test_one stats_t_test_paired stats_wsr_test status std stddev stddev_pop stddev_samp stdev stop storage store stored str str_to_date straight_join strcmp strict string struct stuff style subdate subpartition subpartitions substitutable substr substring subtime subtring_index subtype success sum suspend switch switchoffset switchover sync synchronous synonym sys sys_xmlagg sysasm sysaux sysdate sysdatetimeoffset sysdba sysoper system system_user sysutcdatetime table tables tablespace tablesample tan tdo template temporary terminated tertiary_weights test than then thread through tier ties time time_format time_zone timediff timefromparts timeout timestamp timestampadd timestampdiff timezone_abbr timezone_minute timezone_region to to_base64 to_date to_days to_seconds todatetimeoffset trace tracking transaction transactional translate translation treat trigger trigger_nestlevel triggers trim truncate try_cast try_convert try_parse type ub1 ub2 ub4 ucase unarchived unbounded uncompress under undo unhex unicode uniform uninstall union unique unix_timestamp unknown unlimited unlock unnest unpivot unrecoverable unsafe unsigned until untrusted unusable unused update updated upgrade upped upper upsert url urowid usable usage use use_stored_outlines user user_data user_resources users using utc_date utc_timestamp uuid uuid_short validate validate_password_strength validation valist value values var var_samp varcharc vari varia variab variabl variable variables variance varp varraw varrawc varray verify version versions view virtual visible void wait wallet warning warnings week weekday weekofyear wellformed when whene whenev wheneve whenever where while whitespace window with within without work wrapped xdb xml xmlagg xmlattributes xmlcast xmlcolattval xmlelement xmlexists xmlforest xmlindex xmlnamespaces xmlpi xmlquery xmlroot xmlschema xmlserialize xmltable xmltype xor year year_to_month years yearweek",literal:"true false null unknown",built_in:"array bigint binary bit blob bool boolean char character date dec decimal float int int8 integer interval number numeric real record serial serial8 smallint text time timestamp tinyint varchar varchar2 varying void"},contains:[{className:"string",begin:"'",end:"'",contains:[{begin:"''"}]},{className:"string",begin:'"',end:'"',contains:[{begin:'""'}]},{className:"string",begin:"`",end:"`"},e.C_NUMBER_MODE,e.C_BLOCK_COMMENT_MODE,t,e.HASH_COMMENT_MODE]},e.C_BLOCK_COMMENT_MODE,t,e.HASH_COMMENT_MODE]}}}());hljs.registerLanguage("python",function(){"use strict";return function(e){var n={keyword:"and elif is global as in if from raise for except finally print import pass return exec else break not with class assert yield try while continue del or def lambda async await nonlocal|10",built_in:"Ellipsis NotImplemented",literal:"False None True"},a={className:"meta",begin:/^(>>>|\.\.\.) /},i={className:"subst",begin:/\{/,end:/\}/,keywords:n,illegal:/#/},s={begin:/\{\{/,relevance:0},r={className:"string",contains:[e.BACKSLASH_ESCAPE],variants:[{begin:/(u|b)?r?'''/,end:/'''/,contains:[e.BACKSLASH_ESCAPE,a],relevance:10},{begin:/(u|b)?r?"""/,end:/"""/,contains:[e.BACKSLASH_ESCAPE,a],relevance:10},{begin:/(fr|rf|f)'''/,end:/'''/,contains:[e.BACKSLASH_ESCAPE,a,s,i]},{begin:/(fr|rf|f)"""/,end:/"""/,contains:[e.BACKSLASH_ESCAPE,a,s,i]},{begin:/(u|r|ur)'/,end:/'/,relevance:10},{begin:/(u|r|ur)"/,end:/"/,relevance:10},{begin:/(b|br)'/,end:/'/},{begin:/(b|br)"/,end:/"/},{begin:/(fr|rf|f)'/,end:/'/,contains:[e.BACKSLASH_ESCAPE,s,i]},{begin:/(fr|rf|f)"/,end:/"/,contains:[e.BACKSLASH_ESCAPE,s,i]},e.APOS_STRING_MODE,e.QUOTE_STRING_MODE]},l={className:"number",relevance:0,variants:[{begin:e.BINARY_NUMBER_RE+"[lLjJ]?"},{begin:"\\b(0o[0-7]+)[lLjJ]?"},{begin:e.C_NUMBER_RE+"[lLjJ]?"}]},t={className:"params",variants:[{begin:/\(\s*\)/,skip:!0,className:null},{begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,contains:["self",a,l,r,e.HASH_COMMENT_MODE]}]};return i.contains=[r,l,a],{name:"Python",aliases:["py","gyp","ipython"],keywords:n,illegal:/(<\/|->|\?)|=>/,contains:[a,l,{beginKeywords:"if",relevance:0},r,e.HASH_COMMENT_MODE,{variants:[{className:"function",beginKeywords:"def"},{className:"class",beginKeywords:"class"}],end:/:/,illegal:/[${=;\n,]/,contains:[e.UNDERSCORE_TITLE_MODE,t,{begin:/->/,endsWithParent:!0,keywords:"None"}]},{className:"meta",begin:/^[\t ]*@/,end:/$/},{begin:/\b(print|exec)\(/}]}}}());hljs.registerLanguage("plaintext",function(){"use strict";return function(t){return{name:"Plain text",aliases:["text","txt"],disableAutodetect:!0}}}());hljs.registerLanguage("diff",function(){"use strict";return function(e){return{name:"Diff",aliases:["patch"],contains:[{className:"meta",relevance:10,variants:[{begin:/^@@ +\-\d+,\d+ +\+\d+,\d+ +@@$/},{begin:/^\*\*\* +\d+,\d+ +\*\*\*\*$/},{begin:/^\-\-\- +\d+,\d+ +\-\-\-\-$/}]},{className:"comment",variants:[{begin:/Index: /,end:/$/},{begin:/={3,}/,end:/$/},{begin:/^\-{3}/,end:/$/},{begin:/^\*{3} /,end:/$/},{begin:/^\+{3}/,end:/$/},{begin:/^\*{15}$/}]},{className:"addition",begin:"^\\+",end:"$"},{className:"deletion",begin:"^\\-",end:"$"},{className:"addition",begin:"^\\!",end:"$"}]}}}());hljs.registerLanguage("csharp",function(){"use strict";return function(e){var n={keyword:"abstract as base bool break byte case catch char checked const continue decimal default delegate do double enum event explicit extern finally fixed float for foreach goto if implicit in int interface internal is lock long object operator out override params private protected public readonly ref sbyte sealed short sizeof stackalloc static string struct switch this try typeof uint ulong unchecked unsafe ushort using virtual void volatile while add alias ascending async await by descending dynamic equals from get global group into join let nameof on orderby partial remove select set value var when where yield",literal:"null false true"},i=e.inherit(e.TITLE_MODE,{begin:"[a-zA-Z](\\.?\\w)*"}),a={className:"number",variants:[{begin:"\\b(0b[01']+)"},{begin:"(-?)\\b([\\d']+(\\.[\\d']*)?|\\.[\\d']+)(u|U|l|L|ul|UL|f|F|b|B)"},{begin:"(-?)(\\b0[xX][a-fA-F0-9']+|(\\b[\\d']+(\\.[\\d']*)?|\\.[\\d']+)([eE][-+]?[\\d']+)?)"}],relevance:0},s={className:"string",begin:'@"',end:'"',contains:[{begin:'""'}]},t=e.inherit(s,{illegal:/\n/}),l={className:"subst",begin:"{",end:"}",keywords:n},r=e.inherit(l,{illegal:/\n/}),c={className:"string",begin:/\$"/,end:'"',illegal:/\n/,contains:[{begin:"{{"},{begin:"}}"},e.BACKSLASH_ESCAPE,r]},o={className:"string",begin:/\$@"/,end:'"',contains:[{begin:"{{"},{begin:"}}"},{begin:'""'},l]},g=e.inherit(o,{illegal:/\n/,contains:[{begin:"{{"},{begin:"}}"},{begin:'""'},r]});l.contains=[o,c,s,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,a,e.C_BLOCK_COMMENT_MODE],r.contains=[g,c,t,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,a,e.inherit(e.C_BLOCK_COMMENT_MODE,{illegal:/\n/})];var d={variants:[o,c,s,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE]},E=e.IDENT_RE+"(<"+e.IDENT_RE+"(\\s*,\\s*"+e.IDENT_RE+")*>)?(\\[\\])?",_={begin:"@"+e.IDENT_RE,relevance:0};return{name:"C#",aliases:["cs","c#"],keywords:n,illegal:/::/,contains:[e.COMMENT("///","$",{returnBegin:!0,contains:[{className:"doctag",variants:[{begin:"///",relevance:0},{begin:"\x3c!--|--\x3e"},{begin:""}]}]}),e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,{className:"meta",begin:"#",end:"$",keywords:{"meta-keyword":"if else elif endif define undef warning error line region endregion pragma checksum"}},d,a,{beginKeywords:"class interface",end:/[{;=]/,illegal:/[^\s:,]/,contains:[{beginKeywords:"where class"},i,{begin:"<",end:">",keywords:"in out"},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},{beginKeywords:"namespace",end:/[{;=]/,illegal:/[^\s:]/,contains:[i,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},{className:"meta",begin:"^\\s*\\[",excludeBegin:!0,end:"\\]",excludeEnd:!0,contains:[{className:"meta-string",begin:/"/,end:/"/}]},{beginKeywords:"new return throw await else",relevance:0},{className:"function",begin:"("+E+"\\s+)+"+e.IDENT_RE+"\\s*\\(",returnBegin:!0,end:/\s*[{;=]/,excludeEnd:!0,keywords:n,contains:[{begin:e.IDENT_RE+"\\s*\\(",returnBegin:!0,contains:[e.TITLE_MODE],relevance:0},{className:"params",begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:n,relevance:0,contains:[d,a,e.C_BLOCK_COMMENT_MODE]},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},_]}}}());hljs.registerLanguage("ruby",function(){"use strict";return function(e){var n="[a-zA-Z_]\\w*[!?=]?|[-+~]\\@|<<|>>|=~|===?|<=>|[<>]=?|\\*\\*|[-/+%^&*~`|]|\\[\\]=?",a={keyword:"and then defined module in return redo if BEGIN retry end for self when next until do begin unless END rescue else break undef not super class case require yield alias while ensure elsif or include attr_reader attr_writer attr_accessor",literal:"true false nil"},s={className:"doctag",begin:"@[A-Za-z]+"},i={begin:"#<",end:">"},r=[e.COMMENT("#","$",{contains:[s]}),e.COMMENT("^\\=begin","^\\=end",{contains:[s],relevance:10}),e.COMMENT("^__END__","\\n$")],c={className:"subst",begin:"#\\{",end:"}",keywords:a},t={className:"string",contains:[e.BACKSLASH_ESCAPE,c],variants:[{begin:/'/,end:/'/},{begin:/"/,end:/"/},{begin:/`/,end:/`/},{begin:"%[qQwWx]?\\(",end:"\\)"},{begin:"%[qQwWx]?\\[",end:"\\]"},{begin:"%[qQwWx]?{",end:"}"},{begin:"%[qQwWx]?<",end:">"},{begin:"%[qQwWx]?/",end:"/"},{begin:"%[qQwWx]?%",end:"%"},{begin:"%[qQwWx]?-",end:"-"},{begin:"%[qQwWx]?\\|",end:"\\|"},{begin:/\B\?(\\\d{1,3}|\\x[A-Fa-f0-9]{1,2}|\\u[A-Fa-f0-9]{4}|\\?\S)\b/},{begin:/<<[-~]?'?(\w+)(?:.|\n)*?\n\s*\1\b/,returnBegin:!0,contains:[{begin:/<<[-~]?'?/},{begin:/\w+/,endSameAsBegin:!0,contains:[e.BACKSLASH_ESCAPE,c]}]}]},b={className:"params",begin:"\\(",end:"\\)",endsParent:!0,keywords:a},d=[t,i,{className:"class",beginKeywords:"class module",end:"$|;",illegal:/=/,contains:[e.inherit(e.TITLE_MODE,{begin:"[A-Za-z_]\\w*(::\\w+)*(\\?|\\!)?"}),{begin:"<\\s*",contains:[{begin:"("+e.IDENT_RE+"::)?"+e.IDENT_RE}]}].concat(r)},{className:"function",beginKeywords:"def",end:"$|;",contains:[e.inherit(e.TITLE_MODE,{begin:n}),b].concat(r)},{begin:e.IDENT_RE+"::"},{className:"symbol",begin:e.UNDERSCORE_IDENT_RE+"(\\!|\\?)?:",relevance:0},{className:"symbol",begin:":(?!\\s)",contains:[t,{begin:n}],relevance:0},{className:"number",begin:"(\\b0[0-7_]+)|(\\b0x[0-9a-fA-F_]+)|(\\b[1-9][0-9_]*(\\.[0-9_]+)?)|[0_]\\b",relevance:0},{begin:"(\\$\\W)|((\\$|\\@\\@?)(\\w+))"},{className:"params",begin:/\|/,end:/\|/,keywords:a},{begin:"("+e.RE_STARTERS_RE+"|unless)\\s*",keywords:"unless",contains:[i,{className:"regexp",contains:[e.BACKSLASH_ESCAPE,c],illegal:/\n/,variants:[{begin:"/",end:"/[a-z]*"},{begin:"%r{",end:"}[a-z]*"},{begin:"%r\\(",end:"\\)[a-z]*"},{begin:"%r!",end:"![a-z]*"},{begin:"%r\\[",end:"\\][a-z]*"}]}].concat(r),relevance:0}].concat(r);c.contains=d,b.contains=d;var g=[{begin:/^\s*=>/,starts:{end:"$",contains:d}},{className:"meta",begin:"^([>?]>|[\\w#]+\\(\\w+\\):\\d+:\\d+>|(\\w+-)?\\d+\\.\\d+\\.\\d(p\\d+)?[^>]+>)",starts:{end:"$",contains:d}}];return{name:"Ruby",aliases:["rb","gemspec","podspec","thor","irb"],keywords:a,illegal:/\/\*/,contains:r.concat(g).concat(d)}}}());hljs.registerLanguage("scss",function(){"use strict";return function(e){var t={className:"variable",begin:"(\\$[a-zA-Z-][a-zA-Z0-9_-]*)\\b"},i={className:"number",begin:"#[0-9A-Fa-f]+"};return e.CSS_NUMBER_MODE,e.QUOTE_STRING_MODE,e.APOS_STRING_MODE,e.C_BLOCK_COMMENT_MODE,{name:"SCSS",case_insensitive:!0,illegal:"[=/|']",contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,{className:"selector-id",begin:"\\#[A-Za-z0-9_-]+",relevance:0},{className:"selector-class",begin:"\\.[A-Za-z0-9_-]+",relevance:0},{className:"selector-attr",begin:"\\[",end:"\\]",illegal:"$"},{className:"selector-tag",begin:"\\b(a|abbr|acronym|address|area|article|aside|audio|b|base|big|blockquote|body|br|button|canvas|caption|cite|code|col|colgroup|command|datalist|dd|del|details|dfn|div|dl|dt|em|embed|fieldset|figcaption|figure|footer|form|frame|frameset|(h[1-6])|head|header|hgroup|hr|html|i|iframe|img|input|ins|kbd|keygen|label|legend|li|link|map|mark|meta|meter|nav|noframes|noscript|object|ol|optgroup|option|output|p|param|pre|progress|q|rp|rt|ruby|samp|script|section|select|small|span|strike|strong|style|sub|sup|table|tbody|td|textarea|tfoot|th|thead|time|title|tr|tt|ul|var|video)\\b",relevance:0},{className:"selector-pseudo",begin:":(visited|valid|root|right|required|read-write|read-only|out-range|optional|only-of-type|only-child|nth-of-type|nth-last-of-type|nth-last-child|nth-child|not|link|left|last-of-type|last-child|lang|invalid|indeterminate|in-range|hover|focus|first-of-type|first-line|first-letter|first-child|first|enabled|empty|disabled|default|checked|before|after|active)"},{className:"selector-pseudo",begin:"::(after|before|choices|first-letter|first-line|repeat-index|repeat-item|selection|value)"},t,{className:"attribute",begin:"\\b(src|z-index|word-wrap|word-spacing|word-break|width|widows|white-space|visibility|vertical-align|unicode-bidi|transition-timing-function|transition-property|transition-duration|transition-delay|transition|transform-style|transform-origin|transform|top|text-underline-position|text-transform|text-shadow|text-rendering|text-overflow|text-indent|text-decoration-style|text-decoration-line|text-decoration-color|text-decoration|text-align-last|text-align|tab-size|table-layout|right|resize|quotes|position|pointer-events|perspective-origin|perspective|page-break-inside|page-break-before|page-break-after|padding-top|padding-right|padding-left|padding-bottom|padding|overflow-y|overflow-x|overflow-wrap|overflow|outline-width|outline-style|outline-offset|outline-color|outline|orphans|order|opacity|object-position|object-fit|normal|none|nav-up|nav-right|nav-left|nav-index|nav-down|min-width|min-height|max-width|max-height|mask|marks|margin-top|margin-right|margin-left|margin-bottom|margin|list-style-type|list-style-position|list-style-image|list-style|line-height|letter-spacing|left|justify-content|initial|inherit|ime-mode|image-orientation|image-resolution|image-rendering|icon|hyphens|height|font-weight|font-variant-ligatures|font-variant|font-style|font-stretch|font-size-adjust|font-size|font-language-override|font-kerning|font-feature-settings|font-family|font|float|flex-wrap|flex-shrink|flex-grow|flex-flow|flex-direction|flex-basis|flex|filter|empty-cells|display|direction|cursor|counter-reset|counter-increment|content|column-width|column-span|column-rule-width|column-rule-style|column-rule-color|column-rule|column-gap|column-fill|column-count|columns|color|clip-path|clip|clear|caption-side|break-inside|break-before|break-after|box-sizing|box-shadow|box-decoration-break|bottom|border-width|border-top-width|border-top-style|border-top-right-radius|border-top-left-radius|border-top-color|border-top|border-style|border-spacing|border-right-width|border-right-style|border-right-color|border-right|border-radius|border-left-width|border-left-style|border-left-color|border-left|border-image-width|border-image-source|border-image-slice|border-image-repeat|border-image-outset|border-image|border-color|border-collapse|border-bottom-width|border-bottom-style|border-bottom-right-radius|border-bottom-left-radius|border-bottom-color|border-bottom|border|background-size|background-repeat|background-position|background-origin|background-image|background-color|background-clip|background-attachment|background-blend-mode|background|backface-visibility|auto|animation-timing-function|animation-play-state|animation-name|animation-iteration-count|animation-fill-mode|animation-duration|animation-direction|animation-delay|animation|align-self|align-items|align-content)\\b",illegal:"[^\\s]"},{begin:"\\b(whitespace|wait|w-resize|visible|vertical-text|vertical-ideographic|uppercase|upper-roman|upper-alpha|underline|transparent|top|thin|thick|text|text-top|text-bottom|tb-rl|table-header-group|table-footer-group|sw-resize|super|strict|static|square|solid|small-caps|separate|se-resize|scroll|s-resize|rtl|row-resize|ridge|right|repeat|repeat-y|repeat-x|relative|progress|pointer|overline|outside|outset|oblique|nowrap|not-allowed|normal|none|nw-resize|no-repeat|no-drop|newspaper|ne-resize|n-resize|move|middle|medium|ltr|lr-tb|lowercase|lower-roman|lower-alpha|loose|list-item|line|line-through|line-edge|lighter|left|keep-all|justify|italic|inter-word|inter-ideograph|inside|inset|inline|inline-block|inherit|inactive|ideograph-space|ideograph-parenthesis|ideograph-numeric|ideograph-alpha|horizontal|hidden|help|hand|groove|fixed|ellipsis|e-resize|double|dotted|distribute|distribute-space|distribute-letter|distribute-all-lines|disc|disabled|default|decimal|dashed|crosshair|collapse|col-resize|circle|char|center|capitalize|break-word|break-all|bottom|both|bolder|bold|block|bidi-override|below|baseline|auto|always|all-scroll|absolute|table|table-cell)\\b"},{begin:":",end:";",contains:[t,i,e.CSS_NUMBER_MODE,e.QUOTE_STRING_MODE,e.APOS_STRING_MODE,{className:"meta",begin:"!important"}]},{begin:"@(page|font-face)",lexemes:"@[a-z-]+",keywords:"@page @font-face"},{begin:"@",end:"[{;]",returnBegin:!0,keywords:"and or not only",contains:[{begin:"@[a-z-]+",className:"keyword"},t,e.QUOTE_STRING_MODE,e.APOS_STRING_MODE,i,e.CSS_NUMBER_MODE]}]}}}());hljs.registerLanguage("http",function(){"use strict";return function(e){var n="HTTP/[0-9\\.]+";return{name:"HTTP",aliases:["https"],illegal:"\\S",contains:[{begin:"^"+n,end:"$",contains:[{className:"number",begin:"\\b\\d{3}\\b"}]},{begin:"^[A-Z]+ (.*?) "+n+"$",returnBegin:!0,end:"$",contains:[{className:"string",begin:" ",end:" ",excludeBegin:!0,excludeEnd:!0},{begin:n},{className:"keyword",begin:"[A-Z]+"}]},{className:"attribute",begin:"^\\w",end:": ",excludeEnd:!0,illegal:"\\n|\\s|=",starts:{end:"$",relevance:0}},{begin:"\\n\\n",starts:{subLanguage:[],endsWithParent:!0}}]}}}());hljs.registerLanguage("java",function(){"use strict";return function(e){var a="false synchronized int abstract float private char boolean var static null if const for true while long strictfp finally protected import native final void enum else break transient catch instanceof byte super volatile case assert short package default double public try this switch continue throws protected public private module requires exports do",n={className:"meta",begin:"@[À-ʸa-zA-Z_$][À-ʸa-zA-Z_$0-9]*",contains:[{begin:/\(/,end:/\)/,contains:["self"]}]};return{name:"Java",aliases:["jsp"],keywords:a,illegal:/<\/|#/,contains:[e.COMMENT("/\\*\\*","\\*/",{relevance:0,contains:[{begin:/\w+@/,relevance:0},{className:"doctag",begin:"@[A-Za-z]+"}]}),e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,{className:"class",beginKeywords:"class interface",end:/[{;=]/,excludeEnd:!0,keywords:"class interface",illegal:/[:"\[\]]/,contains:[{beginKeywords:"extends implements"},e.UNDERSCORE_TITLE_MODE]},{beginKeywords:"new throw return else",relevance:0},{className:"function",begin:"([À-ʸa-zA-Z_$][À-ʸa-zA-Z_$0-9]*(<[À-ʸa-zA-Z_$][À-ʸa-zA-Z_$0-9]*(\\s*,\\s*[À-ʸa-zA-Z_$][À-ʸa-zA-Z_$0-9]*)*>)?\\s+)+"+e.UNDERSCORE_IDENT_RE+"\\s*\\(",returnBegin:!0,end:/[{;=]/,excludeEnd:!0,keywords:a,contains:[{begin:e.UNDERSCORE_IDENT_RE+"\\s*\\(",returnBegin:!0,relevance:0,contains:[e.UNDERSCORE_TITLE_MODE]},{className:"params",begin:/\(/,end:/\)/,keywords:a,relevance:0,contains:[n,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,e.C_NUMBER_MODE,e.C_BLOCK_COMMENT_MODE]},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},{className:"number",begin:"\\b(0[bB]([01]+[01_]+[01]+|[01]+)|0[xX]([a-fA-F0-9]+[a-fA-F0-9_]+[a-fA-F0-9]+|[a-fA-F0-9]+)|(([\\d]+[\\d_]+[\\d]+|[\\d]+)(\\.([\\d]+[\\d_]+[\\d]+|[\\d]+))?|\\.([\\d]+[\\d_]+[\\d]+|[\\d]+))([eE][-+]?\\d+)?)[lLfF]?",relevance:0},n]}}}());hljs.registerLanguage("swift",function(){"use strict";return function(e){var i={keyword:"#available #colorLiteral #column #else #elseif #endif #file #fileLiteral #function #if #imageLiteral #line #selector #sourceLocation _ __COLUMN__ __FILE__ __FUNCTION__ __LINE__ Any as as! as? associatedtype associativity break case catch class continue convenience default defer deinit didSet do dynamic dynamicType else enum extension fallthrough false fileprivate final for func get guard if import in indirect infix init inout internal is lazy left let mutating nil none nonmutating open operator optional override postfix precedence prefix private protocol Protocol public repeat required rethrows return right self Self set static struct subscript super switch throw throws true try try! try? Type typealias unowned var weak where while willSet",literal:"true false nil",built_in:"abs advance alignof alignofValue anyGenerator assert assertionFailure bridgeFromObjectiveC bridgeFromObjectiveCUnconditional bridgeToObjectiveC bridgeToObjectiveCUnconditional c compactMap contains count countElements countLeadingZeros debugPrint debugPrintln distance dropFirst dropLast dump encodeBitsAsWords enumerate equal fatalError filter find getBridgedObjectiveCType getVaList indices insertionSort isBridgedToObjectiveC isBridgedVerbatimToObjectiveC isUniquelyReferenced isUniquelyReferencedNonObjC join lazy lexicographicalCompare map max maxElement min minElement numericCast overlaps partition posix precondition preconditionFailure print println quickSort readLine reduce reflect reinterpretCast reverse roundUpToAlignment sizeof sizeofValue sort split startsWith stride strideof strideofValue swap toString transcode underestimateCount unsafeAddressOf unsafeBitCast unsafeDowncast unsafeUnwrap unsafeReflect withExtendedLifetime withObjectAtPlusZero withUnsafePointer withUnsafePointerToObject withUnsafeMutablePointer withUnsafeMutablePointers withUnsafePointer withUnsafePointers withVaList zip"},n=e.COMMENT("/\\*","\\*/",{contains:["self"]}),t={className:"subst",begin:/\\\(/,end:"\\)",keywords:i,contains:[]},a={className:"string",contains:[e.BACKSLASH_ESCAPE,t],variants:[{begin:/"""/,end:/"""/},{begin:/"/,end:/"/}]},r={className:"number",begin:"\\b([\\d_]+(\\.[\\deE_]+)?|0x[a-fA-F0-9_]+(\\.[a-fA-F0-9p_]+)?|0b[01_]+|0o[0-7_]+)\\b",relevance:0};return t.contains=[r],{name:"Swift",keywords:i,contains:[a,e.C_LINE_COMMENT_MODE,n,{className:"type",begin:"\\b[A-Z][\\wÀ-ʸ']*[!?]"},{className:"type",begin:"\\b[A-Z][\\wÀ-ʸ']*",relevance:0},r,{className:"function",beginKeywords:"func",end:"{",excludeEnd:!0,contains:[e.inherit(e.TITLE_MODE,{begin:/[A-Za-z$_][0-9A-Za-z$_]*/}),{begin://},{className:"params",begin:/\(/,end:/\)/,endsParent:!0,keywords:i,contains:["self",r,a,e.C_BLOCK_COMMENT_MODE,{begin:":"}],illegal:/["']/}],illegal:/\[|%/},{className:"class",beginKeywords:"struct protocol class extension enum",keywords:i,end:"\\{",excludeEnd:!0,contains:[e.inherit(e.TITLE_MODE,{begin:/[A-Za-z$_][\u00C0-\u02B80-9A-Za-z$_]*/})]},{className:"meta",begin:"(@discardableResult|@warn_unused_result|@exported|@lazy|@noescape|@NSCopying|@NSManaged|@objc|@objcMembers|@convention|@required|@noreturn|@IBAction|@IBDesignable|@IBInspectable|@IBOutlet|@infix|@prefix|@postfix|@autoclosure|@testable|@available|@nonobjc|@NSApplicationMain|@UIApplicationMain|@dynamicMemberLookup|@propertyWrapper)"},{beginKeywords:"import",end:/$/,contains:[e.C_LINE_COMMENT_MODE,n]}]}}}());hljs.registerLanguage("less",function(){"use strict";return function(e){var n="([\\w-]+|@{[\\w-]+})",a=[],s=[],t=function(e){return{className:"string",begin:"~?"+e+".*?"+e}},r=function(e,n,a){return{className:e,begin:n,relevance:a}},i={begin:"\\(",end:"\\)",contains:s,relevance:0};s.push(e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,t("'"),t('"'),e.CSS_NUMBER_MODE,{begin:"(url|data-uri)\\(",starts:{className:"string",end:"[\\)\\n]",excludeEnd:!0}},r("number","#[0-9A-Fa-f]+\\b"),i,r("variable","@@?[\\w-]+",10),r("variable","@{[\\w-]+}"),r("built_in","~?`[^`]*?`"),{className:"attribute",begin:"[\\w-]+\\s*:",end:":",returnBegin:!0,excludeEnd:!0},{className:"meta",begin:"!important"});var c=s.concat({begin:"{",end:"}",contains:a}),l={beginKeywords:"when",endsWithParent:!0,contains:[{beginKeywords:"and not"}].concat(s)},o={begin:n+"\\s*:",returnBegin:!0,end:"[;}]",relevance:0,contains:[{className:"attribute",begin:n,end:":",excludeEnd:!0,starts:{endsWithParent:!0,illegal:"[<=$]",relevance:0,contains:s}}]},g={className:"keyword",begin:"@(import|media|charset|font-face|(-[a-z]+-)?keyframes|supports|document|namespace|page|viewport|host)\\b",starts:{end:"[;{}]",returnEnd:!0,contains:s,relevance:0}},d={className:"variable",variants:[{begin:"@[\\w-]+\\s*:",relevance:15},{begin:"@[\\w-]+"}],starts:{end:"[;}]",returnEnd:!0,contains:c}},b={variants:[{begin:"[\\.#:&\\[>]",end:"[;{}]"},{begin:n,end:"{"}],returnBegin:!0,returnEnd:!0,illegal:"[<='$\"]",relevance:0,contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,l,r("keyword","all\\b"),r("variable","@{[\\w-]+}"),r("selector-tag",n+"%?",0),r("selector-id","#"+n),r("selector-class","\\."+n,0),r("selector-tag","&",0),{className:"selector-attr",begin:"\\[",end:"\\]"},{className:"selector-pseudo",begin:/:(:)?[a-zA-Z0-9\_\-\+\(\)"'.]+/},{begin:"\\(",end:"\\)",contains:c},{begin:"!important"}]};return a.push(e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,g,d,o,b),{name:"Less",case_insensitive:!0,illegal:"[=>'/<($\"]",contains:a}}}());hljs.registerLanguage("json",function(){"use strict";return function(n){var e={literal:"true false null"},i=[n.C_LINE_COMMENT_MODE,n.C_BLOCK_COMMENT_MODE],t=[n.QUOTE_STRING_MODE,n.C_NUMBER_MODE],a={end:",",endsWithParent:!0,excludeEnd:!0,contains:t,keywords:e},l={begin:"{",end:"}",contains:[{className:"attr",begin:/"/,end:/"/,contains:[n.BACKSLASH_ESCAPE],illegal:"\\n"},n.inherit(a,{begin:/:/})].concat(i),illegal:"\\S"},s={begin:"\\[",end:"\\]",contains:[n.inherit(a)],illegal:"\\S"};return t.push(l,s),i.forEach((function(n){t.push(n)})),{name:"JSON",contains:t,keywords:e,illegal:"\\S"}}}());hljs.registerLanguage("typescript",function(){"use strict";return function(e){var n={keyword:"in if for while finally var new function do return void else break catch instanceof with throw case default try this switch continue typeof delete let yield const class public private protected get set super static implements enum export import declare type namespace abstract as from extends async await",literal:"true false null undefined NaN Infinity",built_in:"eval isFinite isNaN parseFloat parseInt decodeURI decodeURIComponent encodeURI encodeURIComponent escape unescape Object Function Boolean Error EvalError InternalError RangeError ReferenceError StopIteration SyntaxError TypeError URIError Number Math Date String RegExp Array Float32Array Float64Array Int16Array Int32Array Int8Array Uint16Array Uint32Array Uint8Array Uint8ClampedArray ArrayBuffer DataView JSON Intl arguments require module console window document any number boolean string void Promise"},r={className:"meta",begin:"@[A-Za-z$_][0-9A-Za-z$_]*"},a={begin:"\\(",end:/\)/,keywords:n,contains:["self",e.QUOTE_STRING_MODE,e.APOS_STRING_MODE,e.NUMBER_MODE]},t={className:"params",begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:n,contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,r,a]},s={className:"number",variants:[{begin:"\\b(0[bB][01]+)n?"},{begin:"\\b(0[oO][0-7]+)n?"},{begin:e.C_NUMBER_RE+"n?"}],relevance:0},i={className:"subst",begin:"\\$\\{",end:"\\}",keywords:n,contains:[]},o={begin:"html`",end:"",starts:{end:"`",returnEnd:!1,contains:[e.BACKSLASH_ESCAPE,i],subLanguage:"xml"}},c={begin:"css`",end:"",starts:{end:"`",returnEnd:!1,contains:[e.BACKSLASH_ESCAPE,i],subLanguage:"css"}},E={className:"string",begin:"`",end:"`",contains:[e.BACKSLASH_ESCAPE,i]};return i.contains=[e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,o,c,E,s,e.REGEXP_MODE],{name:"TypeScript",aliases:["ts"],keywords:n,contains:[{className:"meta",begin:/^\s*['"]use strict['"]/},e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,o,c,E,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,s,{begin:"("+e.RE_STARTERS_RE+"|\\b(case|return|throw)\\b)\\s*",keywords:"return throw case",contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,e.REGEXP_MODE,{className:"function",begin:"(\\(.*?\\)|"+e.IDENT_RE+")\\s*=>",returnBegin:!0,end:"\\s*=>",contains:[{className:"params",variants:[{begin:e.IDENT_RE},{begin:/\(\s*\)/},{begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:n,contains:["self",e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]}]}]}],relevance:0},{className:"function",beginKeywords:"function",end:/[\{;]/,excludeEnd:!0,keywords:n,contains:["self",e.inherit(e.TITLE_MODE,{begin:"[A-Za-z$_][0-9A-Za-z$_]*"}),t],illegal:/%/,relevance:0},{beginKeywords:"constructor",end:/[\{;]/,excludeEnd:!0,contains:["self",t]},{begin:/module\./,keywords:{built_in:"module"},relevance:0},{beginKeywords:"module",end:/\{/,excludeEnd:!0},{beginKeywords:"interface",end:/\{/,excludeEnd:!0,keywords:"interface extends"},{begin:/\$[(.]/},{begin:"\\."+e.IDENT_RE,relevance:0},r,a]}}}());hljs.registerLanguage("c-like",function(){"use strict";return function(e){function t(e){return"(?:"+e+")?"}var n="(decltype\\(auto\\)|"+t("[a-zA-Z_]\\w*::")+"[a-zA-Z_]\\w*"+t("<.*?>")+")",r={className:"keyword",begin:"\\b[a-z\\d_]*_t\\b"},a={className:"string",variants:[{begin:'(u8?|U|L)?"',end:'"',illegal:"\\n",contains:[e.BACKSLASH_ESCAPE]},{begin:"(u8?|U|L)?'(\\\\(x[0-9A-Fa-f]{2}|u[0-9A-Fa-f]{4,8}|[0-7]{3}|\\S)|.)",end:"'",illegal:"."},{begin:/(?:u8?|U|L)?R"([^()\\ ]{0,16})\((?:.|\n)*?\)\1"/}]},s={className:"number",variants:[{begin:"\\b(0b[01']+)"},{begin:"(-?)\\b([\\d']+(\\.[\\d']*)?|\\.[\\d']+)(u|U|l|L|ul|UL|f|F|b|B)"},{begin:"(-?)(\\b0[xX][a-fA-F0-9']+|(\\b[\\d']+(\\.[\\d']*)?|\\.[\\d']+)([eE][-+]?[\\d']+)?)"}],relevance:0},i={className:"meta",begin:/#\s*[a-z]+\b/,end:/$/,keywords:{"meta-keyword":"if else elif endif define undef warning error line pragma _Pragma ifdef ifndef include"},contains:[{begin:/\\\n/,relevance:0},e.inherit(a,{className:"meta-string"}),{className:"meta-string",begin:/<.*?>/,end:/$/,illegal:"\\n"},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},c={className:"title",begin:t("[a-zA-Z_]\\w*::")+e.IDENT_RE,relevance:0},o=t("[a-zA-Z_]\\w*::")+e.IDENT_RE+"\\s*\\(",l={keyword:"int float while private char char8_t char16_t char32_t catch import module export virtual operator sizeof dynamic_cast|10 typedef const_cast|10 const for static_cast|10 union namespace unsigned long volatile static protected bool template mutable if public friend do goto auto void enum else break extern using asm case typeid wchar_t short reinterpret_cast|10 default double register explicit signed typename try this switch continue inline delete alignas alignof constexpr consteval constinit decltype concept co_await co_return co_yield requires noexcept static_assert thread_local restrict final override atomic_bool atomic_char atomic_schar atomic_uchar atomic_short atomic_ushort atomic_int atomic_uint atomic_long atomic_ulong atomic_llong atomic_ullong new throw return and and_eq bitand bitor compl not not_eq or or_eq xor xor_eq",built_in:"std string wstring cin cout cerr clog stdin stdout stderr stringstream istringstream ostringstream auto_ptr deque list queue stack vector map set bitset multiset multimap unordered_set unordered_map unordered_multiset unordered_multimap array shared_ptr abort terminate abs acos asin atan2 atan calloc ceil cosh cos exit exp fabs floor fmod fprintf fputs free frexp fscanf future isalnum isalpha iscntrl isdigit isgraph islower isprint ispunct isspace isupper isxdigit tolower toupper labs ldexp log10 log malloc realloc memchr memcmp memcpy memset modf pow printf putchar puts scanf sinh sin snprintf sprintf sqrt sscanf strcat strchr strcmp strcpy strcspn strlen strncat strncmp strncpy strpbrk strrchr strspn strstr tanh tan vfprintf vprintf vsprintf endl initializer_list unique_ptr _Bool complex _Complex imaginary _Imaginary",literal:"true false nullptr NULL"},d=[r,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,s,a],_={variants:[{begin:/=/,end:/;/},{begin:/\(/,end:/\)/},{beginKeywords:"new throw return else",end:/;/}],keywords:l,contains:d.concat([{begin:/\(/,end:/\)/,keywords:l,contains:d.concat(["self"]),relevance:0}]),relevance:0},u={className:"function",begin:"("+n+"[\\*&\\s]+)+"+o,returnBegin:!0,end:/[{;=]/,excludeEnd:!0,keywords:l,illegal:/[^\w\s\*&:<>]/,contains:[{begin:"decltype\\(auto\\)",keywords:l,relevance:0},{begin:o,returnBegin:!0,contains:[c],relevance:0},{className:"params",begin:/\(/,end:/\)/,keywords:l,relevance:0,contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,a,s,r,{begin:/\(/,end:/\)/,keywords:l,relevance:0,contains:["self",e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,a,s,r]}]},r,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,i]};return{aliases:["c","cc","h","c++","h++","hpp","hh","hxx","cxx"],keywords:l,disableAutodetect:!0,illegal:"",keywords:l,contains:["self",r]},{begin:e.IDENT_RE+"::",keywords:l},{className:"class",beginKeywords:"class struct",end:/[{;:]/,contains:[{begin://,contains:["self"]},e.TITLE_MODE]}]),exports:{preprocessor:i,strings:a,keywords:l}}}}());hljs.registerLanguage("cpp",function(){"use strict";return function(e){var t=e.getLanguage("c-like").rawDefinition();return t.disableAutodetect=!1,t.name="C++",t.aliases=["cc","c++","h++","hpp","hh","hxx","cxx"],t}}());hljs.registerLanguage("properties",function(){"use strict";return function(e){var n="[ \\t\\f]*",t="("+n+"[:=]"+n+"|[ \\t\\f]+)",a="([^\\\\:= \\t\\f\\n]|\\\\.)+",s={end:t,relevance:0,starts:{className:"string",end:/$/,relevance:0,contains:[{begin:"\\\\\\n"}]}};return{name:".properties",case_insensitive:!0,illegal:/\S/,contains:[e.COMMENT("^\\s*[!#]","$"),{begin:"([^\\\\\\W:= \\t\\f\\n]|\\\\.)+"+t,returnBegin:!0,contains:[{className:"attr",begin:"([^\\\\\\W:= \\t\\f\\n]|\\\\.)+",endsParent:!0,relevance:0}],starts:s},{begin:a+t,returnBegin:!0,relevance:0,contains:[{className:"meta",begin:a,endsParent:!0,relevance:0}],starts:s},{className:"attr",relevance:0,begin:a+n+"$"}]}}}());hljs.registerLanguage("twig",function(){"use strict";return function(e){var a="attribute block constant cycle date dump include max min parent random range source template_from_string",n={beginKeywords:a,keywords:{name:a},relevance:0,contains:[{className:"params",begin:"\\(",end:"\\)"}]},t={begin:/\|[A-Za-z_]+:?/,keywords:"abs batch capitalize column convert_encoding date date_modify default escape filter first format inky_to_html inline_css join json_encode keys last length lower map markdown merge nl2br number_format raw reduce replace reverse round slice sort spaceless split striptags title trim upper url_encode",contains:[n]},s="apply autoescape block deprecated do embed extends filter flush for from if import include macro sandbox set use verbatim with";return s=s+" "+s.split(" ").map((function(e){return"end"+e})).join(" "),{name:"Twig",aliases:["craftcms"],case_insensitive:!0,subLanguage:"xml",contains:[e.COMMENT(/\{#/,/#}/),{className:"template-tag",begin:/\{%/,end:/%}/,contains:[{className:"name",begin:/\w+/,keywords:s,starts:{endsWithParent:!0,contains:[t,n],relevance:0}}]},{className:"template-variable",begin:/\{\{/,end:/}}/,contains:["self",t,n]}]}}}());hljs.registerLanguage("javascript",function(){"use strict";return function(e){var n={begin:/<[A-Za-z0-9\\._:-]+/,end:/\/[A-Za-z0-9\\._:-]+>|\/>/},a="[A-Za-z$_][0-9A-Za-z$_]*",s={keyword:"in of if for while finally var new function do return void else break catch instanceof with throw case default try this switch continue typeof delete let yield const export super debugger as async await static import from as",literal:"true false null undefined NaN Infinity",built_in:"eval isFinite isNaN parseFloat parseInt decodeURI decodeURIComponent encodeURI encodeURIComponent escape unescape Object Function Boolean Error EvalError InternalError RangeError ReferenceError StopIteration SyntaxError TypeError URIError Number Math Date String RegExp Array Float32Array Float64Array Int16Array Int32Array Int8Array Uint16Array Uint32Array Uint8Array Uint8ClampedArray ArrayBuffer DataView JSON Intl arguments require module console window document Symbol Set Map WeakSet WeakMap Proxy Reflect Promise"},r={className:"number",variants:[{begin:"\\b(0[bB][01]+)n?"},{begin:"\\b(0[oO][0-7]+)n?"},{begin:e.C_NUMBER_RE+"n?"}],relevance:0},i={className:"subst",begin:"\\$\\{",end:"\\}",keywords:s,contains:[]},t={begin:"html`",end:"",starts:{end:"`",returnEnd:!1,contains:[e.BACKSLASH_ESCAPE,i],subLanguage:"xml"}},c={begin:"css`",end:"",starts:{end:"`",returnEnd:!1,contains:[e.BACKSLASH_ESCAPE,i],subLanguage:"css"}},o={className:"string",begin:"`",end:"`",contains:[e.BACKSLASH_ESCAPE,i]};i.contains=[e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,t,c,o,r,e.REGEXP_MODE];var l=i.contains.concat([e.C_BLOCK_COMMENT_MODE,e.C_LINE_COMMENT_MODE]),d={className:"params",begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,contains:l};return{name:"JavaScript",aliases:["js","jsx","mjs","cjs"],keywords:s,contains:[{className:"meta",relevance:10,begin:/^\s*['"]use (strict|asm)['"]/},{className:"meta",begin:/^#!/,end:/$/},e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,t,c,o,e.C_LINE_COMMENT_MODE,e.COMMENT("/\\*\\*","\\*/",{relevance:0,contains:[{className:"doctag",begin:"@[A-Za-z]+",contains:[{className:"type",begin:"\\{",end:"\\}",relevance:0},{className:"variable",begin:a+"(?=\\s*(-)|$)",endsParent:!0,relevance:0},{begin:/(?=[^\n])\s/,relevance:0}]}]}),e.C_BLOCK_COMMENT_MODE,r,{begin:/[{,\n]\s*/,relevance:0,contains:[{begin:a+"\\s*:",returnBegin:!0,relevance:0,contains:[{className:"attr",begin:a,relevance:0}]}]},{begin:"("+e.RE_STARTERS_RE+"|\\b(case|return|throw)\\b)\\s*",keywords:"return throw case",contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,e.REGEXP_MODE,{className:"function",begin:"(\\(.*?\\)|"+a+")\\s*=>",returnBegin:!0,end:"\\s*=>",contains:[{className:"params",variants:[{begin:a},{begin:/\(\s*\)/},{begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:s,contains:l}]}]},{begin:/,/,relevance:0},{className:"",begin:/\s/,end:/\s*/,skip:!0},{variants:[{begin:"<>",end:""},{begin:n.begin,end:n.end}],subLanguage:"xml",contains:[{begin:n.begin,end:n.end,skip:!0,contains:["self"]}]}],relevance:0},{className:"function",beginKeywords:"function",end:/\{/,excludeEnd:!0,contains:[e.inherit(e.TITLE_MODE,{begin:a}),d],illegal:/\[|%/},{begin:/\$[(.]/},e.METHOD_GUARD,{className:"class",beginKeywords:"class",end:/[{;=]/,excludeEnd:!0,illegal:/[:"\[\]]/,contains:[{beginKeywords:"extends"},e.UNDERSCORE_TITLE_MODE]},{beginKeywords:"constructor",end:/\{/,excludeEnd:!0},{begin:"(get|set)\\s+(?="+a+"\\()",end:/{/,keywords:"get set",contains:[e.inherit(e.TITLE_MODE,{begin:a}),{begin:/\(\)/},d]}],illegal:/#(?!!)/}}}()); \ No newline at end of file diff --git a/docs/fr/assets/jquery.js b/docs/fr/assets/jquery.js new file mode 100644 index 0000000..9fd22ca --- /dev/null +++ b/docs/fr/assets/jquery.js @@ -0,0 +1,2 @@ +/*! jQuery v3.5.0 | (c) JS Foundation and other contributors | jquery.org/license */ +!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(C,e){"use strict";var t=[],r=Object.getPrototypeOf,s=t.slice,g=t.flat?function(e){return t.flat.call(e)}:function(e){return t.concat.apply([],e)},u=t.push,i=t.indexOf,n={},o=n.toString,v=n.hasOwnProperty,a=v.toString,l=a.call(Object),y={},m=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType},x=function(e){return null!=e&&e===e.window},E=C.document,c={type:!0,src:!0,nonce:!0,noModule:!0};function b(e,t,n){var r,i,o=(n=n||E).createElement("script");if(o.text=e,t)for(r in c)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function w(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[o.call(e)]||"object":typeof e}var f="3.5.0",S=function(e,t){return new S.fn.init(e,t)};function p(e){var t=!!e&&"length"in e&&e.length,n=w(e);return!m(e)&&!x(e)&&("array"===n||0===t||"number"==typeof t&&0+~]|"+M+")"+M+"*"),U=new RegExp(M+"|>"),X=new RegExp(F),V=new RegExp("^"+I+"$"),G={ID:new RegExp("^#("+I+")"),CLASS:new RegExp("^\\.("+I+")"),TAG:new RegExp("^("+I+"|[*])"),ATTR:new RegExp("^"+W),PSEUDO:new RegExp("^"+F),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+R+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/HTML$/i,Q=/^(?:input|select|textarea|button)$/i,J=/^h\d$/i,K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\[\\da-fA-F]{1,6}"+M+"?|\\\\([^\\r\\n\\f])","g"),ne=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},re=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){T()},ae=be(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{H.apply(t=O.call(p.childNodes),p.childNodes),t[p.childNodes.length].nodeType}catch(e){H={apply:t.length?function(e,t){L.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&(T(e),e=e||C,E)){if(11!==p&&(u=Z.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&y(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return H.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&d.getElementsByClassName&&e.getElementsByClassName)return H.apply(n,e.getElementsByClassName(i)),n}if(d.qsa&&!N[t+" "]&&(!v||!v.test(t))&&(1!==p||"object"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===p&&(U.test(t)||z.test(t))){(f=ee.test(t)&&ye(e.parentNode)||e)===e&&d.scope||((s=e.getAttribute("id"))?s=s.replace(re,ie):e.setAttribute("id",s=S)),o=(l=h(t)).length;while(o--)l[o]=(s?"#"+s:":scope")+" "+xe(l[o]);c=l.join(",")}try{return H.apply(n,f.querySelectorAll(c)),n}catch(e){N(t,!0)}finally{s===S&&e.removeAttribute("id")}}}return g(t.replace($,"$1"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function le(e){return e[S]=!0,e}function ce(e){var t=C.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){var n=e.split("|"),r=n.length;while(r--)b.attrHandle[n[r]]=t}function pe(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function de(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return("input"===t||"button"===t)&&e.type===n}}function ge(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ve(a){return le(function(o){return o=+o,le(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ye(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}for(e in d=se.support={},i=se.isXML=function(e){var t=e.namespaceURI,n=(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||"HTML")},T=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:p;return r!=C&&9===r.nodeType&&r.documentElement&&(a=(C=r).documentElement,E=!i(C),p!=C&&(n=C.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",oe,!1):n.attachEvent&&n.attachEvent("onunload",oe)),d.scope=ce(function(e){return a.appendChild(e).appendChild(C.createElement("div")),"undefined"!=typeof e.querySelectorAll&&!e.querySelectorAll(":scope fieldset div").length}),d.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),d.getElementsByTagName=ce(function(e){return e.appendChild(C.createComment("")),!e.getElementsByTagName("*").length}),d.getElementsByClassName=K.test(C.getElementsByClassName),d.getById=ce(function(e){return a.appendChild(e).id=S,!C.getElementsByName||!C.getElementsByName(S).length}),d.getById?(b.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=d.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):d.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},b.find.CLASS=d.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],v=[],(d.qsa=K.test(C.querySelectorAll))&&(ce(function(e){var t;a.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&v.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||v.push("\\["+M+"*(?:value|"+R+")"),e.querySelectorAll("[id~="+S+"-]").length||v.push("~="),(t=C.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||v.push("\\["+M+"*name"+M+"*="+M+"*(?:''|\"\")"),e.querySelectorAll(":checked").length||v.push(":checked"),e.querySelectorAll("a#"+S+"+*").length||v.push(".#.+[+~]"),e.querySelectorAll("\\\f"),v.push("[\\r\\n\\f]")}),ce(function(e){e.innerHTML="";var t=C.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&v.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&v.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&v.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),v.push(",.*:")})),(d.matchesSelector=K.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){d.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",F)}),v=v.length&&new RegExp(v.join("|")),s=s.length&&new RegExp(s.join("|")),t=K.test(a.compareDocumentPosition),y=t||K.test(a.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},D=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!d.sortDetached&&t.compareDocumentPosition(e)===n?e==C||e.ownerDocument==p&&y(p,e)?-1:t==C||t.ownerDocument==p&&y(p,t)?1:u?P(u,e)-P(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e==C?-1:t==C?1:i?-1:o?1:u?P(u,e)-P(u,t):0;if(i===o)return pe(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?pe(a[r],s[r]):a[r]==p?-1:s[r]==p?1:0}),C},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if(T(e),d.matchesSelector&&E&&!N[t+" "]&&(!s||!s.test(t))&&(!v||!v.test(t)))try{var n=c.call(e,t);if(n||d.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){N(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return G.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=h(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=m[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&m(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function D(e,n,r){return m(n)?S.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?S.grep(e,function(e){return e===n!==r}):"string"!=typeof n?S.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(S.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||j,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:q.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof S?t[0]:t,S.merge(this,S.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:E,!0)),N.test(r[1])&&S.isPlainObject(t))for(r in t)m(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=E.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):m(e)?void 0!==n.ready?n.ready(e):e(S):S.makeArray(e,this)}).prototype=S.fn,j=S(E);var L=/^(?:parents|prev(?:Until|All))/,H={children:!0,contents:!0,next:!0,prev:!0};function O(e,t){while((e=e[t])&&1!==e.nodeType);return e}S.fn.extend({has:function(e){var t=S(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,he=/^$|^module$|\/(?:java|ecma)script/i;ce=E.createDocumentFragment().appendChild(E.createElement("div")),(fe=E.createElement("input")).setAttribute("type","radio"),fe.setAttribute("checked","checked"),fe.setAttribute("name","t"),ce.appendChild(fe),y.checkClone=ce.cloneNode(!0).cloneNode(!0).lastChild.checked,ce.innerHTML="",y.noCloneChecked=!!ce.cloneNode(!0).lastChild.defaultValue,ce.innerHTML="",y.option=!!ce.lastChild;var ge={thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};function ve(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&A(e,t)?S.merge([e],n):n}function ye(e,t){for(var n=0,r=e.length;n",""]);var me=/<|&#?\w+;/;function xe(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d\s*$/g;function qe(e,t){return A(e,"table")&&A(11!==t.nodeType?t:t.firstChild,"tr")&&S(e).children("tbody")[0]||e}function Le(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function He(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Oe(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(Y.hasData(e)&&(s=Y.get(e).events))for(i in Y.remove(t,"handle events"),s)for(n=0,r=s[i].length;n").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),E.head.appendChild(r[0])},abort:function(){i&&i()}}});var Ut,Xt=[],Vt=/(=)\?(?=&|$)|\?\?/;S.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=Xt.pop()||S.expando+"_"+Ct.guid++;return this[e]=!0,e}}),S.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Vt.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Vt.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=m(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Vt,"$1"+r):!1!==e.jsonp&&(e.url+=(Et.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||S.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=C[r],C[r]=function(){o=arguments},n.always(function(){void 0===i?S(C).removeProp(r):C[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,Xt.push(r)),o&&m(i)&&i(o[0]),o=i=void 0}),"script"}),y.createHTMLDocument=((Ut=E.implementation.createHTMLDocument("").body).innerHTML="
",2===Ut.childNodes.length),S.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(y.createHTMLDocument?((r=(t=E.implementation.createHTMLDocument("")).createElement("base")).href=E.location.href,t.head.appendChild(r)):t=E),o=!n&&[],(i=N.exec(e))?[t.createElement(i[1])]:(i=xe([e],t,o),o&&o.length&&S(o).remove(),S.merge([],i.childNodes)));var r,i,o},S.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(S.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},S.expr.pseudos.animated=function(t){return S.grep(S.timers,function(e){return t===e.elem}).length},S.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=S.css(e,"position"),c=S(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=S.css(e,"top"),u=S.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),m(t)&&(t=t.call(e,n,S.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):("number"==typeof f.top&&(f.top+="px"),"number"==typeof f.left&&(f.left+="px"),c.css(f))}},S.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){S.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===S.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===S.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=S(e).offset()).top+=S.css(e,"borderTopWidth",!0),i.left+=S.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-S.css(r,"marginTop",!0),left:t.left-i.left-S.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===S.css(e,"position"))e=e.offsetParent;return e||re})}}),S.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;S.fn[t]=function(e){return $(this,function(e,t,n){var r;if(x(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),S.each(["top","left"],function(e,n){S.cssHooks[n]=$e(y.pixelPosition,function(e,t){if(t)return t=Be(e,n),Me.test(t)?S(e).position()[n]+"px":t})}),S.each({Height:"height",Width:"width"},function(a,s){S.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){S.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return $(this,function(e,t,n){var r;return x(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?S.css(e,t,i):S.style(e,t,n,i)},s,n?e:void 0,n)}})}),S.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){S.fn[t]=function(e){return this.on(t,e)}}),S.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),S.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){S.fn[n]=function(e,t){return 0 a",this.offsets=[],this.targets=[],this.activeTarget=null,this.scrollHeight=0,this.$scrollElement.on("scroll.bs.scrollspy",r.proxy(this.process,this)),this.refresh(),this.process()}function s(i){return this.each(function(){var t=r(this),s=t.data("bs.scrollspy"),e="object"==typeof i&&i;s||t.data("bs.scrollspy",s=new o(this,e)),"string"==typeof i&&s[i]()})}o.VERSION="3.3.7",o.DEFAULTS={offset:10},o.prototype.getScrollHeight=function(){return this.$scrollElement[0].scrollHeight||Math.max(this.$body[0].scrollHeight,document.documentElement.scrollHeight)},o.prototype.refresh=function(){var t=this,i="offset",o=0;this.offsets=[],this.targets=[],this.scrollHeight=this.getScrollHeight(),r.isWindow(this.$scrollElement[0])||(i="position",o=this.$scrollElement.scrollTop()),this.$body.find(this.selector).map(function(){var t=r(this),s=t.data("target")||t.attr("href"),e=/^#./.test(s)&&r(s);return e&&e.length&&e.is(":visible")&&[[e[i]().top+o,s]]||null}).sort(function(t,s){return t[0]-s[0]}).each(function(){t.offsets.push(this[0]),t.targets.push(this[1])})},o.prototype.process=function(){var t,s=this.$scrollElement.scrollTop()+this.options.offset,e=this.getScrollHeight(),i=this.options.offset+e-this.$scrollElement.height(),o=this.offsets,r=this.targets,l=this.activeTarget;if(this.scrollHeight!=e&&this.refresh(),i<=s)return l!=(t=r[r.length-1])&&this.activate(t);if(l&&s=o[t]&&(void 0===o[t+1]||s'))&&w.css("position",n.css("position")),(v=function(){var t,o,i;if(!f)return k=x.height(),t=parseInt(b.css("border-top-width"),10),o=parseInt(b.css("padding-top"),10),l=parseInt(b.css("padding-bottom"),10),a=b.offset().top+t+o,c=b.height(),g&&(h=g=!1,null==V&&(n.insertAfter(w),w.detach()),n.css({position:"",top:"",width:"",bottom:""}).removeClass(j),i=!0),p=n.offset().top-(parseInt(n.css("margin-top"),10)||0)-F,d=n.outerHeight(!0),u=n.css("float"),w&&w.css({width:r(n),height:d,display:n.css("display"),"vertical-align":n.css("vertical-align"),float:u}),i?e():void 0})(),m=void 0,y=F,_=A,e=function(){var t,o,i,e,s,r;if(d!==c&&!f)return i=!1,null!=_&&(_-=1)<=0&&(_=A,v(),i=!0),i||x.height()===k||(v(),i=!0),e=Q.scrollTop(),null!=m&&(o=e-m),m=e,g?(C&&(s=c+a + + + + + + + + Wallee Payment Plugin pour Shopware 6 + + + + +
+
+

Wallee Payment Plugin pour Shopware 6

+

Documentation

+ +
+
+
+
+
+
+

+ 1Sommaire

+
+
+
+

Le plugin de paiement Wallee intègre un traitement moderne des paiements dans Shopware 6, offrant des fonctionnalités telles que les paiements basés sur iFrame, les remboursements, les captures et la conformité PCI. Il permet une intégration transparente avec le portail Wallee pour la gestion des transactions et des méthodes de paiement.

+
+

Important : Veuillez noter que seules les mises à jour majeures (par exemple 6.x.0.0) et mineures (par exemple 6.0.x.0) seront testées pour la compatibilité dans les 2 semaines suivant la publication.

+
+
+
+

+ 2Pré-Requis

+
+
+
+

Shopware Version: 6.5.x or 6.6.x (voir tableau de compatibilité).

+
+

PHP: Version minimale requise pour votre installation Shopware (e.g., 7.4+).

+
+

Compte Wallee: Obtenir Space ID, User ID, et clé API du Portail Wallee (voir le Guide de démarrage du Portail).

+
+
+
+

+ 3Compatibilité

+
+
+ ++++++ + + + + + + + + + + + + + + + + + + + + +

Shopware Version

Plugin Version

PHP Version

Support Until

6.6.x

6.x.x

PHP 8.2 and 8.3

En cours

6.5.x - Deprecated

5.x.x

PHP 8.2 and 8.3

October 2024

+
+
+

+ 4Installation

+
+
+
+
+

+ 4.1Via Marketplace

+
+
+
+
    +
  1. +

    Connectez-vous au backend de votre boutique Shopware.

    +
  2. +
  3. +

    Naviguez vers Paramètres → Système → Plugins.

    +
  4. +
  5. +

    Cliquez sur la flèche du menu et sélectionnez le lien d’installation du plugin à installer

    +
    +
    +plugin installation +
    +
    +
  6. +
  7. +

    Activez le plugin wallee Payment à partir du gestionnaire de plugins

    +
  8. +
+
+
+
+

+ 4.2Via Composer (Recommendé)

+
+
+
+
    +
  1. +

    Naviguez jusqu’au répertoire racine de votre Shopware.

    +
  2. +
  3. +

    Exécutez:

    +
    +
    +
    composer require vrpayment/shopware-6
    +php bin/console plugin:refresh
    +php bin/console plugin:install --activate --clearCache VRPaymentPayment
    +
    +
    +
  4. +
+
+
+
+
+

+ 5Guide de démarrage pour le Portail

+
+
+
+

Allez sur VR Payment et créez un Compte si vous n’en avez pas déjà un

+
+ + + + + +
+
Tip
+
+Veuillez sélectionner le plan d’abonnement approprié - il doit prendre en charge les transactions de e-commerce. +
+
+
+

+ 5.1Créez la clé API:

+
+
+
+
    +
  1. +

    Une fois que votre compte est activé, naviguez vers Compte → Utilisateurs de l’application

    +
    +
    +application users +
    +
    +
  2. +
  3. +

    Cliquez sur Créer un utilisateur d’application

    +
    +
    +user +
    +
    +
  4. +
  5. +

    Spécifiez un nom pour cette Clé API - par exemple Test Shop - et cliquez sur Créer un utilisateur user

    +
    +
    +api key +
    +
    +
  6. +
  7. +

    Votre utilisateur d’application est ainsi créé. Veuillez copier l’User ID et la Clé d’authentification, car vous en aurez besoin pour reliez votre magasin au portail.

    +
    +
    +token +
    +
    +
  8. +
  9. +

    Enfin, vous devrez accorder certaines autorisations à cet utilisateur d’application afin qu’il puisse communiquer avec votre espace. Pour ce faire, sous Rôle, naviguez jusqu’à Gérer.

    +
    +
    +roles +
    +
    +
  10. +
  11. +

    Cliquez sur le sign + à côté de Space Roles

    +
    +
    +assign role +
    +
    +
  12. +
  13. +

    Veuillez ajouter le rôle Space Admin et cliquez sur and click on Assign Role.

    +
    + + + + + +
    +
    Tip
    +
    +Veuillez noter que le chargement des rôles peut durer quelques secondes. +
    +
    +
    +
    +loading roles +
    +
    +
  14. +
  15. +

    Enfin, cliquez sur Enregistrer les Roles; vous devrez entrer votre mot de passe pour confirmer.

    +
    +
    +save role +
    +
    +
  16. +
+
+
+
+

+ 5.2Configurer les modes de paiement

+
+
+
+

Vous pouvez maintenant configurer les méthodes de paiement que vous souhaitez utiliser ; à des fins de test, nous utiliserons le Bogus Processor. Veuillez noter que si vous souhaitez effectuer une transaction de production, vous pouvez utiliser notre offre Payfac ou vous connecter directement à un PSP spécifique (Worldline ; ACI ; …​).

+
+
    +
  1. +

    Naviguez vers Space → Paramètres et cliquez sur Processors

    +
    +
    +payment settings +
    +
    +
  2. +
  3. +

    Cliquez sur Configurez le processeur

    +
  4. +
  5. +

    Sélectionnez Bogus Processor et cliquez sur Continue

    +
    +
    +bogus processor +
    +
    +
  6. +
  7. +

    Définissez le nom que vous souhaitez donner à votre processeur - e.g. Test Processor - et cliquez sur Créer

    +
    +
    +name processor +
    +
    +
  8. +
  9. +

    Sélectionnez tous les connecteurs qui s’appliquent et cliquez sur Enregistrer

    +
    + + + + + +
    +
    Tip
    +
    +Veuillez noter que les connecteurs semblent faire double emploi, mais c’est parce que l’un concerne le paiement par terminal physique et l’autre le paiement par ecommerce. +
    +
    +
    +
    +connectors +
    +
    +
  10. +
+
+

Les méthodes de paiement sont désormais disponibles dans le portail.

+
+
+
+
+

+ 6Guide de démarrage pour Shopware

+
+
+
+
    +
  1. +

    Identifiants API

    +
    +
      +
    1. +

      Naviguez vers Shopware Admin → Paramètres → Extensions → Wallee Payment et cliquez sur Sauvegarder.

      +
    2. +
    3. +

      Entrez votre Space ID, User ID, et clé API

      +
      +
        +
      1. +

        Pour le Space id; vous pouvez naviguer vers le Space et il sera fourni - par exemple, l’identifiant du Space id = 76231

        +
        +
        +plugin configuration +
        +
        +
      2. +
      +
      +
    4. +
    +
    +
  2. +
  3. +

    Méthodes de Paiment

    +
    +

    Les méthodes de paiement disponibles à la caisse sont gérées par le Portail Wallee. Si vous souhaitez désactiver une méthode de paiement, vous devez la désactiver à partir du portail.

    +
    +
    + + + + + +
    +
    Note
    +
    +Veuillez noter qu’en raison de la synchronisation entre le portail et la boutique, vous pouvez effectuer la même opération à partir de la boutique, sous Paramètres → Méthodes de paiement. +
    +
    +
    +
      +
    1. +

      a.Allez dans Space → Paramètres → Paiements → Méthodes de Paiement

      +
      +
      +payment methods +
      +
      +
    2. +
    3. +

      Sélectionnez le mode de paiement que vous souhaitez désactiver. Cliquez sur le curseur pour le désactiver → il devrait devenir inactif.

      +
      +
      +cc enable +
      +
      +
      +
      +cc disable +
      +
      +
      + + + + + +
      +
      Tip
      +
      +Si vous souhaitez désactiver uniquement un Connecteur d’un mode de paiement (par exemple, Mastercard pour une carte de crédit), veuillez consulter la section Connecteur. +
      +
      +
    4. +
    +
    +
  4. +
  5. +

    Options

    +
    +
      +
    1. +

      Space View Id: Ce champ vous permet d’appliquer un style personnalisé au formulaire et à la page de paiement. Le style est défini dans les paramètres de votre espace dans le portail.

      +
      + + + + + +
      +
      Note
      +
      +Veuillez noter que si vous n’utilisez pas Space View Id, cette option doit rester vide. +
      +
      +
    2. +
    3. +

      Intégration: Le paramètre Options d’intégration détermine comment le formulaire de paiement est affiché pendant la procédure de paiement. Les options suivantes sont disponibles :

      +
      +
        +
      1. +

        IFrame: Incorpore le formulaire de paiement directement dans la page de paiement de Shopware pour une expérience transparente.

        +
      2. +
      3. +

        Lightbox: Ouvre une fenêtre contextuelle sécurisée pour que les clients puissent effectuer leur paiement sans quitter la page de paiement.

        +
      4. +
      5. +

        Payment Page: Redirige les clients vers une page de paiement dédiée, hébergée par le fournisseur de paiement.

        +
      6. +
      +
      +
    4. +
    5. +

      Consistence des articles: Shopware calcule les taxes au niveau de l’article, ce qui peut entraîner des différences mineures (généralement de quelques centimes) entre le total des taxes de la commande et le prix affiché. Cet écart est dû à des différences d’arrondi lors du calcul de chaque article. Si le paramètre « Renforcer la cohérence » est activé, le portail rejettera automatiquement les commandes présentant de tels écarts. Pour éviter les problèmes liés au traitement des paiements, nous vous recommandons de désactiver ce paramètre, à moins qu’une validation stricte du total des taxes ne soit nécessaire.

      +
    6. +
    7. +

      Envoyer un email de confirmation de commande: Activez cette option pour envoyer des emails de confirmation de commande directement depuis Shopware au lieu du portail.

      +
      +
      +order confirmation email +
      +
      +
    8. +
    +
    +
  6. +
+
+
+
+

+ 7Différents Etats pour une Transaction

+
+
+
+

Le processus de paiement de wallee est complètement standardisé pour chaque méthode de paiement que vous pouvez traiter. Cela vous permet d’ajouter simplement une méthode de paiement ou un processeur sans modifier la configuration de votre Shopware. Une vue d’ensemble des états et des processus de paiement de wallee peut être trouvée dans la documentation sur les paiments..

+
+

Dans la section suivante, nous vous donnons un aperçu de la façon dont les états de wallee sont mappés dans le graphique des états de Shopware pour les commandes et les états de paiement.

+
+
+

+ 7.1Cartographie des différents états d’une commande de Shopware

+
+
+
+

Actuellement, nous ne modifions pas l’état de la commande. Nous ne modifions que l’état du paiement et l’état de la livraison.

+
+
+

+ 7.1.1Remarque générales concernant les status des commandes

+
+
+
+

Nous vous recommandons de ne modifier l’état de la commande que lorsque l’état du paiement est définitif (Payée, Annulée ou Echouée) .

+
+
+
+
+

+ 7.2Cartographie des différents états du paiement de Shopware

+
+
+
+

Vous trouverez ci-dessous un diagramme qui montre l’association des différents états de Shopware pour l’état de paiement pour wallee, ainsi que des informations supplémentaires sur les transitions entre les états.

+
+
+shopware 6 stage graph order +
+
+
    +
  1. +

    Si la transaction est Autorisée dans wallee, le statut du paiement de la commande dans le Shopware est marqué comme étant En Cours.

    +
  2. +
  3. +

    Si la transaction échoue avant ou pendant le processus d’autorisation, le statut du paiement de la commande du Shopware est marqué comme Échouée.

    +
  4. +
  5. +

    Si la transaction échoue après l’autorisation, le statut du paiement de la commande du Shopware est marqué comme Annulée.

    +
  6. +
  7. +

    Si la facture de la transaction dans wallee est marquée comme Payée ou Non Applicable, le statut du paiement de la commande dans le Shopware est marqué comme Payée.

    +
  8. +
+
+
+

+ 7.2.1Remarques générales concernant les différents status pour les paiements

+
+
+
+

Nous vous recommandons de ne pas modifier manuellement l’état du paiement. Si vous le faites, il peut être modifié à nouveau par le plugin.

+
+
+
+
+

+ 7.3Carthographie des différents états de livraison chez Shopware

+
+
+
+

Vous trouverez ci-dessous un diagramme qui montre les différents états pour la livraison chez Shopware avec des informations supplémentaires pour les transitions d’états.

+
+
+shopware 6 stage graph delivery +
+
+
    +
  1. +

    Si la transaction est confirmée dans wallee, le statut de livraison de la commande dans le Shopware est indiqué comme étant En Attente.

    +
  2. +
  3. +

    Si la transaction dans wallee est marquée comme Délivrée, le statut de livraison de la commande Shopware est marqué comme Ouvert.

    +
  4. +
  5. +

    Si la transaction est en statut Déclinée, Échouée ou Annulée, le statut de livraison de la commande du Shopware est marqué comme Annulée.

    +
  6. +
+
+
+
+
+

+ 8Gestion des Transactions

+
+
+
+

Vous pouvez capturer, annuler et rembourser des transactions directement depuis le backend de Shopware. Veuillez noter que si vous remboursez, annulez ou capturez des transactions dans wallee, les événements seront synchronisés dans Shopware. Cependant, il y a quelques limitations (voir ci-dessous).

+
+
+

+ 8.1Complete (capture) an order

+
+
+
+

Vous avez la possibilité pour vos transactions de n’autoriser le paiement qu’une fois la commande passée. Dans la configuration du connecteur, vous avez la possibilité, si la méthode de paiement le permet, de définir si le paiement doit être effectué immédiatement ou différé.

+
+

Pour capturer une transaction, ouvrez la commande et cliquez sur le bouton Terminer.

+
+ + + + + +
+
Note
+
+Lorsque le paiement est en attente dans wallee, la commande reste en attente. +
+
+
+capture transaction +
+
+

Finalisation du paiement de manière différée

+
+

Les détaillants souhaitent souvent autoriser les transactions et lancer le processus d’exécution une fois que tous les articles peuvent être expédiés. Cela est également possible avec wallee.

+
+

Cependant, certains processus doivent être suivis. Si vous avez configuré la finalisation du paiement pour qu’il soit différé, vous devez capturer la transaction avant d’initier l’expédition, car il peut toujours arriver qu’une finalisation échoue. Si vous voulez être sûr de ne pas expédier d’articles pour lesquels vous n’avez pas été payé, vous devez reporter l’expédition jusqu’à ce que l’état Confirmé soit atteint. Au départ, la transaction sera dans l’état Autorisé dans wallee et En cours dans Shopware. Si vous souhaitez lancer le processus d’exécution, assurez-vous de lancer le processus d’achèvement comme décrit ci-dessus. Une fois le processus terminé avec succès, la commande passera à l’état Confirmée dans wallee et à l’état Payée dans Shopware. Vous pouvez maintenant lancer le processus de livraison .

+
+
+
+

+ 8.2Annuler une transaction

+
+
+
+

Pour annuler une transaction, ouvrez la commande et cliquez sur le bouton Annuler l’autorisation.

+
+ + + + + +
+
Note
+
+Vous ne pouvez annuler que les transactions qui ne sont pas encore complétée.. +
+
+
+void transaction +
+
+
+
+

+ 8.3Remboursement d’une transaction

+
+
+
+

Vous avez la possibilité de rembourser des transactions déjà effectuées. Pour ce faire, ouvrez l’ordre saisi. En cliquant sur les 3 points (…​) sur un article, vous pouvez rembourser l’article partiellement (s’il a une quantité supérieure à 1), ou vous pouvez rembourser l’article en entier. Si la méthode de paiement ne prend pas en charge les remboursements, vous n’aurez pas la possibilité d’effectuer des remboursements en ligne.

+
+
+refund transaction +
+
+

Vous pouvez effectuer autant de remboursements individuels que vous le souhaitez jusqu’à ce que vous ayez atteint le montant total de la commande initiale. Le statut de la commande passe alors automatiquement à completée.

+
+ + + + + +
+
Note
+
+Il peut s’écouler un certain temps avant que vous ne voyiez le remboursement dans Shopware. Les remboursements ne seront visibles que lorsqu’ils auront été traités avec succès. +
+
+
+
+

+ 8.4Commandes en attente

+
+
+
+

La livraison ne doit pas être effectuée tant que l’état de la livraison est en attente. Cela se produit lorsque la transaction dans wallee n’a pas atteint l’état Confirmé.

+
+

Il y a essentiellement deux raisons pour lesquelles cela peut se produire :

+
+
    +
  • +

    La transaction n’est pas terminée. Dans ce cas, vous devez compléter la transaction comme indiqué ci-dessus..

    +
  • +
  • +

    Nous ne sommes pas en mesure de déterminer si vous devez honorer la commande. La décision de livraison est prise automatiquement. Si cela ne se produit pas dans le délai défini, wallee génère une tâche manuelle que vous devez observer et suivre les instructions.

    +
  • +
+
+

Vous trouverez plus d’informations sur les tâches manuelles dans notre Documentation sur les Tâches Manuelles..

+
+
+
+

+ 8.5Limites de la synchronisation entre wallee et Shopware

+
+
+
+

Veuillez noter que les captures, annulations et remboursements effectués dans wallee sont synchronisés. Cependant, il y a quelques limitations. Dans wallee, vous pouvez modifier le prix unitaire et la quantité en une seule fois. Cela n’est pas possible dans le backend du Shopware. Nous vous recommandons donc d’effectuer les remboursements toujours dans le backend de Shopware et non dans wallee. Si un remboursement ne peut pas être synchronisé, il sera envoyé au processeur, mais il se peut que vous ne le voyiez pas dans votre backend Shopware.

+
+

Vous pouvez trouver plus d’informations sur les remboursements dans wallee dans notre Documentation sur les Remboursements..

+
+
+
+

+ 8.6Tokenisation

+
+
+
+

Si la méthode de paiement prend en charge la tokenisation, vous pouvez stocker les détails de paiement de votre client pour des achats futurs. Afin d’utiliser cette fonctionnalité, assurez-vous que le Mode de paiement en un clic dans la configuration de votre méthode de paiement est réglé sur autoriser ou forcer le stockage.

+
+ + + + + +
+
Note
+
+La tokenisation n’est pas disponible pour les paiements par les invités. +
+
+
+
+

+ 8.7Caractéristiques Pricinpales

+
+
+
+
    +
  • +

    Intégration iFrame: Intégrez des formulaires de paiement directement dans votre checkout.

    +
  • +
  • +

    Remboursements & Captures: Déclenchez des remboursements complets/partiels et des captures à partir de Shopware ou du portail Wallee.

    +
  • +
  • +

    Support Multi-Magasins: Gérez les configurations sur plusieurs magasins.

    +
  • +
  • +

    Mises à jour automatiques: Les méthodes de paiement se synchronisent dynamiquement via l’API Wallee.

    +
  • +
+
+
+
+

+ 8.8Troubleshooting

+
+
+
+
    +
  • +

    Logs: Vérifiez les logs des payments avec:

    +
    +
    +
    tail -f var/log/whitelabelname_payment*.log
    +
    +
    +
  • +
  • +

    Problèmes courants:

    +
    +
      +
    • +

      Assurez-vous que la commande composer update whitelabelname/shopware-6 est exécutée après les mises à jour.

      +
    • +
    • +

      Vérifier que les identifiants de l’API correspondent à votre compte Wallee.

      +
    • +
    +
    +
  • +
+
+
+
+

+ 8.9FAQs

+
+
+
+

Q: Comment s’assurer que la connexion entre le portail et la boutique fonctionne? +A: Vous devez vérifier que les webhooks ont été correctement créés. Pour ce faire, naviguez vers votre espace dans le portail, allez dans Paramètres → Général → Webhook Listeners.

+
+
+webhooks +
+
+
+webhook listeners +
+
+

Q: Ce plugin prend-il en charge les paiements en un clic ? +A: Oui, via la tokenisation dans le portail Wallee.

+
+

Q: Comment gérer la conformité PCI ? +A: Le plugin utilise l’intégration iFrame, réduisant les exigences PCI à SAQ-A.

+
+

Q: Le plugin prend-il en charge Apple Pay ? +A: Oui, le plugin prend en charge les portefeuilles comme Apple Pay.

+
+
+
+
+

+ 9Changelog

+
+
+
+

Pour les mises à jour spécifiques à une version, voir les GitHub Releases.

+
+
+
+

+ 10Contribuer

+
+
+
+

Signaler les problèmes via GitHub Issues.

+
+

Suivez le Shopware Plugin Base Guide pour le développement.

+
+
+
+

+ 11Support

+
+
+
+

Si vous avez besoin d’aide, n’hésitez pas à contacter notre support.

+
+
+
+
+ +
+
+ + + + + + + + diff --git a/docs/it/assets/base.css b/docs/it/assets/base.css new file mode 100644 index 0000000..bb25826 --- /dev/null +++ b/docs/it/assets/base.css @@ -0,0 +1,692 @@ +* { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} + +*:before, *:after { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} + +html { + font-size: 100%; + font-family: sans-serif; + line-height: 1.15; + -webkit-text-size-adjust: 100%; + -ms-text-size-adjust: 100%; + -ms-overflow-style: scrollbar; + -webkit-tap-highlight-color: transparent; +} + +@-ms-viewport { + width: device-width; +} + +article, aside, dialog, figcaption, figure, footer, header, hgroup, main, nav, section { + display: block; +} + +body { + margin: 0; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; + font-size: 1rem; + font-weight: 300; + line-height: 1.5; + color: #212529; + text-align: left; + background-color: #fff; + padding-right: 0 !important; + position: relative; +} + +html,body { + width: 100%; + height: 100%; +} + +[tabindex="-1"]:focus { + outline: 0 !important; +} + +hr { + box-sizing: content-box; + height: 0; + overflow: visible; +} + +h1, h2, h3, h4, h5, h6 { + margin-top: 0; + margin-bottom: 0.5rem; +} + +h1 { + font-size: 2.5rem; + font-weight: 200; + margin-bottom: 1.875rem; +} + +h2 { + font-size: 1.625rem; + font-weight: 300; + margin-bottom: 1.3rem; +} + +h3 { + font-size: 1.3rem; + font-weight: 300; + margin-top: 1.3rem; +} + +h4 { + font-size: 1.125rem; + font-weight: 400; + margin-top: 1.875rem; + margin-bottom: 1.3rem; +} + +h5 { + font-size: 1rem; + font-weight: bold; + margin-top: 1.875rem; + margin-bottom: 1.3rem; +} + +p { + margin-top: 0; + margin-bottom: 1rem; +} + +abbr[title], abbr[data-original-title] { + text-decoration: underline; + -webkit-text-decoration: underline dotted; + text-decoration: underline dotted; + cursor: help; + border-bottom: 0; +} + +address { + margin-bottom: 1rem; + font-style: normal; + line-height: inherit; +} + +ol, ul, dl { + margin-top: 0; + margin-bottom: 1rem; +} + +ol ol, ul ul, ol ul, ul ol { + margin-bottom: 0; +} + +dt { + font-weight: 700; +} + +dd { + margin-bottom: .5rem; + margin-left: 0; +} + +blockquote { + margin: 0 0 1rem; +} + +dfn { + font-style: italic; +} + +b, strong { + font-weight: bold; +} + +small { + font-size: 80%; +} + +sub, sup { + position: relative; + font-size: 75%; + line-height: 0; + vertical-align: baseline; +} + +sub { + bottom: -.25em; +} + +sup { + top: -.5em; +} + +a { + color: #007bff; + text-decoration: none; + background-color: transparent; + -webkit-text-decoration-skip: objects; +} + +a:hover { + color: #0056b3; + text-decoration: underline; +} + +a:not([href]):not([tabindex]) { + color: inherit; + text-decoration: none; +} + +a:not([href]):not([tabindex]):hover, a:not([href]):not([tabindex]):focus { + color: inherit; + text-decoration: none; +} + +a:not([href]):not([tabindex]):focus { + outline: 0; +} + +pre, code, kbd, samp { + font-family: monospace, monospace; + font-size: 90%; + padding: 2px 4px 2px 4px; + color: #c7254e; + background-color: #f9f2f4; + border-radius: 4px; +} + +pre { + margin-top: 0; + margin-bottom: 1rem; + overflow: auto; + -ms-overflow-style: scrollbar; +} + +figure { + margin: 0 0 1rem; +} + +img { + vertical-align: middle; + border-style: none; +} + +svg:not(:root) { + overflow: hidden; +} + +table { + border-collapse: collapse; + background-color: transparent; +} + +caption { + padding-top: 8px; + padding-bottom: 8px; + color: #a7a7a7; + text-align: left; +} + +th { + text-align: left; +} + +output { + display: inline-block; +} + +summary { + display: list-item; + cursor: pointer; +} + +template { + display: none; +} + +table col[class*="col-"] { + position: static; + float: none; + display: table-column; +} + +table td[class*="col-"],table th[class*="col-"] { + position: static; + float: none; + display: table-cell; +} + +ol.glossary { + counter-reset: glossary-counter; + list-style: none; + padding-left: 40px; +} + +ol.glossary li { + counter-increment: glossary-counter; + position: relative; +} + +ol.glossary li::before { + content: counter(glossary-counter); + position: absolute; + background-color: #73EAA9; + color: #fff; + border-radius: 100px; + width: 24px; + left: -40px; + text-align: center; + font-weight: bold; + line-height: 24px; +} + +.layout-wrapper { + position: relative; + width: 100%; + height: auto; + min-height: 100%; +} + +.layout-title { + padding: 1.875rem 0; + border-bottom: 1px solid #f0f0f0; +} + +.layout-title h1 { + font-size: 3rem; + font-weight: 200; + text-align: center; + margin: 0; +} + +.layout-title h2 { + font-size: 2rem; + font-weight: 200; + text-align: center; + color: #999; + margin-bottom: 0; +} + +.layout-navigation .nav { + padding: 1.875rem 0; + border-bottom: 1px solid #f0f0f0; + text-align: center; + background: #fff; + z-index: 1000; +} + +.layout-navigation .nav > li { + display: inline-block; +} + +.layout-navigation .nav > li > a { + border: 1px solid #007bff; + border-radius: 100px; + padding: 6px 12px; + margin: 0 8px; +} + +.layout-navigation .nav > li > a:hover, .layout-navigation .nav > li > a:active, .layout-navigation .nav > li > a:focus { + border: 1px solid #0056b3; + color: #0056b3; + text-decoration: none; +} + +.layout-content { + position: relative; +} + +.layout-content:before, .layout-content:after { + content: " "; + display: table; +} + +.layout-content:after { + clear: both; +} + +.layout-content .col-right { + width: 25%; + float: right; +} + +.layout-content .col-right-wrapper { + width: 100%; + position: relative; + overflow-x: hidden; + overflow-y: auto; + padding: 2.5rem 2rem 0; +} + +.layout-content .col-body { + width: 75%; + float: left; +} + +.layout-content .col-body:before, .layout-content .col-body:after { + content: " "; + display: table; +} + +.layout-content .col-body:after { + clear: both; +} + +.layout-content .col-body-wrapper { + position: relative; + width: 100%; + padding: 2.5rem 2rem 0; +} + +.nav { + padding-left: 0; + margin-bottom: 0; + list-style: none; + line-height: 2; +} + +.table-of-contents { + padding: 1.25rem 0; +} + +.table-of-contents .nav > li > a { + display: flex; +} + +.table-of-contents .nav > li > a .item-number { + display: none; +} + +.table-of-contents .nav > li > a .item-title { + color: #212529; + overflow: hidden; + text-overflow: ellipsis; + flex-grow: 1; + white-space: nowrap; +} + +.table-of-contents .nav > li > a .item-title:hover { + color: #0056b3; +} + +.table-of-contents .nav > li.extended > a .item-title, .table-of-contents .nav > li.active > a .item-title, .table-of-contents .nav > li.extended > a .item-title:hover, .table-of-contents .nav > li.active > a .item-title:hover { + color: #007bff; +} + +.table-of-contents > .nav > li > .nav { + display: none; + margin-bottom: 0.5rem; +} + +.table-of-contents > .nav > li > .nav > li > a { + padding-left: 1rem; +} + +.table-of-contents > .nav > li > .nav > li > a .item-title { + font-size: 0.875rem; +} + +.table-of-contents > .nav > li > .nav > li > .nav > li > a { + padding-left: 2rem; +} + +.table-of-contents > .nav > li > .nav > li > .nav > li > a .item-title { + font-size: 0.75rem; +} + +.table-of-contents > .nav > li.active > .nav { + display: block; +} + +.chapter { + margin: 0 0 6rem; + font-weight: 300; +} + +.section { + margin-top: 3rem; +} + +.chapter > .chapter-title h1, .chapter > .chapter-title h2, .chapter > .chapter-title h3, .chapter > .chapter-title h4, .chapter > .chapter-title h5, .chapter > .chapter-title h6, .section > .section-title h1, .section > .section-title h2, .section > .section-title h3, .section > .section-title h4, .section > .section-title h5, .section > .section-title h6 { + margin-top: 0; +} + +.chapter > .chapter-title h1 { + border-bottom: 2px solid #eeeeee; + margin-bottom: 1.5rem; + padding-bottom: 0.2em; +} + +.chapter-title .title-number, .section-title .title-number { + display: none; +} + +.paragraph { + line-height: 1.75em; +} + +.paragraph + .paragraph { + margin-top: 1em; +} + +.dlist { + margin-top: 30px; +} + +.dlist dl dt { + float: left; + width: 160px; + clear: left; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.dlist dl dd { + margin-left: 180px; +} + +.ulist { + margin-top: 30px; +} + +.imageblock { + margin: 30px auto; +} + +.imageblock .content img { + max-width: 100%; +} + +.imageblock .title { + padding: 10px 0 0; +} + +.exampleblock, .quoteblock, .literalblock { + background: #f5f4f4; + padding: 20px; + margin: 30px 0; +} + +.exampleblock .title, .quoteblock .title, .literalblock .title { + text-transform: uppercase; + font-size: 0.75em; + font-weight: 400; + color: #979797; + margin-bottom: 10px; +} + +.quoteblock blockquote { + margin: 0; + padding: 0; + border: 0; + font-size: inherit; +} + +.quoteblock blockquote p:last-child, .quoteblock blockquote ul:last-child, .quoteblock blockquote ol:last-child { + margin-bottom: 9px; +} + +.literalblock pre { + border: 0; + padding: 0; + margin: 0; +} + +.listingblock { + margin: 30px 0; +} + +.listingblock pre { + border: 0; + padding: 0; + margin: 0; +} + +.listingblock pre code { + display: block; + padding: 20px; +} + +.admonitionblock { + line-height: 1.8em; + padding: 20px; + margin: 30px 0; +} + +.admonitionblock .icon { + display: none; +} + +.admonitionblock.important { + background: #fce1e1; + border-left: 5px solid #ff6060; +} + +.admonitionblock.note, .admonitionblock.tip { + background: #e0f2fc; + border-left: 5px solid #88d5ff; +} + +.admonitionblock.caution, .admonitionblock.warning { + background: #fdf3d8; + border-left: 5px solid #f1c654; +} + +table.tableblock { + background-color: #fff; + width: 100%; + max-width: 100%; + margin-bottom: 18px; + margin: 30px 0; +} + +table.tableblock > thead > tr > th, table.tableblock > tbody > tr > th, table.tableblock > tfoot > tr > th, table.tableblock > thead > tr > td, table.tableblock > tbody > tr > td, table.tableblock > tfoot > tr > td { + padding: 8px; + line-height: 1.42857143; + vertical-align: top; + border-top: 1px solid #eee; +} + +table.tableblock > thead > tr > th { + vertical-align: bottom; + border-bottom: 2px solid #eee; +} + +table.tableblock > caption + thead > tr:first-child > th, table.tableblock > colgroup + thead > tr:first-child > th, table.tableblock > thead:first-child > tr:first-child > th, table.tableblock > caption + thead > tr:first-child > td, table.tableblock > colgroup + thead > tr:first-child > td, table.tableblock > thead:first-child > tr:first-child > td { + border-top: 0; +} + +table.tableblock > tbody + tbody { + border-top: 2px solid #eee; +} + +table.tableblock .table { + background-color: #fff; +} + +table.tableblock > tbody > tr:nth-of-type(odd) { + background-color: #f7f7f7; +} + +table.tableblock > thead > tr > th p:last-child, table.tableblock > tbody > tr > th p:last-child, table.tableblock > tfoot > tr > th p:last-child, table.tableblock > thead > tr > td p:last-child, table.tableblock > tbody > tr > td p:last-child, table.tableblock > tfoot > tr > td p:last-child { + margin-bottom: 0; +} + +.loaded .table-of-contents .nav .nav { + display: none; +} + +@media (min-width: 1200px) { + .layout-wrapper .layout-title, .layout-wrapper .layout-navigation, .layout-wrapper .layout-content { + max-width: 1200px; + margin-left: auto; + margin-right: auto; + } +} + +@media (max-width: 991px) { + html { + font-size: 90%; + } + + .layout-content .col-right { + display: none; + } + + .layout-content .col-body { + width: 100%; + } +} + +@media print { + body { + color: #000; + font-family: Georgia, "Times New Roman", Times, serif; + } + + a { + color: #000; + } + + h1 { + font-size: 1.6rem; + } + + h2 { + font-size: 1.4rem; + } + + h3 { + font-size: 1.2rem; + } + + h4 { + font-size: 1rem; + } + + h5 { + font-size: 0.9rem; + } + + .layout-title h1 { + font-size: 2rem; + } + + .layout-content .col-right { + display: none; + } + + .layout-content .col-body { + width: 100%; + } + + .chapter { + margin-bottom: 3rem; + } + + .section { + margin-top: 2rem; + } +} diff --git a/docs/it/assets/base.js b/docs/it/assets/base.js new file mode 100644 index 0000000..df5c13f --- /dev/null +++ b/docs/it/assets/base.js @@ -0,0 +1,14 @@ +(function($){ + + hljs.initHighlightingOnLoad(); + + $(document).ready(function(){ + $('.col-right-wrapper').stick_in_parent({ + parent: '.layout-content' + }); + $('body').scrollspy({ + target: '.table-of-contents' + }); + }); + +})(jQuery); diff --git a/docs/it/assets/highlight.js b/docs/it/assets/highlight.js new file mode 100644 index 0000000..9a30d5f --- /dev/null +++ b/docs/it/assets/highlight.js @@ -0,0 +1,6 @@ +/* + Highlight.js 10.0.3 (a4b1bd2d) + License: BSD-3-Clause + Copyright (c) 2006-2020, Ivan Sagalaev +*/ +var hljs=function(){"use strict";function e(n){Object.freeze(n);var t="function"==typeof n;return Object.getOwnPropertyNames(n).forEach((function(r){!n.hasOwnProperty(r)||null===n[r]||"object"!=typeof n[r]&&"function"!=typeof n[r]||t&&("caller"===r||"callee"===r||"arguments"===r)||Object.isFrozen(n[r])||e(n[r])})),n}function n(e){return e.replace(/&/g,"&").replace(//g,">")}function t(e){var n,t={},r=Array.prototype.slice.call(arguments,1);for(n in e)t[n]=e[n];return r.forEach((function(e){for(n in e)t[n]=e[n]})),t}function r(e){return e.nodeName.toLowerCase()}var a=Object.freeze({__proto__:null,escapeHTML:n,inherit:t,nodeStream:function(e){var n=[];return function e(t,a){for(var i=t.firstChild;i;i=i.nextSibling)3===i.nodeType?a+=i.nodeValue.length:1===i.nodeType&&(n.push({event:"start",offset:a,node:i}),a=e(i,a),r(i).match(/br|hr|img|input/)||n.push({event:"stop",offset:a,node:i}));return a}(e,0),n},mergeStreams:function(e,t,a){var i=0,s="",o=[];function l(){return e.length&&t.length?e[0].offset!==t[0].offset?e[0].offset"}function u(e){s+=""}function d(e){("start"===e.event?c:u)(e.node)}for(;e.length||t.length;){var g=l();if(s+=n(a.substring(i,g[0].offset)),i=g[0].offset,g===e){o.reverse().forEach(u);do{d(g.splice(0,1)[0]),g=l()}while(g===e&&g.length&&g[0].offset===i);o.reverse().forEach(c)}else"start"===g[0].event?o.push(g[0].node):o.pop(),d(g.splice(0,1)[0])}return s+n(a.substr(i))}});const i="
",s=e=>!!e.kind;class o{constructor(e,n){this.buffer="",this.classPrefix=n.classPrefix,e.walk(this)}addText(e){this.buffer+=n(e)}openNode(e){if(!s(e))return;let n=e.kind;e.sublanguage||(n=`${this.classPrefix}${n}`),this.span(n)}closeNode(e){s(e)&&(this.buffer+=i)}span(e){this.buffer+=``}value(){return this.buffer}}class l{constructor(){this.rootNode={children:[]},this.stack=[this.rootNode]}get top(){return this.stack[this.stack.length-1]}get root(){return this.rootNode}add(e){this.top.children.push(e)}openNode(e){let n={kind:e,children:[]};this.add(n),this.stack.push(n)}closeNode(){if(this.stack.length>1)return this.stack.pop()}closeAllNodes(){for(;this.closeNode(););}toJSON(){return JSON.stringify(this.rootNode,null,4)}walk(e){return this.constructor._walk(e,this.rootNode)}static _walk(e,n){return"string"==typeof n?e.addText(n):n.children&&(e.openNode(n),n.children.forEach(n=>this._walk(e,n)),e.closeNode(n)),e}static _collapse(e){e.children&&(e.children.every(e=>"string"==typeof e)?(e.text=e.children.join(""),delete e.children):e.children.forEach(e=>{"string"!=typeof e&&l._collapse(e)}))}}class c extends l{constructor(e){super(),this.options=e}addKeyword(e,n){""!==e&&(this.openNode(n),this.addText(e),this.closeNode())}addText(e){""!==e&&this.add(e)}addSublanguage(e,n){let t=e.root;t.kind=n,t.sublanguage=!0,this.add(t)}toHTML(){return new o(this,this.options).value()}finalize(){}}function u(e){return e&&e.source||e}const d="(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)",g={begin:"\\\\[\\s\\S]",relevance:0},h={className:"string",begin:"'",end:"'",illegal:"\\n",contains:[g]},f={className:"string",begin:'"',end:'"',illegal:"\\n",contains:[g]},p={begin:/\b(a|an|the|are|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|they|like|more)\b/},m=function(e,n,r){var a=t({className:"comment",begin:e,end:n,contains:[]},r||{});return a.contains.push(p),a.contains.push({className:"doctag",begin:"(?:TODO|FIXME|NOTE|BUG|XXX):",relevance:0}),a},b=m("//","$"),v=m("/\\*","\\*/"),x=m("#","$");var _=Object.freeze({__proto__:null,IDENT_RE:"[a-zA-Z]\\w*",UNDERSCORE_IDENT_RE:"[a-zA-Z_]\\w*",NUMBER_RE:"\\b\\d+(\\.\\d+)?",C_NUMBER_RE:d,BINARY_NUMBER_RE:"\\b(0b[01]+)",RE_STARTERS_RE:"!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~",BACKSLASH_ESCAPE:g,APOS_STRING_MODE:h,QUOTE_STRING_MODE:f,PHRASAL_WORDS_MODE:p,COMMENT:m,C_LINE_COMMENT_MODE:b,C_BLOCK_COMMENT_MODE:v,HASH_COMMENT_MODE:x,NUMBER_MODE:{className:"number",begin:"\\b\\d+(\\.\\d+)?",relevance:0},C_NUMBER_MODE:{className:"number",begin:d,relevance:0},BINARY_NUMBER_MODE:{className:"number",begin:"\\b(0b[01]+)",relevance:0},CSS_NUMBER_MODE:{className:"number",begin:"\\b\\d+(\\.\\d+)?(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?",relevance:0},REGEXP_MODE:{begin:/(?=\/[^\/\n]*\/)/,contains:[{className:"regexp",begin:/\//,end:/\/[gimuy]*/,illegal:/\n/,contains:[g,{begin:/\[/,end:/\]/,relevance:0,contains:[g]}]}]},TITLE_MODE:{className:"title",begin:"[a-zA-Z]\\w*",relevance:0},UNDERSCORE_TITLE_MODE:{className:"title",begin:"[a-zA-Z_]\\w*",relevance:0},METHOD_GUARD:{begin:"\\.\\s*[a-zA-Z_]\\w*",relevance:0}}),E="of and for in not or if then".split(" ");function R(e,n){return n?+n:(t=e,E.includes(t.toLowerCase())?0:1);var t}const N=n,w=t,{nodeStream:y,mergeStreams:O}=a;return function(n){var r=[],a={},i={},s=[],o=!0,l=/((^(<[^>]+>|\t|)+|(?:\n)))/gm,d="Could not find the language '{}', did you forget to load/include a language module?",g={noHighlightRe:/^(no-?highlight)$/i,languageDetectRe:/\blang(?:uage)?-([\w-]+)\b/i,classPrefix:"hljs-",tabReplace:null,useBR:!1,languages:void 0,__emitter:c};function h(e){return g.noHighlightRe.test(e)}function f(e,n,t,r){var a={code:n,language:e};T("before:highlight",a);var i=a.result?a.result:p(a.language,a.code,t,r);return i.code=a.code,T("after:highlight",i),i}function p(e,n,r,i){var s=n;function l(e,n){var t=v.case_insensitive?n[0].toLowerCase():n[0];return e.keywords.hasOwnProperty(t)&&e.keywords[t]}function c(){null!=_.subLanguage?function(){if(""!==k){var e="string"==typeof _.subLanguage;if(!e||a[_.subLanguage]){var n=e?p(_.subLanguage,k,!0,E[_.subLanguage]):m(k,_.subLanguage.length?_.subLanguage:void 0);_.relevance>0&&(T+=n.relevance),e&&(E[_.subLanguage]=n.top),w.addSublanguage(n.emitter,n.language)}else w.addText(k)}}():function(){var e,n,t,r;if(_.keywords){for(n=0,_.lexemesRe.lastIndex=0,t=_.lexemesRe.exec(k),r="";t;){r+=k.substring(n,t.index);var a=null;(e=l(_,t))?(w.addText(r),r="",T+=e[1],a=e[0],w.addKeyword(t[0],a)):r+=t[0],n=_.lexemesRe.lastIndex,t=_.lexemesRe.exec(k)}r+=k.substr(n),w.addText(r)}else w.addText(k)}(),k=""}function h(e){e.className&&w.openNode(e.className),_=Object.create(e,{parent:{value:_}})}var f={};function b(n,t){var a,i=t&&t[0];if(k+=n,null==i)return c(),0;if("begin"==f.type&&"end"==t.type&&f.index==t.index&&""===i){if(k+=s.slice(t.index,t.index+1),!o)throw(a=Error("0 width match regex")).languageName=e,a.badRule=f.rule,a;return 1}if(f=t,"begin"===t.type)return function(e){var n=e[0],t=e.rule;return t.__onBegin&&(t.__onBegin(e)||{}).ignoreMatch?function(e){return 0===_.matcher.regexIndex?(k+=e[0],1):(B=!0,0)}(n):(t&&t.endSameAsBegin&&(t.endRe=RegExp(n.replace(/[-\/\\^$*+?.()|[\]{}]/g,"\\$&"),"m")),t.skip?k+=n:(t.excludeBegin&&(k+=n),c(),t.returnBegin||t.excludeBegin||(k=n)),h(t),t.returnBegin?0:n.length)}(t);if("illegal"===t.type&&!r)throw(a=Error('Illegal lexeme "'+i+'" for mode "'+(_.className||"")+'"')).mode=_,a;if("end"===t.type){var l=function(e){var n=e[0],t=s.substr(e.index),r=function e(n,t){if(function(e,n){var t=e&&e.exec(n);return t&&0===t.index}(n.endRe,t)){for(;n.endsParent&&n.parent;)n=n.parent;return n}if(n.endsWithParent)return e(n.parent,t)}(_,t);if(r){var a=_;a.skip?k+=n:(a.returnEnd||a.excludeEnd||(k+=n),c(),a.excludeEnd&&(k=n));do{_.className&&w.closeNode(),_.skip||_.subLanguage||(T+=_.relevance),_=_.parent}while(_!==r.parent);return r.starts&&(r.endSameAsBegin&&(r.starts.endRe=r.endRe),h(r.starts)),a.returnEnd?0:n.length}}(t);if(null!=l)return l}if("illegal"===t.type&&""===i)return 1;if(A>1e5&&A>3*t.index)throw Error("potential infinite loop, way more iterations than matches");return k+=i,i.length}var v=M(e);if(!v)throw console.error(d.replace("{}",e)),Error('Unknown language: "'+e+'"');!function(e){function n(n,t){return RegExp(u(n),"m"+(e.case_insensitive?"i":"")+(t?"g":""))}class r{constructor(){this.matchIndexes={},this.regexes=[],this.matchAt=1,this.position=0}addRule(e,n){n.position=this.position++,this.matchIndexes[this.matchAt]=n,this.regexes.push([n,e]),this.matchAt+=function(e){return RegExp(e.toString()+"|").exec("").length-1}(e)+1}compile(){0===this.regexes.length&&(this.exec=()=>null);let e=this.regexes.map(e=>e[1]);this.matcherRe=n(function(e,n){for(var t=/\[(?:[^\\\]]|\\.)*\]|\(\??|\\([1-9][0-9]*)|\\./,r=0,a="",i=0;i0&&(a+="|"),a+="(";o.length>0;){var l=t.exec(o);if(null==l){a+=o;break}a+=o.substring(0,l.index),o=o.substring(l.index+l[0].length),"\\"==l[0][0]&&l[1]?a+="\\"+(+l[1]+s):(a+=l[0],"("==l[0]&&r++)}a+=")"}return a}(e),!0),this.lastIndex=0}exec(e){this.matcherRe.lastIndex=this.lastIndex;let n=this.matcherRe.exec(e);if(!n)return null;let t=n.findIndex((e,n)=>n>0&&null!=e),r=this.matchIndexes[t];return Object.assign(n,r)}}class a{constructor(){this.rules=[],this.multiRegexes=[],this.count=0,this.lastIndex=0,this.regexIndex=0}getMatcher(e){if(this.multiRegexes[e])return this.multiRegexes[e];let n=new r;return this.rules.slice(e).forEach(([e,t])=>n.addRule(e,t)),n.compile(),this.multiRegexes[e]=n,n}considerAll(){this.regexIndex=0}addRule(e,n){this.rules.push([e,n]),"begin"===n.type&&this.count++}exec(e){let n=this.getMatcher(this.regexIndex);n.lastIndex=this.lastIndex;let t=n.exec(e);return t&&(this.regexIndex+=t.position+1,this.regexIndex===this.count&&(this.regexIndex=0)),t}}function i(e){let n=e.input[e.index-1],t=e.input[e.index+e[0].length];if("."===n||"."===t)return{ignoreMatch:!0}}if(e.contains&&e.contains.includes("self"))throw Error("ERR: contains `self` is not supported at the top-level of a language. See documentation.");!function r(s,o){s.compiled||(s.compiled=!0,s.__onBegin=null,s.keywords=s.keywords||s.beginKeywords,s.keywords&&(s.keywords=function(e,n){var t={};return"string"==typeof e?r("keyword",e):Object.keys(e).forEach((function(n){r(n,e[n])})),t;function r(e,r){n&&(r=r.toLowerCase()),r.split(" ").forEach((function(n){var r=n.split("|");t[r[0]]=[e,R(r[0],r[1])]}))}}(s.keywords,e.case_insensitive)),s.lexemesRe=n(s.lexemes||/\w+/,!0),o&&(s.beginKeywords&&(s.begin="\\b("+s.beginKeywords.split(" ").join("|")+")(?=\\b|\\s)",s.__onBegin=i),s.begin||(s.begin=/\B|\b/),s.beginRe=n(s.begin),s.endSameAsBegin&&(s.end=s.begin),s.end||s.endsWithParent||(s.end=/\B|\b/),s.end&&(s.endRe=n(s.end)),s.terminator_end=u(s.end)||"",s.endsWithParent&&o.terminator_end&&(s.terminator_end+=(s.end?"|":"")+o.terminator_end)),s.illegal&&(s.illegalRe=n(s.illegal)),null==s.relevance&&(s.relevance=1),s.contains||(s.contains=[]),s.contains=[].concat(...s.contains.map((function(e){return function(e){return e.variants&&!e.cached_variants&&(e.cached_variants=e.variants.map((function(n){return t(e,{variants:null},n)}))),e.cached_variants?e.cached_variants:function e(n){return!!n&&(n.endsWithParent||e(n.starts))}(e)?t(e,{starts:e.starts?t(e.starts):null}):Object.isFrozen(e)?t(e):e}("self"===e?s:e)}))),s.contains.forEach((function(e){r(e,s)})),s.starts&&r(s.starts,o),s.matcher=function(e){let n=new a;return e.contains.forEach(e=>n.addRule(e.begin,{rule:e,type:"begin"})),e.terminator_end&&n.addRule(e.terminator_end,{type:"end"}),e.illegal&&n.addRule(e.illegal,{type:"illegal"}),n}(s))}(e)}(v);var x,_=i||v,E={},w=new g.__emitter(g);!function(){for(var e=[],n=_;n!==v;n=n.parent)n.className&&e.unshift(n.className);e.forEach(e=>w.openNode(e))}();var y,O,k="",T=0,L=0,A=0,B=!1;try{for(_.matcher.considerAll();A++,B?B=!1:(_.matcher.lastIndex=L,_.matcher.considerAll()),y=_.matcher.exec(s);)O=b(s.substring(L,y.index),y),L=y.index+O;return b(s.substr(L)),w.closeAllNodes(),w.finalize(),x=w.toHTML(),{relevance:T,value:x,language:e,illegal:!1,emitter:w,top:_}}catch(n){if(n.message&&n.message.includes("Illegal"))return{illegal:!0,illegalBy:{msg:n.message,context:s.slice(L-100,L+100),mode:n.mode},sofar:x,relevance:0,value:N(s),emitter:w};if(o)return{relevance:0,value:N(s),emitter:w,language:e,top:_,errorRaised:n};throw n}}function m(e,n){n=n||g.languages||Object.keys(a);var t=function(e){const n={relevance:0,emitter:new g.__emitter(g),value:N(e),illegal:!1,top:E};return n.emitter.addText(e),n}(e),r=t;return n.filter(M).filter(k).forEach((function(n){var a=p(n,e,!1);a.language=n,a.relevance>r.relevance&&(r=a),a.relevance>t.relevance&&(r=t,t=a)})),r.language&&(t.second_best=r),t}function b(e){return g.tabReplace||g.useBR?e.replace(l,(function(e,n){return g.useBR&&"\n"===e?"
":g.tabReplace?n.replace(/\t/g,g.tabReplace):""})):e}function v(e){var n,t,r,a,s,o=function(e){var n,t=e.className+" ";if(t+=e.parentNode?e.parentNode.className:"",n=g.languageDetectRe.exec(t)){var r=M(n[1]);return r||(console.warn(d.replace("{}",n[1])),console.warn("Falling back to no-highlight mode for this block.",e)),r?n[1]:"no-highlight"}return t.split(/\s+/).find(e=>h(e)||M(e))}(e);h(o)||(T("before:highlightBlock",{block:e,language:o}),g.useBR?(n=document.createElement("div")).innerHTML=e.innerHTML.replace(/\n/g,"").replace(//g,"\n"):n=e,s=n.textContent,r=o?f(o,s,!0):m(s),(t=y(n)).length&&((a=document.createElement("div")).innerHTML=r.value,r.value=O(t,y(a),s)),r.value=b(r.value),T("after:highlightBlock",{block:e,result:r}),e.innerHTML=r.value,e.className=function(e,n,t){var r=n?i[n]:t,a=[e.trim()];return e.match(/\bhljs\b/)||a.push("hljs"),e.includes(r)||a.push(r),a.join(" ").trim()}(e.className,o,r.language),e.result={language:r.language,re:r.relevance},r.second_best&&(e.second_best={language:r.second_best.language,re:r.second_best.relevance}))}function x(){if(!x.called){x.called=!0;var e=document.querySelectorAll("pre code");r.forEach.call(e,v)}}const E={disableAutodetect:!0,name:"Plain text"};function M(e){return e=(e||"").toLowerCase(),a[e]||a[i[e]]}function k(e){var n=M(e);return n&&!n.disableAutodetect}function T(e,n){var t=e;s.forEach((function(e){e[t]&&e[t](n)}))}Object.assign(n,{highlight:f,highlightAuto:m,fixMarkup:b,highlightBlock:v,configure:function(e){g=w(g,e)},initHighlighting:x,initHighlightingOnLoad:function(){window.addEventListener("DOMContentLoaded",x,!1)},registerLanguage:function(e,t){var r;try{r=t(n)}catch(n){if(console.error("Language definition for '{}' could not be registered.".replace("{}",e)),!o)throw n;console.error(n),r=E}r.name||(r.name=e),a[e]=r,r.rawDefinition=t.bind(null,n),r.aliases&&r.aliases.forEach((function(n){i[n]=e}))},listLanguages:function(){return Object.keys(a)},getLanguage:M,requireLanguage:function(e){var n=M(e);if(n)return n;throw Error("The '{}' language is required, but not loaded.".replace("{}",e))},autoDetection:k,inherit:w,addPlugin:function(e,n){s.push(e)}}),n.debugMode=function(){o=!1},n.safeMode=function(){o=!0},n.versionString="10.0.3";for(const n in _)"object"==typeof _[n]&&e(_[n]);return Object.assign(n,_),n}({})}();"object"==typeof exports&&"undefined"!=typeof module&&(module.exports=hljs);hljs.registerLanguage("css",function(){"use strict";return function(e){var n={begin:/(?:[A-Z\_\.\-]+|--[a-zA-Z0-9_-]+)\s*:/,returnBegin:!0,end:";",endsWithParent:!0,contains:[{className:"attribute",begin:/\S/,end:":",excludeEnd:!0,starts:{endsWithParent:!0,excludeEnd:!0,contains:[{begin:/[\w-]+\(/,returnBegin:!0,contains:[{className:"built_in",begin:/[\w-]+/},{begin:/\(/,end:/\)/,contains:[e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,e.CSS_NUMBER_MODE]}]},e.CSS_NUMBER_MODE,e.QUOTE_STRING_MODE,e.APOS_STRING_MODE,e.C_BLOCK_COMMENT_MODE,{className:"number",begin:"#[0-9A-Fa-f]+"},{className:"meta",begin:"!important"}]}}]};return{name:"CSS",case_insensitive:!0,illegal:/[=\/|'\$]/,contains:[e.C_BLOCK_COMMENT_MODE,{className:"selector-id",begin:/#[A-Za-z0-9_-]+/},{className:"selector-class",begin:/\.[A-Za-z0-9_-]+/},{className:"selector-attr",begin:/\[/,end:/\]/,illegal:"$",contains:[e.APOS_STRING_MODE,e.QUOTE_STRING_MODE]},{className:"selector-pseudo",begin:/:(:)?[a-zA-Z0-9\_\-\+\(\)"'.]+/},{begin:"@(page|font-face)",lexemes:"@[a-z-]+",keywords:"@page @font-face"},{begin:"@",end:"[{;]",illegal:/:/,returnBegin:!0,contains:[{className:"keyword",begin:/@\-?\w[\w]*(\-\w+)*/},{begin:/\s/,endsWithParent:!0,excludeEnd:!0,relevance:0,keywords:"and or not only",contains:[{begin:/[a-z-]+:/,className:"attribute"},e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,e.CSS_NUMBER_MODE]}]},{className:"selector-tag",begin:"[a-zA-Z-][a-zA-Z0-9_-]*",relevance:0},{begin:"{",end:"}",illegal:/\S/,contains:[e.C_BLOCK_COMMENT_MODE,n]}]}}}());hljs.registerLanguage("bash",function(){"use strict";return function(e){const s={};Object.assign(s,{className:"variable",variants:[{begin:/\$[\w\d#@][\w\d_]*/},{begin:/\$\{/,end:/\}/,contains:[{begin:/:-/,contains:[s]}]}]});const n={className:"subst",begin:/\$\(/,end:/\)/,contains:[e.BACKSLASH_ESCAPE]},t={className:"string",begin:/"/,end:/"/,contains:[e.BACKSLASH_ESCAPE,s,n]};n.contains.push(t);const a={begin:/\$\(\(/,end:/\)\)/,contains:[{begin:/\d+#[0-9a-f]+/,className:"number"},e.NUMBER_MODE,s]};return{name:"Bash",aliases:["sh","zsh"],lexemes:/\b-?[a-z\._]+\b/,keywords:{keyword:"if then else elif fi for while in do done case esac function",literal:"true false",built_in:"break cd continue eval exec exit export getopts hash pwd readonly return shift test times trap umask unset alias bind builtin caller command declare echo enable help let local logout mapfile printf read readarray source type typeset ulimit unalias set shopt autoload bg bindkey bye cap chdir clone comparguments compcall compctl compdescribe compfiles compgroups compquote comptags comptry compvalues dirs disable disown echotc echoti emulate fc fg float functions getcap getln history integer jobs kill limit log noglob popd print pushd pushln rehash sched setcap setopt stat suspend ttyctl unfunction unhash unlimit unsetopt vared wait whence where which zcompile zformat zftp zle zmodload zparseopts zprof zpty zregexparse zsocket zstyle ztcp",_:"-ne -eq -lt -gt -f -d -e -s -l -a"},contains:[{className:"meta",begin:/^#![^\n]+sh\s*$/,relevance:10},{className:"function",begin:/\w[\w\d_]*\s*\(\s*\)\s*\{/,returnBegin:!0,contains:[e.inherit(e.TITLE_MODE,{begin:/\w[\w\d_]*/})],relevance:0},a,e.HASH_COMMENT_MODE,t,{className:"",begin:/\\"/},{className:"string",begin:/'/,end:/'/},s]}}}());hljs.registerLanguage("xml",function(){"use strict";return function(e){var n={className:"symbol",begin:"&[a-z]+;|&#[0-9]+;|&#x[a-f0-9]+;"},a={begin:"\\s",contains:[{className:"meta-keyword",begin:"#?[a-z_][a-z1-9_-]+",illegal:"\\n"}]},s=e.inherit(a,{begin:"\\(",end:"\\)"}),t=e.inherit(e.APOS_STRING_MODE,{className:"meta-string"}),i=e.inherit(e.QUOTE_STRING_MODE,{className:"meta-string"}),c={endsWithParent:!0,illegal:/`]+/}]}]}]};return{name:"HTML, XML",aliases:["html","xhtml","rss","atom","xjb","xsd","xsl","plist","wsf","svg"],case_insensitive:!0,contains:[{className:"meta",begin:"",relevance:10,contains:[a,i,t,s,{begin:"\\[",end:"\\]",contains:[{className:"meta",begin:"",contains:[a,s,i,t]}]}]},e.COMMENT("\x3c!--","--\x3e",{relevance:10}),{begin:"<\\!\\[CDATA\\[",end:"\\]\\]>",relevance:10},n,{className:"meta",begin:/<\?xml/,end:/\?>/,relevance:10},{className:"tag",begin:")",end:">",keywords:{name:"style"},contains:[c],starts:{end:"",returnEnd:!0,subLanguage:["css","xml"]}},{className:"tag",begin:")",end:">",keywords:{name:"script"},contains:[c],starts:{end:"<\/script>",returnEnd:!0,subLanguage:["javascript","handlebars","xml"]}},{className:"tag",begin:"",contains:[{className:"name",begin:/[^\/><\s]+/,relevance:0},c]}]}}}());hljs.registerLanguage("php",function(){"use strict";return function(e){var r={begin:"\\$+[a-zA-Z_-ÿ][a-zA-Z0-9_-ÿ]*"},t={className:"meta",variants:[{begin:/<\?php/,relevance:10},{begin:/<\?[=]?/},{begin:/\?>/}]},a={className:"string",contains:[e.BACKSLASH_ESCAPE,t],variants:[{begin:'b"',end:'"'},{begin:"b'",end:"'"},e.inherit(e.APOS_STRING_MODE,{illegal:null}),e.inherit(e.QUOTE_STRING_MODE,{illegal:null})]},n={variants:[e.BINARY_NUMBER_MODE,e.C_NUMBER_MODE]},i={keyword:"__CLASS__ __DIR__ __FILE__ __FUNCTION__ __LINE__ __METHOD__ __NAMESPACE__ __TRAIT__ die echo exit include include_once print require require_once array abstract and as binary bool boolean break callable case catch class clone const continue declare default do double else elseif empty enddeclare endfor endforeach endif endswitch endwhile eval extends final finally float for foreach from global goto if implements instanceof insteadof int integer interface isset iterable list new object or private protected public real return string switch throw trait try unset use var void while xor yield",literal:"false null true",built_in:"Error|0 AppendIterator ArgumentCountError ArithmeticError ArrayIterator ArrayObject AssertionError BadFunctionCallException BadMethodCallException CachingIterator CallbackFilterIterator CompileError Countable DirectoryIterator DivisionByZeroError DomainException EmptyIterator ErrorException Exception FilesystemIterator FilterIterator GlobIterator InfiniteIterator InvalidArgumentException IteratorIterator LengthException LimitIterator LogicException MultipleIterator NoRewindIterator OutOfBoundsException OutOfRangeException OuterIterator OverflowException ParentIterator ParseError RangeException RecursiveArrayIterator RecursiveCachingIterator RecursiveCallbackFilterIterator RecursiveDirectoryIterator RecursiveFilterIterator RecursiveIterator RecursiveIteratorIterator RecursiveRegexIterator RecursiveTreeIterator RegexIterator RuntimeException SeekableIterator SplDoublyLinkedList SplFileInfo SplFileObject SplFixedArray SplHeap SplMaxHeap SplMinHeap SplObjectStorage SplObserver SplObserver SplPriorityQueue SplQueue SplStack SplSubject SplSubject SplTempFileObject TypeError UnderflowException UnexpectedValueException ArrayAccess Closure Generator Iterator IteratorAggregate Serializable Throwable Traversable WeakReference Directory __PHP_Incomplete_Class parent php_user_filter self static stdClass"};return{aliases:["php","php3","php4","php5","php6","php7"],case_insensitive:!0,keywords:i,contains:[e.HASH_COMMENT_MODE,e.COMMENT("//","$",{contains:[t]}),e.COMMENT("/\\*","\\*/",{contains:[{className:"doctag",begin:"@[A-Za-z]+"}]}),e.COMMENT("__halt_compiler.+?;",!1,{endsWithParent:!0,keywords:"__halt_compiler",lexemes:e.UNDERSCORE_IDENT_RE}),{className:"string",begin:/<<<['"]?\w+['"]?$/,end:/^\w+;?$/,contains:[e.BACKSLASH_ESCAPE,{className:"subst",variants:[{begin:/\$\w+/},{begin:/\{\$/,end:/\}/}]}]},t,{className:"keyword",begin:/\$this\b/},r,{begin:/(::|->)+[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/},{className:"function",beginKeywords:"fn function",end:/[;{]/,excludeEnd:!0,illegal:"[$%\\[]",contains:[e.UNDERSCORE_TITLE_MODE,{className:"params",begin:"\\(",end:"\\)",excludeBegin:!0,excludeEnd:!0,keywords:i,contains:["self",r,e.C_BLOCK_COMMENT_MODE,a,n]}]},{className:"class",beginKeywords:"class interface",end:"{",excludeEnd:!0,illegal:/[:\(\$"]/,contains:[{beginKeywords:"extends implements"},e.UNDERSCORE_TITLE_MODE]},{beginKeywords:"namespace",end:";",illegal:/[\.']/,contains:[e.UNDERSCORE_TITLE_MODE]},{beginKeywords:"use",end:";",contains:[e.UNDERSCORE_TITLE_MODE]},{begin:"=>"},a,n]}}}());hljs.registerLanguage("php-template",function(){"use strict";return function(n){return{name:"PHP template",subLanguage:"xml",contains:[{begin:/<\?(php|=)?/,end:/\?>/,subLanguage:"php",contains:[{begin:"/\\*",end:"\\*/",skip:!0},{begin:'b"',end:'"',skip:!0},{begin:"b'",end:"'",skip:!0},n.inherit(n.APOS_STRING_MODE,{illegal:null,className:null,contains:null,skip:!0}),n.inherit(n.QUOTE_STRING_MODE,{illegal:null,className:null,contains:null,skip:!0})]}]}}}());hljs.registerLanguage("sql",function(){"use strict";return function(e){var t=e.COMMENT("--","$");return{name:"SQL",case_insensitive:!0,illegal:/[<>{}*]/,contains:[{beginKeywords:"begin end start commit rollback savepoint lock alter create drop rename call delete do handler insert load replace select truncate update set show pragma grant merge describe use explain help declare prepare execute deallocate release unlock purge reset change stop analyze cache flush optimize repair kill install uninstall checksum restore check backup revoke comment values with",end:/;/,endsWithParent:!0,lexemes:/[\w\.]+/,keywords:{keyword:"as abort abs absolute acc acce accep accept access accessed accessible account acos action activate add addtime admin administer advanced advise aes_decrypt aes_encrypt after agent aggregate ali alia alias all allocate allow alter always analyze ancillary and anti any anydata anydataset anyschema anytype apply archive archived archivelog are as asc ascii asin assembly assertion associate asynchronous at atan atn2 attr attri attrib attribu attribut attribute attributes audit authenticated authentication authid authors auto autoallocate autodblink autoextend automatic availability avg backup badfile basicfile before begin beginning benchmark between bfile bfile_base big bigfile bin binary_double binary_float binlog bit_and bit_count bit_length bit_or bit_xor bitmap blob_base block blocksize body both bound bucket buffer_cache buffer_pool build bulk by byte byteordermark bytes cache caching call calling cancel capacity cascade cascaded case cast catalog category ceil ceiling chain change changed char_base char_length character_length characters characterset charindex charset charsetform charsetid check checksum checksum_agg child choose chr chunk class cleanup clear client clob clob_base clone close cluster_id cluster_probability cluster_set clustering coalesce coercibility col collate collation collect colu colum column column_value columns columns_updated comment commit compact compatibility compiled complete composite_limit compound compress compute concat concat_ws concurrent confirm conn connec connect connect_by_iscycle connect_by_isleaf connect_by_root connect_time connection consider consistent constant constraint constraints constructor container content contents context contributors controlfile conv convert convert_tz corr corr_k corr_s corresponding corruption cos cost count count_big counted covar_pop covar_samp cpu_per_call cpu_per_session crc32 create creation critical cross cube cume_dist curdate current current_date current_time current_timestamp current_user cursor curtime customdatum cycle data database databases datafile datafiles datalength date_add date_cache date_format date_sub dateadd datediff datefromparts datename datepart datetime2fromparts day day_to_second dayname dayofmonth dayofweek dayofyear days db_role_change dbtimezone ddl deallocate declare decode decompose decrement decrypt deduplicate def defa defau defaul default defaults deferred defi defin define degrees delayed delegate delete delete_all delimited demand dense_rank depth dequeue des_decrypt des_encrypt des_key_file desc descr descri describ describe descriptor deterministic diagnostics difference dimension direct_load directory disable disable_all disallow disassociate discardfile disconnect diskgroup distinct distinctrow distribute distributed div do document domain dotnet double downgrade drop dumpfile duplicate duration each edition editionable editions element ellipsis else elsif elt empty enable enable_all enclosed encode encoding encrypt end end-exec endian enforced engine engines enqueue enterprise entityescaping eomonth error errors escaped evalname evaluate event eventdata events except exception exceptions exchange exclude excluding execu execut execute exempt exists exit exp expire explain explode export export_set extended extent external external_1 external_2 externally extract failed failed_login_attempts failover failure far fast feature_set feature_value fetch field fields file file_name_convert filesystem_like_logging final finish first first_value fixed flash_cache flashback floor flush following follows for forall force foreign form forma format found found_rows freelist freelists freepools fresh from from_base64 from_days ftp full function general generated get get_format get_lock getdate getutcdate global global_name globally go goto grant grants greatest group group_concat group_id grouping grouping_id groups gtid_subtract guarantee guard handler hash hashkeys having hea head headi headin heading heap help hex hierarchy high high_priority hosts hour hours http id ident_current ident_incr ident_seed identified identity idle_time if ifnull ignore iif ilike ilm immediate import in include including increment index indexes indexing indextype indicator indices inet6_aton inet6_ntoa inet_aton inet_ntoa infile initial initialized initially initrans inmemory inner innodb input insert install instance instantiable instr interface interleaved intersect into invalidate invisible is is_free_lock is_ipv4 is_ipv4_compat is_not is_not_null is_used_lock isdate isnull isolation iterate java join json json_exists keep keep_duplicates key keys kill language large last last_day last_insert_id last_value lateral lax lcase lead leading least leaves left len lenght length less level levels library like like2 like4 likec limit lines link list listagg little ln load load_file lob lobs local localtime localtimestamp locate locator lock locked log log10 log2 logfile logfiles logging logical logical_reads_per_call logoff logon logs long loop low low_priority lower lpad lrtrim ltrim main make_set makedate maketime managed management manual map mapping mask master master_pos_wait match matched materialized max maxextents maximize maxinstances maxlen maxlogfiles maxloghistory maxlogmembers maxsize maxtrans md5 measures median medium member memcompress memory merge microsecond mid migration min minextents minimum mining minus minute minutes minvalue missing mod mode model modification modify module monitoring month months mount move movement multiset mutex name name_const names nan national native natural nav nchar nclob nested never new newline next nextval no no_write_to_binlog noarchivelog noaudit nobadfile nocheck nocompress nocopy nocycle nodelay nodiscardfile noentityescaping noguarantee nokeep nologfile nomapping nomaxvalue nominimize nominvalue nomonitoring none noneditionable nonschema noorder nopr nopro noprom nopromp noprompt norely noresetlogs noreverse normal norowdependencies noschemacheck noswitch not nothing notice notnull notrim novalidate now nowait nth_value nullif nulls num numb numbe nvarchar nvarchar2 object ocicoll ocidate ocidatetime ociduration ociinterval ociloblocator ocinumber ociref ocirefcursor ocirowid ocistring ocitype oct octet_length of off offline offset oid oidindex old on online only opaque open operations operator optimal optimize option optionally or oracle oracle_date oradata ord ordaudio orddicom orddoc order ordimage ordinality ordvideo organization orlany orlvary out outer outfile outline output over overflow overriding package pad parallel parallel_enable parameters parent parse partial partition partitions pascal passing password password_grace_time password_lock_time password_reuse_max password_reuse_time password_verify_function patch path patindex pctincrease pctthreshold pctused pctversion percent percent_rank percentile_cont percentile_disc performance period period_add period_diff permanent physical pi pipe pipelined pivot pluggable plugin policy position post_transaction pow power pragma prebuilt precedes preceding precision prediction prediction_cost prediction_details prediction_probability prediction_set prepare present preserve prior priority private private_sga privileges procedural procedure procedure_analyze processlist profiles project prompt protection public publishingservername purge quarter query quick quiesce quota quotename radians raise rand range rank raw read reads readsize rebuild record records recover recovery recursive recycle redo reduced ref reference referenced references referencing refresh regexp_like register regr_avgx regr_avgy regr_count regr_intercept regr_r2 regr_slope regr_sxx regr_sxy reject rekey relational relative relaylog release release_lock relies_on relocate rely rem remainder rename repair repeat replace replicate replication required reset resetlogs resize resource respect restore restricted result result_cache resumable resume retention return returning returns reuse reverse revoke right rlike role roles rollback rolling rollup round row row_count rowdependencies rowid rownum rows rtrim rules safe salt sample save savepoint sb1 sb2 sb4 scan schema schemacheck scn scope scroll sdo_georaster sdo_topo_geometry search sec_to_time second seconds section securefile security seed segment select self semi sequence sequential serializable server servererror session session_user sessions_per_user set sets settings sha sha1 sha2 share shared shared_pool short show shrink shutdown si_averagecolor si_colorhistogram si_featurelist si_positionalcolor si_stillimage si_texture siblings sid sign sin size size_t sizes skip slave sleep smalldatetimefromparts smallfile snapshot some soname sort soundex source space sparse spfile split sql sql_big_result sql_buffer_result sql_cache sql_calc_found_rows sql_small_result sql_variant_property sqlcode sqldata sqlerror sqlname sqlstate sqrt square standalone standby start starting startup statement static statistics stats_binomial_test stats_crosstab stats_ks_test stats_mode stats_mw_test stats_one_way_anova stats_t_test_ stats_t_test_indep stats_t_test_one stats_t_test_paired stats_wsr_test status std stddev stddev_pop stddev_samp stdev stop storage store stored str str_to_date straight_join strcmp strict string struct stuff style subdate subpartition subpartitions substitutable substr substring subtime subtring_index subtype success sum suspend switch switchoffset switchover sync synchronous synonym sys sys_xmlagg sysasm sysaux sysdate sysdatetimeoffset sysdba sysoper system system_user sysutcdatetime table tables tablespace tablesample tan tdo template temporary terminated tertiary_weights test than then thread through tier ties time time_format time_zone timediff timefromparts timeout timestamp timestampadd timestampdiff timezone_abbr timezone_minute timezone_region to to_base64 to_date to_days to_seconds todatetimeoffset trace tracking transaction transactional translate translation treat trigger trigger_nestlevel triggers trim truncate try_cast try_convert try_parse type ub1 ub2 ub4 ucase unarchived unbounded uncompress under undo unhex unicode uniform uninstall union unique unix_timestamp unknown unlimited unlock unnest unpivot unrecoverable unsafe unsigned until untrusted unusable unused update updated upgrade upped upper upsert url urowid usable usage use use_stored_outlines user user_data user_resources users using utc_date utc_timestamp uuid uuid_short validate validate_password_strength validation valist value values var var_samp varcharc vari varia variab variabl variable variables variance varp varraw varrawc varray verify version versions view virtual visible void wait wallet warning warnings week weekday weekofyear wellformed when whene whenev wheneve whenever where while whitespace window with within without work wrapped xdb xml xmlagg xmlattributes xmlcast xmlcolattval xmlelement xmlexists xmlforest xmlindex xmlnamespaces xmlpi xmlquery xmlroot xmlschema xmlserialize xmltable xmltype xor year year_to_month years yearweek",literal:"true false null unknown",built_in:"array bigint binary bit blob bool boolean char character date dec decimal float int int8 integer interval number numeric real record serial serial8 smallint text time timestamp tinyint varchar varchar2 varying void"},contains:[{className:"string",begin:"'",end:"'",contains:[{begin:"''"}]},{className:"string",begin:'"',end:'"',contains:[{begin:'""'}]},{className:"string",begin:"`",end:"`"},e.C_NUMBER_MODE,e.C_BLOCK_COMMENT_MODE,t,e.HASH_COMMENT_MODE]},e.C_BLOCK_COMMENT_MODE,t,e.HASH_COMMENT_MODE]}}}());hljs.registerLanguage("python",function(){"use strict";return function(e){var n={keyword:"and elif is global as in if from raise for except finally print import pass return exec else break not with class assert yield try while continue del or def lambda async await nonlocal|10",built_in:"Ellipsis NotImplemented",literal:"False None True"},a={className:"meta",begin:/^(>>>|\.\.\.) /},i={className:"subst",begin:/\{/,end:/\}/,keywords:n,illegal:/#/},s={begin:/\{\{/,relevance:0},r={className:"string",contains:[e.BACKSLASH_ESCAPE],variants:[{begin:/(u|b)?r?'''/,end:/'''/,contains:[e.BACKSLASH_ESCAPE,a],relevance:10},{begin:/(u|b)?r?"""/,end:/"""/,contains:[e.BACKSLASH_ESCAPE,a],relevance:10},{begin:/(fr|rf|f)'''/,end:/'''/,contains:[e.BACKSLASH_ESCAPE,a,s,i]},{begin:/(fr|rf|f)"""/,end:/"""/,contains:[e.BACKSLASH_ESCAPE,a,s,i]},{begin:/(u|r|ur)'/,end:/'/,relevance:10},{begin:/(u|r|ur)"/,end:/"/,relevance:10},{begin:/(b|br)'/,end:/'/},{begin:/(b|br)"/,end:/"/},{begin:/(fr|rf|f)'/,end:/'/,contains:[e.BACKSLASH_ESCAPE,s,i]},{begin:/(fr|rf|f)"/,end:/"/,contains:[e.BACKSLASH_ESCAPE,s,i]},e.APOS_STRING_MODE,e.QUOTE_STRING_MODE]},l={className:"number",relevance:0,variants:[{begin:e.BINARY_NUMBER_RE+"[lLjJ]?"},{begin:"\\b(0o[0-7]+)[lLjJ]?"},{begin:e.C_NUMBER_RE+"[lLjJ]?"}]},t={className:"params",variants:[{begin:/\(\s*\)/,skip:!0,className:null},{begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,contains:["self",a,l,r,e.HASH_COMMENT_MODE]}]};return i.contains=[r,l,a],{name:"Python",aliases:["py","gyp","ipython"],keywords:n,illegal:/(<\/|->|\?)|=>/,contains:[a,l,{beginKeywords:"if",relevance:0},r,e.HASH_COMMENT_MODE,{variants:[{className:"function",beginKeywords:"def"},{className:"class",beginKeywords:"class"}],end:/:/,illegal:/[${=;\n,]/,contains:[e.UNDERSCORE_TITLE_MODE,t,{begin:/->/,endsWithParent:!0,keywords:"None"}]},{className:"meta",begin:/^[\t ]*@/,end:/$/},{begin:/\b(print|exec)\(/}]}}}());hljs.registerLanguage("plaintext",function(){"use strict";return function(t){return{name:"Plain text",aliases:["text","txt"],disableAutodetect:!0}}}());hljs.registerLanguage("diff",function(){"use strict";return function(e){return{name:"Diff",aliases:["patch"],contains:[{className:"meta",relevance:10,variants:[{begin:/^@@ +\-\d+,\d+ +\+\d+,\d+ +@@$/},{begin:/^\*\*\* +\d+,\d+ +\*\*\*\*$/},{begin:/^\-\-\- +\d+,\d+ +\-\-\-\-$/}]},{className:"comment",variants:[{begin:/Index: /,end:/$/},{begin:/={3,}/,end:/$/},{begin:/^\-{3}/,end:/$/},{begin:/^\*{3} /,end:/$/},{begin:/^\+{3}/,end:/$/},{begin:/^\*{15}$/}]},{className:"addition",begin:"^\\+",end:"$"},{className:"deletion",begin:"^\\-",end:"$"},{className:"addition",begin:"^\\!",end:"$"}]}}}());hljs.registerLanguage("csharp",function(){"use strict";return function(e){var n={keyword:"abstract as base bool break byte case catch char checked const continue decimal default delegate do double enum event explicit extern finally fixed float for foreach goto if implicit in int interface internal is lock long object operator out override params private protected public readonly ref sbyte sealed short sizeof stackalloc static string struct switch this try typeof uint ulong unchecked unsafe ushort using virtual void volatile while add alias ascending async await by descending dynamic equals from get global group into join let nameof on orderby partial remove select set value var when where yield",literal:"null false true"},i=e.inherit(e.TITLE_MODE,{begin:"[a-zA-Z](\\.?\\w)*"}),a={className:"number",variants:[{begin:"\\b(0b[01']+)"},{begin:"(-?)\\b([\\d']+(\\.[\\d']*)?|\\.[\\d']+)(u|U|l|L|ul|UL|f|F|b|B)"},{begin:"(-?)(\\b0[xX][a-fA-F0-9']+|(\\b[\\d']+(\\.[\\d']*)?|\\.[\\d']+)([eE][-+]?[\\d']+)?)"}],relevance:0},s={className:"string",begin:'@"',end:'"',contains:[{begin:'""'}]},t=e.inherit(s,{illegal:/\n/}),l={className:"subst",begin:"{",end:"}",keywords:n},r=e.inherit(l,{illegal:/\n/}),c={className:"string",begin:/\$"/,end:'"',illegal:/\n/,contains:[{begin:"{{"},{begin:"}}"},e.BACKSLASH_ESCAPE,r]},o={className:"string",begin:/\$@"/,end:'"',contains:[{begin:"{{"},{begin:"}}"},{begin:'""'},l]},g=e.inherit(o,{illegal:/\n/,contains:[{begin:"{{"},{begin:"}}"},{begin:'""'},r]});l.contains=[o,c,s,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,a,e.C_BLOCK_COMMENT_MODE],r.contains=[g,c,t,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,a,e.inherit(e.C_BLOCK_COMMENT_MODE,{illegal:/\n/})];var d={variants:[o,c,s,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE]},E=e.IDENT_RE+"(<"+e.IDENT_RE+"(\\s*,\\s*"+e.IDENT_RE+")*>)?(\\[\\])?",_={begin:"@"+e.IDENT_RE,relevance:0};return{name:"C#",aliases:["cs","c#"],keywords:n,illegal:/::/,contains:[e.COMMENT("///","$",{returnBegin:!0,contains:[{className:"doctag",variants:[{begin:"///",relevance:0},{begin:"\x3c!--|--\x3e"},{begin:""}]}]}),e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,{className:"meta",begin:"#",end:"$",keywords:{"meta-keyword":"if else elif endif define undef warning error line region endregion pragma checksum"}},d,a,{beginKeywords:"class interface",end:/[{;=]/,illegal:/[^\s:,]/,contains:[{beginKeywords:"where class"},i,{begin:"<",end:">",keywords:"in out"},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},{beginKeywords:"namespace",end:/[{;=]/,illegal:/[^\s:]/,contains:[i,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},{className:"meta",begin:"^\\s*\\[",excludeBegin:!0,end:"\\]",excludeEnd:!0,contains:[{className:"meta-string",begin:/"/,end:/"/}]},{beginKeywords:"new return throw await else",relevance:0},{className:"function",begin:"("+E+"\\s+)+"+e.IDENT_RE+"\\s*\\(",returnBegin:!0,end:/\s*[{;=]/,excludeEnd:!0,keywords:n,contains:[{begin:e.IDENT_RE+"\\s*\\(",returnBegin:!0,contains:[e.TITLE_MODE],relevance:0},{className:"params",begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:n,relevance:0,contains:[d,a,e.C_BLOCK_COMMENT_MODE]},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},_]}}}());hljs.registerLanguage("ruby",function(){"use strict";return function(e){var n="[a-zA-Z_]\\w*[!?=]?|[-+~]\\@|<<|>>|=~|===?|<=>|[<>]=?|\\*\\*|[-/+%^&*~`|]|\\[\\]=?",a={keyword:"and then defined module in return redo if BEGIN retry end for self when next until do begin unless END rescue else break undef not super class case require yield alias while ensure elsif or include attr_reader attr_writer attr_accessor",literal:"true false nil"},s={className:"doctag",begin:"@[A-Za-z]+"},i={begin:"#<",end:">"},r=[e.COMMENT("#","$",{contains:[s]}),e.COMMENT("^\\=begin","^\\=end",{contains:[s],relevance:10}),e.COMMENT("^__END__","\\n$")],c={className:"subst",begin:"#\\{",end:"}",keywords:a},t={className:"string",contains:[e.BACKSLASH_ESCAPE,c],variants:[{begin:/'/,end:/'/},{begin:/"/,end:/"/},{begin:/`/,end:/`/},{begin:"%[qQwWx]?\\(",end:"\\)"},{begin:"%[qQwWx]?\\[",end:"\\]"},{begin:"%[qQwWx]?{",end:"}"},{begin:"%[qQwWx]?<",end:">"},{begin:"%[qQwWx]?/",end:"/"},{begin:"%[qQwWx]?%",end:"%"},{begin:"%[qQwWx]?-",end:"-"},{begin:"%[qQwWx]?\\|",end:"\\|"},{begin:/\B\?(\\\d{1,3}|\\x[A-Fa-f0-9]{1,2}|\\u[A-Fa-f0-9]{4}|\\?\S)\b/},{begin:/<<[-~]?'?(\w+)(?:.|\n)*?\n\s*\1\b/,returnBegin:!0,contains:[{begin:/<<[-~]?'?/},{begin:/\w+/,endSameAsBegin:!0,contains:[e.BACKSLASH_ESCAPE,c]}]}]},b={className:"params",begin:"\\(",end:"\\)",endsParent:!0,keywords:a},d=[t,i,{className:"class",beginKeywords:"class module",end:"$|;",illegal:/=/,contains:[e.inherit(e.TITLE_MODE,{begin:"[A-Za-z_]\\w*(::\\w+)*(\\?|\\!)?"}),{begin:"<\\s*",contains:[{begin:"("+e.IDENT_RE+"::)?"+e.IDENT_RE}]}].concat(r)},{className:"function",beginKeywords:"def",end:"$|;",contains:[e.inherit(e.TITLE_MODE,{begin:n}),b].concat(r)},{begin:e.IDENT_RE+"::"},{className:"symbol",begin:e.UNDERSCORE_IDENT_RE+"(\\!|\\?)?:",relevance:0},{className:"symbol",begin:":(?!\\s)",contains:[t,{begin:n}],relevance:0},{className:"number",begin:"(\\b0[0-7_]+)|(\\b0x[0-9a-fA-F_]+)|(\\b[1-9][0-9_]*(\\.[0-9_]+)?)|[0_]\\b",relevance:0},{begin:"(\\$\\W)|((\\$|\\@\\@?)(\\w+))"},{className:"params",begin:/\|/,end:/\|/,keywords:a},{begin:"("+e.RE_STARTERS_RE+"|unless)\\s*",keywords:"unless",contains:[i,{className:"regexp",contains:[e.BACKSLASH_ESCAPE,c],illegal:/\n/,variants:[{begin:"/",end:"/[a-z]*"},{begin:"%r{",end:"}[a-z]*"},{begin:"%r\\(",end:"\\)[a-z]*"},{begin:"%r!",end:"![a-z]*"},{begin:"%r\\[",end:"\\][a-z]*"}]}].concat(r),relevance:0}].concat(r);c.contains=d,b.contains=d;var g=[{begin:/^\s*=>/,starts:{end:"$",contains:d}},{className:"meta",begin:"^([>?]>|[\\w#]+\\(\\w+\\):\\d+:\\d+>|(\\w+-)?\\d+\\.\\d+\\.\\d(p\\d+)?[^>]+>)",starts:{end:"$",contains:d}}];return{name:"Ruby",aliases:["rb","gemspec","podspec","thor","irb"],keywords:a,illegal:/\/\*/,contains:r.concat(g).concat(d)}}}());hljs.registerLanguage("scss",function(){"use strict";return function(e){var t={className:"variable",begin:"(\\$[a-zA-Z-][a-zA-Z0-9_-]*)\\b"},i={className:"number",begin:"#[0-9A-Fa-f]+"};return e.CSS_NUMBER_MODE,e.QUOTE_STRING_MODE,e.APOS_STRING_MODE,e.C_BLOCK_COMMENT_MODE,{name:"SCSS",case_insensitive:!0,illegal:"[=/|']",contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,{className:"selector-id",begin:"\\#[A-Za-z0-9_-]+",relevance:0},{className:"selector-class",begin:"\\.[A-Za-z0-9_-]+",relevance:0},{className:"selector-attr",begin:"\\[",end:"\\]",illegal:"$"},{className:"selector-tag",begin:"\\b(a|abbr|acronym|address|area|article|aside|audio|b|base|big|blockquote|body|br|button|canvas|caption|cite|code|col|colgroup|command|datalist|dd|del|details|dfn|div|dl|dt|em|embed|fieldset|figcaption|figure|footer|form|frame|frameset|(h[1-6])|head|header|hgroup|hr|html|i|iframe|img|input|ins|kbd|keygen|label|legend|li|link|map|mark|meta|meter|nav|noframes|noscript|object|ol|optgroup|option|output|p|param|pre|progress|q|rp|rt|ruby|samp|script|section|select|small|span|strike|strong|style|sub|sup|table|tbody|td|textarea|tfoot|th|thead|time|title|tr|tt|ul|var|video)\\b",relevance:0},{className:"selector-pseudo",begin:":(visited|valid|root|right|required|read-write|read-only|out-range|optional|only-of-type|only-child|nth-of-type|nth-last-of-type|nth-last-child|nth-child|not|link|left|last-of-type|last-child|lang|invalid|indeterminate|in-range|hover|focus|first-of-type|first-line|first-letter|first-child|first|enabled|empty|disabled|default|checked|before|after|active)"},{className:"selector-pseudo",begin:"::(after|before|choices|first-letter|first-line|repeat-index|repeat-item|selection|value)"},t,{className:"attribute",begin:"\\b(src|z-index|word-wrap|word-spacing|word-break|width|widows|white-space|visibility|vertical-align|unicode-bidi|transition-timing-function|transition-property|transition-duration|transition-delay|transition|transform-style|transform-origin|transform|top|text-underline-position|text-transform|text-shadow|text-rendering|text-overflow|text-indent|text-decoration-style|text-decoration-line|text-decoration-color|text-decoration|text-align-last|text-align|tab-size|table-layout|right|resize|quotes|position|pointer-events|perspective-origin|perspective|page-break-inside|page-break-before|page-break-after|padding-top|padding-right|padding-left|padding-bottom|padding|overflow-y|overflow-x|overflow-wrap|overflow|outline-width|outline-style|outline-offset|outline-color|outline|orphans|order|opacity|object-position|object-fit|normal|none|nav-up|nav-right|nav-left|nav-index|nav-down|min-width|min-height|max-width|max-height|mask|marks|margin-top|margin-right|margin-left|margin-bottom|margin|list-style-type|list-style-position|list-style-image|list-style|line-height|letter-spacing|left|justify-content|initial|inherit|ime-mode|image-orientation|image-resolution|image-rendering|icon|hyphens|height|font-weight|font-variant-ligatures|font-variant|font-style|font-stretch|font-size-adjust|font-size|font-language-override|font-kerning|font-feature-settings|font-family|font|float|flex-wrap|flex-shrink|flex-grow|flex-flow|flex-direction|flex-basis|flex|filter|empty-cells|display|direction|cursor|counter-reset|counter-increment|content|column-width|column-span|column-rule-width|column-rule-style|column-rule-color|column-rule|column-gap|column-fill|column-count|columns|color|clip-path|clip|clear|caption-side|break-inside|break-before|break-after|box-sizing|box-shadow|box-decoration-break|bottom|border-width|border-top-width|border-top-style|border-top-right-radius|border-top-left-radius|border-top-color|border-top|border-style|border-spacing|border-right-width|border-right-style|border-right-color|border-right|border-radius|border-left-width|border-left-style|border-left-color|border-left|border-image-width|border-image-source|border-image-slice|border-image-repeat|border-image-outset|border-image|border-color|border-collapse|border-bottom-width|border-bottom-style|border-bottom-right-radius|border-bottom-left-radius|border-bottom-color|border-bottom|border|background-size|background-repeat|background-position|background-origin|background-image|background-color|background-clip|background-attachment|background-blend-mode|background|backface-visibility|auto|animation-timing-function|animation-play-state|animation-name|animation-iteration-count|animation-fill-mode|animation-duration|animation-direction|animation-delay|animation|align-self|align-items|align-content)\\b",illegal:"[^\\s]"},{begin:"\\b(whitespace|wait|w-resize|visible|vertical-text|vertical-ideographic|uppercase|upper-roman|upper-alpha|underline|transparent|top|thin|thick|text|text-top|text-bottom|tb-rl|table-header-group|table-footer-group|sw-resize|super|strict|static|square|solid|small-caps|separate|se-resize|scroll|s-resize|rtl|row-resize|ridge|right|repeat|repeat-y|repeat-x|relative|progress|pointer|overline|outside|outset|oblique|nowrap|not-allowed|normal|none|nw-resize|no-repeat|no-drop|newspaper|ne-resize|n-resize|move|middle|medium|ltr|lr-tb|lowercase|lower-roman|lower-alpha|loose|list-item|line|line-through|line-edge|lighter|left|keep-all|justify|italic|inter-word|inter-ideograph|inside|inset|inline|inline-block|inherit|inactive|ideograph-space|ideograph-parenthesis|ideograph-numeric|ideograph-alpha|horizontal|hidden|help|hand|groove|fixed|ellipsis|e-resize|double|dotted|distribute|distribute-space|distribute-letter|distribute-all-lines|disc|disabled|default|decimal|dashed|crosshair|collapse|col-resize|circle|char|center|capitalize|break-word|break-all|bottom|both|bolder|bold|block|bidi-override|below|baseline|auto|always|all-scroll|absolute|table|table-cell)\\b"},{begin:":",end:";",contains:[t,i,e.CSS_NUMBER_MODE,e.QUOTE_STRING_MODE,e.APOS_STRING_MODE,{className:"meta",begin:"!important"}]},{begin:"@(page|font-face)",lexemes:"@[a-z-]+",keywords:"@page @font-face"},{begin:"@",end:"[{;]",returnBegin:!0,keywords:"and or not only",contains:[{begin:"@[a-z-]+",className:"keyword"},t,e.QUOTE_STRING_MODE,e.APOS_STRING_MODE,i,e.CSS_NUMBER_MODE]}]}}}());hljs.registerLanguage("http",function(){"use strict";return function(e){var n="HTTP/[0-9\\.]+";return{name:"HTTP",aliases:["https"],illegal:"\\S",contains:[{begin:"^"+n,end:"$",contains:[{className:"number",begin:"\\b\\d{3}\\b"}]},{begin:"^[A-Z]+ (.*?) "+n+"$",returnBegin:!0,end:"$",contains:[{className:"string",begin:" ",end:" ",excludeBegin:!0,excludeEnd:!0},{begin:n},{className:"keyword",begin:"[A-Z]+"}]},{className:"attribute",begin:"^\\w",end:": ",excludeEnd:!0,illegal:"\\n|\\s|=",starts:{end:"$",relevance:0}},{begin:"\\n\\n",starts:{subLanguage:[],endsWithParent:!0}}]}}}());hljs.registerLanguage("java",function(){"use strict";return function(e){var a="false synchronized int abstract float private char boolean var static null if const for true while long strictfp finally protected import native final void enum else break transient catch instanceof byte super volatile case assert short package default double public try this switch continue throws protected public private module requires exports do",n={className:"meta",begin:"@[À-ʸa-zA-Z_$][À-ʸa-zA-Z_$0-9]*",contains:[{begin:/\(/,end:/\)/,contains:["self"]}]};return{name:"Java",aliases:["jsp"],keywords:a,illegal:/<\/|#/,contains:[e.COMMENT("/\\*\\*","\\*/",{relevance:0,contains:[{begin:/\w+@/,relevance:0},{className:"doctag",begin:"@[A-Za-z]+"}]}),e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,{className:"class",beginKeywords:"class interface",end:/[{;=]/,excludeEnd:!0,keywords:"class interface",illegal:/[:"\[\]]/,contains:[{beginKeywords:"extends implements"},e.UNDERSCORE_TITLE_MODE]},{beginKeywords:"new throw return else",relevance:0},{className:"function",begin:"([À-ʸa-zA-Z_$][À-ʸa-zA-Z_$0-9]*(<[À-ʸa-zA-Z_$][À-ʸa-zA-Z_$0-9]*(\\s*,\\s*[À-ʸa-zA-Z_$][À-ʸa-zA-Z_$0-9]*)*>)?\\s+)+"+e.UNDERSCORE_IDENT_RE+"\\s*\\(",returnBegin:!0,end:/[{;=]/,excludeEnd:!0,keywords:a,contains:[{begin:e.UNDERSCORE_IDENT_RE+"\\s*\\(",returnBegin:!0,relevance:0,contains:[e.UNDERSCORE_TITLE_MODE]},{className:"params",begin:/\(/,end:/\)/,keywords:a,relevance:0,contains:[n,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,e.C_NUMBER_MODE,e.C_BLOCK_COMMENT_MODE]},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},{className:"number",begin:"\\b(0[bB]([01]+[01_]+[01]+|[01]+)|0[xX]([a-fA-F0-9]+[a-fA-F0-9_]+[a-fA-F0-9]+|[a-fA-F0-9]+)|(([\\d]+[\\d_]+[\\d]+|[\\d]+)(\\.([\\d]+[\\d_]+[\\d]+|[\\d]+))?|\\.([\\d]+[\\d_]+[\\d]+|[\\d]+))([eE][-+]?\\d+)?)[lLfF]?",relevance:0},n]}}}());hljs.registerLanguage("swift",function(){"use strict";return function(e){var i={keyword:"#available #colorLiteral #column #else #elseif #endif #file #fileLiteral #function #if #imageLiteral #line #selector #sourceLocation _ __COLUMN__ __FILE__ __FUNCTION__ __LINE__ Any as as! as? associatedtype associativity break case catch class continue convenience default defer deinit didSet do dynamic dynamicType else enum extension fallthrough false fileprivate final for func get guard if import in indirect infix init inout internal is lazy left let mutating nil none nonmutating open operator optional override postfix precedence prefix private protocol Protocol public repeat required rethrows return right self Self set static struct subscript super switch throw throws true try try! try? Type typealias unowned var weak where while willSet",literal:"true false nil",built_in:"abs advance alignof alignofValue anyGenerator assert assertionFailure bridgeFromObjectiveC bridgeFromObjectiveCUnconditional bridgeToObjectiveC bridgeToObjectiveCUnconditional c compactMap contains count countElements countLeadingZeros debugPrint debugPrintln distance dropFirst dropLast dump encodeBitsAsWords enumerate equal fatalError filter find getBridgedObjectiveCType getVaList indices insertionSort isBridgedToObjectiveC isBridgedVerbatimToObjectiveC isUniquelyReferenced isUniquelyReferencedNonObjC join lazy lexicographicalCompare map max maxElement min minElement numericCast overlaps partition posix precondition preconditionFailure print println quickSort readLine reduce reflect reinterpretCast reverse roundUpToAlignment sizeof sizeofValue sort split startsWith stride strideof strideofValue swap toString transcode underestimateCount unsafeAddressOf unsafeBitCast unsafeDowncast unsafeUnwrap unsafeReflect withExtendedLifetime withObjectAtPlusZero withUnsafePointer withUnsafePointerToObject withUnsafeMutablePointer withUnsafeMutablePointers withUnsafePointer withUnsafePointers withVaList zip"},n=e.COMMENT("/\\*","\\*/",{contains:["self"]}),t={className:"subst",begin:/\\\(/,end:"\\)",keywords:i,contains:[]},a={className:"string",contains:[e.BACKSLASH_ESCAPE,t],variants:[{begin:/"""/,end:/"""/},{begin:/"/,end:/"/}]},r={className:"number",begin:"\\b([\\d_]+(\\.[\\deE_]+)?|0x[a-fA-F0-9_]+(\\.[a-fA-F0-9p_]+)?|0b[01_]+|0o[0-7_]+)\\b",relevance:0};return t.contains=[r],{name:"Swift",keywords:i,contains:[a,e.C_LINE_COMMENT_MODE,n,{className:"type",begin:"\\b[A-Z][\\wÀ-ʸ']*[!?]"},{className:"type",begin:"\\b[A-Z][\\wÀ-ʸ']*",relevance:0},r,{className:"function",beginKeywords:"func",end:"{",excludeEnd:!0,contains:[e.inherit(e.TITLE_MODE,{begin:/[A-Za-z$_][0-9A-Za-z$_]*/}),{begin://},{className:"params",begin:/\(/,end:/\)/,endsParent:!0,keywords:i,contains:["self",r,a,e.C_BLOCK_COMMENT_MODE,{begin:":"}],illegal:/["']/}],illegal:/\[|%/},{className:"class",beginKeywords:"struct protocol class extension enum",keywords:i,end:"\\{",excludeEnd:!0,contains:[e.inherit(e.TITLE_MODE,{begin:/[A-Za-z$_][\u00C0-\u02B80-9A-Za-z$_]*/})]},{className:"meta",begin:"(@discardableResult|@warn_unused_result|@exported|@lazy|@noescape|@NSCopying|@NSManaged|@objc|@objcMembers|@convention|@required|@noreturn|@IBAction|@IBDesignable|@IBInspectable|@IBOutlet|@infix|@prefix|@postfix|@autoclosure|@testable|@available|@nonobjc|@NSApplicationMain|@UIApplicationMain|@dynamicMemberLookup|@propertyWrapper)"},{beginKeywords:"import",end:/$/,contains:[e.C_LINE_COMMENT_MODE,n]}]}}}());hljs.registerLanguage("less",function(){"use strict";return function(e){var n="([\\w-]+|@{[\\w-]+})",a=[],s=[],t=function(e){return{className:"string",begin:"~?"+e+".*?"+e}},r=function(e,n,a){return{className:e,begin:n,relevance:a}},i={begin:"\\(",end:"\\)",contains:s,relevance:0};s.push(e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,t("'"),t('"'),e.CSS_NUMBER_MODE,{begin:"(url|data-uri)\\(",starts:{className:"string",end:"[\\)\\n]",excludeEnd:!0}},r("number","#[0-9A-Fa-f]+\\b"),i,r("variable","@@?[\\w-]+",10),r("variable","@{[\\w-]+}"),r("built_in","~?`[^`]*?`"),{className:"attribute",begin:"[\\w-]+\\s*:",end:":",returnBegin:!0,excludeEnd:!0},{className:"meta",begin:"!important"});var c=s.concat({begin:"{",end:"}",contains:a}),l={beginKeywords:"when",endsWithParent:!0,contains:[{beginKeywords:"and not"}].concat(s)},o={begin:n+"\\s*:",returnBegin:!0,end:"[;}]",relevance:0,contains:[{className:"attribute",begin:n,end:":",excludeEnd:!0,starts:{endsWithParent:!0,illegal:"[<=$]",relevance:0,contains:s}}]},g={className:"keyword",begin:"@(import|media|charset|font-face|(-[a-z]+-)?keyframes|supports|document|namespace|page|viewport|host)\\b",starts:{end:"[;{}]",returnEnd:!0,contains:s,relevance:0}},d={className:"variable",variants:[{begin:"@[\\w-]+\\s*:",relevance:15},{begin:"@[\\w-]+"}],starts:{end:"[;}]",returnEnd:!0,contains:c}},b={variants:[{begin:"[\\.#:&\\[>]",end:"[;{}]"},{begin:n,end:"{"}],returnBegin:!0,returnEnd:!0,illegal:"[<='$\"]",relevance:0,contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,l,r("keyword","all\\b"),r("variable","@{[\\w-]+}"),r("selector-tag",n+"%?",0),r("selector-id","#"+n),r("selector-class","\\."+n,0),r("selector-tag","&",0),{className:"selector-attr",begin:"\\[",end:"\\]"},{className:"selector-pseudo",begin:/:(:)?[a-zA-Z0-9\_\-\+\(\)"'.]+/},{begin:"\\(",end:"\\)",contains:c},{begin:"!important"}]};return a.push(e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,g,d,o,b),{name:"Less",case_insensitive:!0,illegal:"[=>'/<($\"]",contains:a}}}());hljs.registerLanguage("json",function(){"use strict";return function(n){var e={literal:"true false null"},i=[n.C_LINE_COMMENT_MODE,n.C_BLOCK_COMMENT_MODE],t=[n.QUOTE_STRING_MODE,n.C_NUMBER_MODE],a={end:",",endsWithParent:!0,excludeEnd:!0,contains:t,keywords:e},l={begin:"{",end:"}",contains:[{className:"attr",begin:/"/,end:/"/,contains:[n.BACKSLASH_ESCAPE],illegal:"\\n"},n.inherit(a,{begin:/:/})].concat(i),illegal:"\\S"},s={begin:"\\[",end:"\\]",contains:[n.inherit(a)],illegal:"\\S"};return t.push(l,s),i.forEach((function(n){t.push(n)})),{name:"JSON",contains:t,keywords:e,illegal:"\\S"}}}());hljs.registerLanguage("typescript",function(){"use strict";return function(e){var n={keyword:"in if for while finally var new function do return void else break catch instanceof with throw case default try this switch continue typeof delete let yield const class public private protected get set super static implements enum export import declare type namespace abstract as from extends async await",literal:"true false null undefined NaN Infinity",built_in:"eval isFinite isNaN parseFloat parseInt decodeURI decodeURIComponent encodeURI encodeURIComponent escape unescape Object Function Boolean Error EvalError InternalError RangeError ReferenceError StopIteration SyntaxError TypeError URIError Number Math Date String RegExp Array Float32Array Float64Array Int16Array Int32Array Int8Array Uint16Array Uint32Array Uint8Array Uint8ClampedArray ArrayBuffer DataView JSON Intl arguments require module console window document any number boolean string void Promise"},r={className:"meta",begin:"@[A-Za-z$_][0-9A-Za-z$_]*"},a={begin:"\\(",end:/\)/,keywords:n,contains:["self",e.QUOTE_STRING_MODE,e.APOS_STRING_MODE,e.NUMBER_MODE]},t={className:"params",begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:n,contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,r,a]},s={className:"number",variants:[{begin:"\\b(0[bB][01]+)n?"},{begin:"\\b(0[oO][0-7]+)n?"},{begin:e.C_NUMBER_RE+"n?"}],relevance:0},i={className:"subst",begin:"\\$\\{",end:"\\}",keywords:n,contains:[]},o={begin:"html`",end:"",starts:{end:"`",returnEnd:!1,contains:[e.BACKSLASH_ESCAPE,i],subLanguage:"xml"}},c={begin:"css`",end:"",starts:{end:"`",returnEnd:!1,contains:[e.BACKSLASH_ESCAPE,i],subLanguage:"css"}},E={className:"string",begin:"`",end:"`",contains:[e.BACKSLASH_ESCAPE,i]};return i.contains=[e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,o,c,E,s,e.REGEXP_MODE],{name:"TypeScript",aliases:["ts"],keywords:n,contains:[{className:"meta",begin:/^\s*['"]use strict['"]/},e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,o,c,E,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,s,{begin:"("+e.RE_STARTERS_RE+"|\\b(case|return|throw)\\b)\\s*",keywords:"return throw case",contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,e.REGEXP_MODE,{className:"function",begin:"(\\(.*?\\)|"+e.IDENT_RE+")\\s*=>",returnBegin:!0,end:"\\s*=>",contains:[{className:"params",variants:[{begin:e.IDENT_RE},{begin:/\(\s*\)/},{begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:n,contains:["self",e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]}]}]}],relevance:0},{className:"function",beginKeywords:"function",end:/[\{;]/,excludeEnd:!0,keywords:n,contains:["self",e.inherit(e.TITLE_MODE,{begin:"[A-Za-z$_][0-9A-Za-z$_]*"}),t],illegal:/%/,relevance:0},{beginKeywords:"constructor",end:/[\{;]/,excludeEnd:!0,contains:["self",t]},{begin:/module\./,keywords:{built_in:"module"},relevance:0},{beginKeywords:"module",end:/\{/,excludeEnd:!0},{beginKeywords:"interface",end:/\{/,excludeEnd:!0,keywords:"interface extends"},{begin:/\$[(.]/},{begin:"\\."+e.IDENT_RE,relevance:0},r,a]}}}());hljs.registerLanguage("c-like",function(){"use strict";return function(e){function t(e){return"(?:"+e+")?"}var n="(decltype\\(auto\\)|"+t("[a-zA-Z_]\\w*::")+"[a-zA-Z_]\\w*"+t("<.*?>")+")",r={className:"keyword",begin:"\\b[a-z\\d_]*_t\\b"},a={className:"string",variants:[{begin:'(u8?|U|L)?"',end:'"',illegal:"\\n",contains:[e.BACKSLASH_ESCAPE]},{begin:"(u8?|U|L)?'(\\\\(x[0-9A-Fa-f]{2}|u[0-9A-Fa-f]{4,8}|[0-7]{3}|\\S)|.)",end:"'",illegal:"."},{begin:/(?:u8?|U|L)?R"([^()\\ ]{0,16})\((?:.|\n)*?\)\1"/}]},s={className:"number",variants:[{begin:"\\b(0b[01']+)"},{begin:"(-?)\\b([\\d']+(\\.[\\d']*)?|\\.[\\d']+)(u|U|l|L|ul|UL|f|F|b|B)"},{begin:"(-?)(\\b0[xX][a-fA-F0-9']+|(\\b[\\d']+(\\.[\\d']*)?|\\.[\\d']+)([eE][-+]?[\\d']+)?)"}],relevance:0},i={className:"meta",begin:/#\s*[a-z]+\b/,end:/$/,keywords:{"meta-keyword":"if else elif endif define undef warning error line pragma _Pragma ifdef ifndef include"},contains:[{begin:/\\\n/,relevance:0},e.inherit(a,{className:"meta-string"}),{className:"meta-string",begin:/<.*?>/,end:/$/,illegal:"\\n"},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},c={className:"title",begin:t("[a-zA-Z_]\\w*::")+e.IDENT_RE,relevance:0},o=t("[a-zA-Z_]\\w*::")+e.IDENT_RE+"\\s*\\(",l={keyword:"int float while private char char8_t char16_t char32_t catch import module export virtual operator sizeof dynamic_cast|10 typedef const_cast|10 const for static_cast|10 union namespace unsigned long volatile static protected bool template mutable if public friend do goto auto void enum else break extern using asm case typeid wchar_t short reinterpret_cast|10 default double register explicit signed typename try this switch continue inline delete alignas alignof constexpr consteval constinit decltype concept co_await co_return co_yield requires noexcept static_assert thread_local restrict final override atomic_bool atomic_char atomic_schar atomic_uchar atomic_short atomic_ushort atomic_int atomic_uint atomic_long atomic_ulong atomic_llong atomic_ullong new throw return and and_eq bitand bitor compl not not_eq or or_eq xor xor_eq",built_in:"std string wstring cin cout cerr clog stdin stdout stderr stringstream istringstream ostringstream auto_ptr deque list queue stack vector map set bitset multiset multimap unordered_set unordered_map unordered_multiset unordered_multimap array shared_ptr abort terminate abs acos asin atan2 atan calloc ceil cosh cos exit exp fabs floor fmod fprintf fputs free frexp fscanf future isalnum isalpha iscntrl isdigit isgraph islower isprint ispunct isspace isupper isxdigit tolower toupper labs ldexp log10 log malloc realloc memchr memcmp memcpy memset modf pow printf putchar puts scanf sinh sin snprintf sprintf sqrt sscanf strcat strchr strcmp strcpy strcspn strlen strncat strncmp strncpy strpbrk strrchr strspn strstr tanh tan vfprintf vprintf vsprintf endl initializer_list unique_ptr _Bool complex _Complex imaginary _Imaginary",literal:"true false nullptr NULL"},d=[r,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,s,a],_={variants:[{begin:/=/,end:/;/},{begin:/\(/,end:/\)/},{beginKeywords:"new throw return else",end:/;/}],keywords:l,contains:d.concat([{begin:/\(/,end:/\)/,keywords:l,contains:d.concat(["self"]),relevance:0}]),relevance:0},u={className:"function",begin:"("+n+"[\\*&\\s]+)+"+o,returnBegin:!0,end:/[{;=]/,excludeEnd:!0,keywords:l,illegal:/[^\w\s\*&:<>]/,contains:[{begin:"decltype\\(auto\\)",keywords:l,relevance:0},{begin:o,returnBegin:!0,contains:[c],relevance:0},{className:"params",begin:/\(/,end:/\)/,keywords:l,relevance:0,contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,a,s,r,{begin:/\(/,end:/\)/,keywords:l,relevance:0,contains:["self",e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,a,s,r]}]},r,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,i]};return{aliases:["c","cc","h","c++","h++","hpp","hh","hxx","cxx"],keywords:l,disableAutodetect:!0,illegal:"",keywords:l,contains:["self",r]},{begin:e.IDENT_RE+"::",keywords:l},{className:"class",beginKeywords:"class struct",end:/[{;:]/,contains:[{begin://,contains:["self"]},e.TITLE_MODE]}]),exports:{preprocessor:i,strings:a,keywords:l}}}}());hljs.registerLanguage("cpp",function(){"use strict";return function(e){var t=e.getLanguage("c-like").rawDefinition();return t.disableAutodetect=!1,t.name="C++",t.aliases=["cc","c++","h++","hpp","hh","hxx","cxx"],t}}());hljs.registerLanguage("properties",function(){"use strict";return function(e){var n="[ \\t\\f]*",t="("+n+"[:=]"+n+"|[ \\t\\f]+)",a="([^\\\\:= \\t\\f\\n]|\\\\.)+",s={end:t,relevance:0,starts:{className:"string",end:/$/,relevance:0,contains:[{begin:"\\\\\\n"}]}};return{name:".properties",case_insensitive:!0,illegal:/\S/,contains:[e.COMMENT("^\\s*[!#]","$"),{begin:"([^\\\\\\W:= \\t\\f\\n]|\\\\.)+"+t,returnBegin:!0,contains:[{className:"attr",begin:"([^\\\\\\W:= \\t\\f\\n]|\\\\.)+",endsParent:!0,relevance:0}],starts:s},{begin:a+t,returnBegin:!0,relevance:0,contains:[{className:"meta",begin:a,endsParent:!0,relevance:0}],starts:s},{className:"attr",relevance:0,begin:a+n+"$"}]}}}());hljs.registerLanguage("twig",function(){"use strict";return function(e){var a="attribute block constant cycle date dump include max min parent random range source template_from_string",n={beginKeywords:a,keywords:{name:a},relevance:0,contains:[{className:"params",begin:"\\(",end:"\\)"}]},t={begin:/\|[A-Za-z_]+:?/,keywords:"abs batch capitalize column convert_encoding date date_modify default escape filter first format inky_to_html inline_css join json_encode keys last length lower map markdown merge nl2br number_format raw reduce replace reverse round slice sort spaceless split striptags title trim upper url_encode",contains:[n]},s="apply autoescape block deprecated do embed extends filter flush for from if import include macro sandbox set use verbatim with";return s=s+" "+s.split(" ").map((function(e){return"end"+e})).join(" "),{name:"Twig",aliases:["craftcms"],case_insensitive:!0,subLanguage:"xml",contains:[e.COMMENT(/\{#/,/#}/),{className:"template-tag",begin:/\{%/,end:/%}/,contains:[{className:"name",begin:/\w+/,keywords:s,starts:{endsWithParent:!0,contains:[t,n],relevance:0}}]},{className:"template-variable",begin:/\{\{/,end:/}}/,contains:["self",t,n]}]}}}());hljs.registerLanguage("javascript",function(){"use strict";return function(e){var n={begin:/<[A-Za-z0-9\\._:-]+/,end:/\/[A-Za-z0-9\\._:-]+>|\/>/},a="[A-Za-z$_][0-9A-Za-z$_]*",s={keyword:"in of if for while finally var new function do return void else break catch instanceof with throw case default try this switch continue typeof delete let yield const export super debugger as async await static import from as",literal:"true false null undefined NaN Infinity",built_in:"eval isFinite isNaN parseFloat parseInt decodeURI decodeURIComponent encodeURI encodeURIComponent escape unescape Object Function Boolean Error EvalError InternalError RangeError ReferenceError StopIteration SyntaxError TypeError URIError Number Math Date String RegExp Array Float32Array Float64Array Int16Array Int32Array Int8Array Uint16Array Uint32Array Uint8Array Uint8ClampedArray ArrayBuffer DataView JSON Intl arguments require module console window document Symbol Set Map WeakSet WeakMap Proxy Reflect Promise"},r={className:"number",variants:[{begin:"\\b(0[bB][01]+)n?"},{begin:"\\b(0[oO][0-7]+)n?"},{begin:e.C_NUMBER_RE+"n?"}],relevance:0},i={className:"subst",begin:"\\$\\{",end:"\\}",keywords:s,contains:[]},t={begin:"html`",end:"",starts:{end:"`",returnEnd:!1,contains:[e.BACKSLASH_ESCAPE,i],subLanguage:"xml"}},c={begin:"css`",end:"",starts:{end:"`",returnEnd:!1,contains:[e.BACKSLASH_ESCAPE,i],subLanguage:"css"}},o={className:"string",begin:"`",end:"`",contains:[e.BACKSLASH_ESCAPE,i]};i.contains=[e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,t,c,o,r,e.REGEXP_MODE];var l=i.contains.concat([e.C_BLOCK_COMMENT_MODE,e.C_LINE_COMMENT_MODE]),d={className:"params",begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,contains:l};return{name:"JavaScript",aliases:["js","jsx","mjs","cjs"],keywords:s,contains:[{className:"meta",relevance:10,begin:/^\s*['"]use (strict|asm)['"]/},{className:"meta",begin:/^#!/,end:/$/},e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,t,c,o,e.C_LINE_COMMENT_MODE,e.COMMENT("/\\*\\*","\\*/",{relevance:0,contains:[{className:"doctag",begin:"@[A-Za-z]+",contains:[{className:"type",begin:"\\{",end:"\\}",relevance:0},{className:"variable",begin:a+"(?=\\s*(-)|$)",endsParent:!0,relevance:0},{begin:/(?=[^\n])\s/,relevance:0}]}]}),e.C_BLOCK_COMMENT_MODE,r,{begin:/[{,\n]\s*/,relevance:0,contains:[{begin:a+"\\s*:",returnBegin:!0,relevance:0,contains:[{className:"attr",begin:a,relevance:0}]}]},{begin:"("+e.RE_STARTERS_RE+"|\\b(case|return|throw)\\b)\\s*",keywords:"return throw case",contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,e.REGEXP_MODE,{className:"function",begin:"(\\(.*?\\)|"+a+")\\s*=>",returnBegin:!0,end:"\\s*=>",contains:[{className:"params",variants:[{begin:a},{begin:/\(\s*\)/},{begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:s,contains:l}]}]},{begin:/,/,relevance:0},{className:"",begin:/\s/,end:/\s*/,skip:!0},{variants:[{begin:"<>",end:""},{begin:n.begin,end:n.end}],subLanguage:"xml",contains:[{begin:n.begin,end:n.end,skip:!0,contains:["self"]}]}],relevance:0},{className:"function",beginKeywords:"function",end:/\{/,excludeEnd:!0,contains:[e.inherit(e.TITLE_MODE,{begin:a}),d],illegal:/\[|%/},{begin:/\$[(.]/},e.METHOD_GUARD,{className:"class",beginKeywords:"class",end:/[{;=]/,excludeEnd:!0,illegal:/[:"\[\]]/,contains:[{beginKeywords:"extends"},e.UNDERSCORE_TITLE_MODE]},{beginKeywords:"constructor",end:/\{/,excludeEnd:!0},{begin:"(get|set)\\s+(?="+a+"\\()",end:/{/,keywords:"get set",contains:[e.inherit(e.TITLE_MODE,{begin:a}),{begin:/\(\)/},d]}],illegal:/#(?!!)/}}}()); \ No newline at end of file diff --git a/docs/it/assets/jquery.js b/docs/it/assets/jquery.js new file mode 100644 index 0000000..9fd22ca --- /dev/null +++ b/docs/it/assets/jquery.js @@ -0,0 +1,2 @@ +/*! jQuery v3.5.0 | (c) JS Foundation and other contributors | jquery.org/license */ +!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(C,e){"use strict";var t=[],r=Object.getPrototypeOf,s=t.slice,g=t.flat?function(e){return t.flat.call(e)}:function(e){return t.concat.apply([],e)},u=t.push,i=t.indexOf,n={},o=n.toString,v=n.hasOwnProperty,a=v.toString,l=a.call(Object),y={},m=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType},x=function(e){return null!=e&&e===e.window},E=C.document,c={type:!0,src:!0,nonce:!0,noModule:!0};function b(e,t,n){var r,i,o=(n=n||E).createElement("script");if(o.text=e,t)for(r in c)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function w(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[o.call(e)]||"object":typeof e}var f="3.5.0",S=function(e,t){return new S.fn.init(e,t)};function p(e){var t=!!e&&"length"in e&&e.length,n=w(e);return!m(e)&&!x(e)&&("array"===n||0===t||"number"==typeof t&&0+~]|"+M+")"+M+"*"),U=new RegExp(M+"|>"),X=new RegExp(F),V=new RegExp("^"+I+"$"),G={ID:new RegExp("^#("+I+")"),CLASS:new RegExp("^\\.("+I+")"),TAG:new RegExp("^("+I+"|[*])"),ATTR:new RegExp("^"+W),PSEUDO:new RegExp("^"+F),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+R+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/HTML$/i,Q=/^(?:input|select|textarea|button)$/i,J=/^h\d$/i,K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\[\\da-fA-F]{1,6}"+M+"?|\\\\([^\\r\\n\\f])","g"),ne=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},re=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){T()},ae=be(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{H.apply(t=O.call(p.childNodes),p.childNodes),t[p.childNodes.length].nodeType}catch(e){H={apply:t.length?function(e,t){L.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&(T(e),e=e||C,E)){if(11!==p&&(u=Z.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&y(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return H.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&d.getElementsByClassName&&e.getElementsByClassName)return H.apply(n,e.getElementsByClassName(i)),n}if(d.qsa&&!N[t+" "]&&(!v||!v.test(t))&&(1!==p||"object"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===p&&(U.test(t)||z.test(t))){(f=ee.test(t)&&ye(e.parentNode)||e)===e&&d.scope||((s=e.getAttribute("id"))?s=s.replace(re,ie):e.setAttribute("id",s=S)),o=(l=h(t)).length;while(o--)l[o]=(s?"#"+s:":scope")+" "+xe(l[o]);c=l.join(",")}try{return H.apply(n,f.querySelectorAll(c)),n}catch(e){N(t,!0)}finally{s===S&&e.removeAttribute("id")}}}return g(t.replace($,"$1"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function le(e){return e[S]=!0,e}function ce(e){var t=C.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){var n=e.split("|"),r=n.length;while(r--)b.attrHandle[n[r]]=t}function pe(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function de(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return("input"===t||"button"===t)&&e.type===n}}function ge(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ve(a){return le(function(o){return o=+o,le(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ye(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}for(e in d=se.support={},i=se.isXML=function(e){var t=e.namespaceURI,n=(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||"HTML")},T=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:p;return r!=C&&9===r.nodeType&&r.documentElement&&(a=(C=r).documentElement,E=!i(C),p!=C&&(n=C.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",oe,!1):n.attachEvent&&n.attachEvent("onunload",oe)),d.scope=ce(function(e){return a.appendChild(e).appendChild(C.createElement("div")),"undefined"!=typeof e.querySelectorAll&&!e.querySelectorAll(":scope fieldset div").length}),d.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),d.getElementsByTagName=ce(function(e){return e.appendChild(C.createComment("")),!e.getElementsByTagName("*").length}),d.getElementsByClassName=K.test(C.getElementsByClassName),d.getById=ce(function(e){return a.appendChild(e).id=S,!C.getElementsByName||!C.getElementsByName(S).length}),d.getById?(b.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=d.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):d.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},b.find.CLASS=d.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],v=[],(d.qsa=K.test(C.querySelectorAll))&&(ce(function(e){var t;a.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&v.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||v.push("\\["+M+"*(?:value|"+R+")"),e.querySelectorAll("[id~="+S+"-]").length||v.push("~="),(t=C.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||v.push("\\["+M+"*name"+M+"*="+M+"*(?:''|\"\")"),e.querySelectorAll(":checked").length||v.push(":checked"),e.querySelectorAll("a#"+S+"+*").length||v.push(".#.+[+~]"),e.querySelectorAll("\\\f"),v.push("[\\r\\n\\f]")}),ce(function(e){e.innerHTML="";var t=C.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&v.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&v.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&v.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),v.push(",.*:")})),(d.matchesSelector=K.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){d.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",F)}),v=v.length&&new RegExp(v.join("|")),s=s.length&&new RegExp(s.join("|")),t=K.test(a.compareDocumentPosition),y=t||K.test(a.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},D=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!d.sortDetached&&t.compareDocumentPosition(e)===n?e==C||e.ownerDocument==p&&y(p,e)?-1:t==C||t.ownerDocument==p&&y(p,t)?1:u?P(u,e)-P(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e==C?-1:t==C?1:i?-1:o?1:u?P(u,e)-P(u,t):0;if(i===o)return pe(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?pe(a[r],s[r]):a[r]==p?-1:s[r]==p?1:0}),C},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if(T(e),d.matchesSelector&&E&&!N[t+" "]&&(!s||!s.test(t))&&(!v||!v.test(t)))try{var n=c.call(e,t);if(n||d.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){N(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return G.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=h(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=m[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&m(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function D(e,n,r){return m(n)?S.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?S.grep(e,function(e){return e===n!==r}):"string"!=typeof n?S.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(S.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||j,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:q.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof S?t[0]:t,S.merge(this,S.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:E,!0)),N.test(r[1])&&S.isPlainObject(t))for(r in t)m(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=E.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):m(e)?void 0!==n.ready?n.ready(e):e(S):S.makeArray(e,this)}).prototype=S.fn,j=S(E);var L=/^(?:parents|prev(?:Until|All))/,H={children:!0,contents:!0,next:!0,prev:!0};function O(e,t){while((e=e[t])&&1!==e.nodeType);return e}S.fn.extend({has:function(e){var t=S(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,he=/^$|^module$|\/(?:java|ecma)script/i;ce=E.createDocumentFragment().appendChild(E.createElement("div")),(fe=E.createElement("input")).setAttribute("type","radio"),fe.setAttribute("checked","checked"),fe.setAttribute("name","t"),ce.appendChild(fe),y.checkClone=ce.cloneNode(!0).cloneNode(!0).lastChild.checked,ce.innerHTML="",y.noCloneChecked=!!ce.cloneNode(!0).lastChild.defaultValue,ce.innerHTML="",y.option=!!ce.lastChild;var ge={thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};function ve(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&A(e,t)?S.merge([e],n):n}function ye(e,t){for(var n=0,r=e.length;n",""]);var me=/<|&#?\w+;/;function xe(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d\s*$/g;function qe(e,t){return A(e,"table")&&A(11!==t.nodeType?t:t.firstChild,"tr")&&S(e).children("tbody")[0]||e}function Le(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function He(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Oe(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(Y.hasData(e)&&(s=Y.get(e).events))for(i in Y.remove(t,"handle events"),s)for(n=0,r=s[i].length;n").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),E.head.appendChild(r[0])},abort:function(){i&&i()}}});var Ut,Xt=[],Vt=/(=)\?(?=&|$)|\?\?/;S.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=Xt.pop()||S.expando+"_"+Ct.guid++;return this[e]=!0,e}}),S.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Vt.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Vt.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=m(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Vt,"$1"+r):!1!==e.jsonp&&(e.url+=(Et.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||S.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=C[r],C[r]=function(){o=arguments},n.always(function(){void 0===i?S(C).removeProp(r):C[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,Xt.push(r)),o&&m(i)&&i(o[0]),o=i=void 0}),"script"}),y.createHTMLDocument=((Ut=E.implementation.createHTMLDocument("").body).innerHTML="
",2===Ut.childNodes.length),S.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(y.createHTMLDocument?((r=(t=E.implementation.createHTMLDocument("")).createElement("base")).href=E.location.href,t.head.appendChild(r)):t=E),o=!n&&[],(i=N.exec(e))?[t.createElement(i[1])]:(i=xe([e],t,o),o&&o.length&&S(o).remove(),S.merge([],i.childNodes)));var r,i,o},S.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(S.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},S.expr.pseudos.animated=function(t){return S.grep(S.timers,function(e){return t===e.elem}).length},S.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=S.css(e,"position"),c=S(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=S.css(e,"top"),u=S.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),m(t)&&(t=t.call(e,n,S.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):("number"==typeof f.top&&(f.top+="px"),"number"==typeof f.left&&(f.left+="px"),c.css(f))}},S.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){S.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===S.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===S.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=S(e).offset()).top+=S.css(e,"borderTopWidth",!0),i.left+=S.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-S.css(r,"marginTop",!0),left:t.left-i.left-S.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===S.css(e,"position"))e=e.offsetParent;return e||re})}}),S.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;S.fn[t]=function(e){return $(this,function(e,t,n){var r;if(x(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),S.each(["top","left"],function(e,n){S.cssHooks[n]=$e(y.pixelPosition,function(e,t){if(t)return t=Be(e,n),Me.test(t)?S(e).position()[n]+"px":t})}),S.each({Height:"height",Width:"width"},function(a,s){S.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){S.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return $(this,function(e,t,n){var r;return x(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?S.css(e,t,i):S.style(e,t,n,i)},s,n?e:void 0,n)}})}),S.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){S.fn[t]=function(e){return this.on(t,e)}}),S.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),S.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){S.fn[n]=function(e,t){return 0 a",this.offsets=[],this.targets=[],this.activeTarget=null,this.scrollHeight=0,this.$scrollElement.on("scroll.bs.scrollspy",r.proxy(this.process,this)),this.refresh(),this.process()}function s(i){return this.each(function(){var t=r(this),s=t.data("bs.scrollspy"),e="object"==typeof i&&i;s||t.data("bs.scrollspy",s=new o(this,e)),"string"==typeof i&&s[i]()})}o.VERSION="3.3.7",o.DEFAULTS={offset:10},o.prototype.getScrollHeight=function(){return this.$scrollElement[0].scrollHeight||Math.max(this.$body[0].scrollHeight,document.documentElement.scrollHeight)},o.prototype.refresh=function(){var t=this,i="offset",o=0;this.offsets=[],this.targets=[],this.scrollHeight=this.getScrollHeight(),r.isWindow(this.$scrollElement[0])||(i="position",o=this.$scrollElement.scrollTop()),this.$body.find(this.selector).map(function(){var t=r(this),s=t.data("target")||t.attr("href"),e=/^#./.test(s)&&r(s);return e&&e.length&&e.is(":visible")&&[[e[i]().top+o,s]]||null}).sort(function(t,s){return t[0]-s[0]}).each(function(){t.offsets.push(this[0]),t.targets.push(this[1])})},o.prototype.process=function(){var t,s=this.$scrollElement.scrollTop()+this.options.offset,e=this.getScrollHeight(),i=this.options.offset+e-this.$scrollElement.height(),o=this.offsets,r=this.targets,l=this.activeTarget;if(this.scrollHeight!=e&&this.refresh(),i<=s)return l!=(t=r[r.length-1])&&this.activate(t);if(l&&s=o[t]&&(void 0===o[t+1]||s'))&&w.css("position",n.css("position")),(v=function(){var t,o,i;if(!f)return k=x.height(),t=parseInt(b.css("border-top-width"),10),o=parseInt(b.css("padding-top"),10),l=parseInt(b.css("padding-bottom"),10),a=b.offset().top+t+o,c=b.height(),g&&(h=g=!1,null==V&&(n.insertAfter(w),w.detach()),n.css({position:"",top:"",width:"",bottom:""}).removeClass(j),i=!0),p=n.offset().top-(parseInt(n.css("margin-top"),10)||0)-F,d=n.outerHeight(!0),u=n.css("float"),w&&w.css({width:r(n),height:d,display:n.css("display"),"vertical-align":n.css("vertical-align"),float:u}),i?e():void 0})(),m=void 0,y=F,_=A,e=function(){var t,o,i,e,s,r;if(d!==c&&!f)return i=!1,null!=_&&(_-=1)<=0&&(_=A,v(),i=!0),i||x.height()===k||(v(),i=!0),e=Q.scrollTop(),null!=m&&(o=e-m),m=e,g?(C&&(s=c+a + + + + + + + + VR Payment Shopware 6 Documentation + + + + +
+
+

VR Payment Shopware 6 Documentation

+

Documentation

+ +
+
+
+
+
+
+

+ 1Panoramica

+
+
+
+

Il Wallee Payment Plugin integra l’elaborazione moderna dei pagamenti in Shopware 6, offrendo funzionalità come pagamenti basati su iFrame, rimborsi, acquisizioni e conformità PCI. Supporta l’integrazione perfetta con il [Portale VR Payment](https://gateway.vr-payment.de/) per la gestione delle transazioni e dei metodi di pagamento.

+
+

Importante: Si prega di notare che solo gli aggiornamenti Major (ad es. 6.x.0.0) e Minor (ad es. 6.0.x.0) saranno testati per la compatibilità entro 2 settimane dal rilascio.

+
+
+
+

+ 2Requisiti

+
+
+
+

Versione di Shopware: 6.5.x o 6.6.x (vedere la tabella di compatibilità).

+
+

PHP: Versione minima richiesta dalla vostra installazione di Shopware (ad es. 7.4+).

+
+

Account Wallee: Ottenere Space ID, User ID e API Key dal Pannello di Controllo Wallee..

+
+
+
+

+ 3Compatibilità

+
+
+ ++++++ + + + + + + + + + + + + + + + + + + + + +

Shopware Version

Plugin Version

PHP Version

Support Until

6.6.x

6.x.x

PHP 8.2 and 8.3

Ongoing

6.5.x - Deprecated

5.x.x

PHP 8.2 and 8.3

October 2024

+
+
+

+ 4Installazione

+
+
+
+
+

+ 4.1Tramite il Marketplace

+
+
+
+
    +
  1. +

    Accedete al backend del vostro negozio Shopware.

    +
  2. +
  3. +

    Navigate su Impostazioni → Sistema → Plugin.

    +
  4. +
  5. +

    Cliccate sul simbolo del menu a tendina e selezionate il link Installa del plugin per installarlo

    +
    +
    +plugin installation +
    +
    +
  6. +
  7. +

    Attivate il plugin Wallee Payment dal Gestore Plugin.

    +
  8. +
+
+
+
+

+ 4.2Tramite Composer (Raccomandato)

+
+
+
+
    +
  1. +

    Navigate nella directory principale del vostro Shopware.

    +
  2. +
  3. +

    Eseguite:

    +
    +
    +
    composer require vrpayment/shopware-6
    +php bin/console plugin:refresh
    +php bin/console plugin:install --activate --clearCache VRPaymentPayment
    +
    +
    +
  4. +
+
+
+
+

+ 4.3Installazione manuale

+
+
+
+
    +
  1. +

    Scaricate l’ultima versione..

    +
  2. +
  3. +

    Estraete il file ZIP in custom/plugins/

    +
  4. +
  5. +

    Eseguite

    +
    +
    +
    php bin/console plugin:refresh
    +php bin/console plugin:install --activate --clearCache VRPaymentPayment
    +
    +
    +
  6. +
+
+
+
+
+

+ 5Guida Rapida al Portale

+
+
+
+

Andate su VR Payment e create un account se non ne avete già uno.

+
+ + + + + +
+
Tip
+
+Selezionate il piano di abbonamento appropriato: dovrebbe supportare le transazioni di e-commerce. +
+
+
+

+ 5.1Create la chiave API:

+
+
+
+
    +
  1. +

    Una volta attivato il vostro account, navigate su Account → Utenti Applicazione.

    +
    +
    +application users +
    +
    +
  2. +
  3. +

    Cliccate su Crea utente dell’aplicazione

    +
    +
    +user +
    +
    +
  4. +
  5. +

    Specificate un nome per questa chiave API – io inserirò Test Shop – e cliccate su Crea utente dell’applicazione.

    +
    +
    +api key +
    +
    +
  6. +
  7. +

    Il vostro Utente dell’Applicazione verrà quindi creato. Copiate l’ID Utente e la Chiave di Autenticazione, poiché vi serviranno per collegare il vostro negozio al portale.

    +
    +
    +token +
    +
    +
  8. +
  9. +

    Infine, dovrete concedere alcune autorizzazioni a questo Utente Applicazione in modo che possa comunicare con il vostro space. Per farlo, sotto Ruoli, navigate su Gestisci.

    +
    +
    +roles +
    +
    +
  10. +
  11. +

    Cliccate sul segno + accanto a Ruoli Spazio.

    +
    +
    +assign role +
    +
    +
  12. +
  13. +

    Aggiungete il ruolo Space Admin e cliccate su Assegna Ruolo .

    +
    + + + + + +
    +
    Tip
    +
    +Si prega di notare che il caricamento dei ruoli potrebbe richiedere alcuni secondi +
    +
    +
    +
    +loading roles +
    +
    +
  14. +
  15. +

    Infine, cliccate su Salva i Ruoli; dovrete inserire la vostra password per confermare.

    +
    +
    +save role +
    +
    +
  16. +
+
+
+
+

+ 5.2Configurate i Metodi di Pagamento

+
+
+
+

Ora potete configurare i metodi di pagamento che desiderate avere; per scopi di test, utilizzeremo il Processore Fittizio (Bogus Processor). Si prega di notare che se desiderate eseguire transazioni di produzione, potete utilizzare la nostra offerta Payfac o connettervi direttamente a uno specifico PSP (Worldline; ACI; …).

+
+
    +
  1. +

    Navigate su Spazio → Impostazioni e cliccate su Processori.

    +
    +
    +payment settings +
    +
    +
  2. +
  3. +

    Cliccate su Configura processore

    +
  4. +
  5. +

    Selezionate il processore che desiderate e cliccate su Continua.

    +
    +
    +bogus processor +
    +
    +
  6. +
  7. +

    Impostate il nome che desiderate dare al vostro processore – ad es. Test Processor – e cliccate su Crea.

    +
    +
    +name processor +
    +
    +
  8. +
  9. +

    Selezionate tutti i Connettori applicabili e cliccate su Salva

    +
    + + + + + +
    +
    Tip
    +
    +Si prega di notare che i connettori sembrano duplicati, ma è perché uno è per il pagamento tramite terminale fisico e l’altro per il pagamento e-commerce. +
    +
    +
    +
    +connectors +
    +
    +
  10. +
+
+

I metodi di pagamento sono ora disponibili nel portale

+
+
+
+
+

+ 6Guida Rapida al Shop

+
+
+
+
    +
  1. +

    Credenziali API

    +
    +
      +
    1. +

      Navigate su Shopware Admin → Impostazioni → Estensioni → VR Payment Payment e cliccate su Salva.

      +
    2. +
    3. +

      Inserite il vostro Space ID, User ID, e API Key

      +
      +
        +
      1. +

        Potete trovare lo Space ID navigando su Spazio – ad es. Space id = 76231

        +
        +
        +plugin configuration +
        +
        +
      2. +
      +
      +
    4. +
    +
    +
  2. +
  3. +

    Metodi di Pagamento

    +
    +

    I metodi di pagamento disponibili al checkout sono gestiti dal [Portale VR Payment](https://gateway.vr-payment.de/). Se desiderate disabilitare un metodo di pagamento, dovrete disabilitarlo dal portale.

    +
    +
    + + + + + +
    +
    Note
    +
    +Si prega di notare che, poiché esiste una sincronizzazione tra il portale e il shop, potete fare la stessa cosa dal shop in Impostazioni → Metodi di Pagamento. +
    +
    +
    +
      +
    1. +

      Andate su Space → Impostazioni → Pagamento → Modalita di pagamento

      +
      +
      +payment methods +
      +
      +
    2. +
    3. +

      Selezionate il metodo di pagamento che desiderate disabilitare. Cliccate sul cursore per disabilitarlo → dovrebbe diventare inattivo.

      +
      +
      +cc enable +
      +
      +
      +
      +cc disable +
      +
      +
      + + + + + +
      +
      Tip
      +
      +Se desiderate disabilitare solo un Connettore da un Metodo di Pagamento (ad es. Mastercard per una Carta di Credito), andate alla sezione Connettore +
      +
      +
    4. +
    +
    +
  4. +
  5. +

    Opzioni

    +
    +
      +
    1. +

      Space View Id: Questo campo vi permette di applicare stili personalizzati al modulo di pagamento e alla pagina di pagamento. Lo stile è definito nelle impostazioni del vostro Spazio nel Portale

      +
      + + + + + +
      +
      Note
      +
      +Si prega di notare che se non utilizzate lo Space View Id, questa opzione dovrebbe rimanere vuota. +
      +
      +
    2. +
    3. +

      Integrazione: L’impostazione Opzioni di Integrazione determina come viene visualizzato il modulo di pagamento durante il processo di checkout. Sono disponibili le seguenti opzioni:

      +
      +
        +
      1. +

        IFrame: Incorpora il modulo di pagamento direttamente all’interno della pagina di checkout di Shopware per un’esperienza senza interruzioni.

        +
      2. +
      3. +

        Lightbox: Apre una finestra popup sicura in cui i clienti possono completare il pagamento senza lasciare la pagina di checkout.

        +
      4. +
      5. +

        Payment Page: Reindirizza i clienti a una pagina di pagamento dedicata ospitata dal fornitore di servizi di pagamento.

        +
      6. +
      +
      +
    4. +
    5. +

      Line Item Consistency: Shopware calcola le tasse a livello di voce di riga, il che può comportare piccole discrepanze (tipicamente pochi centesimi) tra l’imposta totale dell’ordine e il prezzo visualizzato. Questa discrepanza si verifica a causa di differenze di arrotondamento durante i calcoli delle singole voci di riga. Se l’impostazione "Imponi coerenza" è abilitata, il portale rifiuterà automaticamente gli ordini con tali discrepanze. Per evitare problemi di elaborazione dei pagamenti, si consiglia di disabilitare questa impostazione a meno che non sia richiesta una rigorosa convalida del totale delle imposte.

      +
    6. +
    7. +

      Invia Email di Conferma Ordine: Abilitate questa opzione per inviare email di conferma dell’ordine direttamente da Shopware anziché dal Portale.

      +
      +
      +order confirmation email +
      +
      +
    8. +
    +
    +
  6. +
+
+
+
+

+ 7Grafico dello Stato della Transazione

+
+
+
+

Il processo di pagamento di Wallee è completamente standardizzato per ogni metodo di pagamento che potete elaborare. Questo vi dà la possibilità di aggiungere semplicemente un metodo di pagamento o un processore senza modifiche all’interno della vostra configurazione di Shopware. Una panoramica degli stati e dei processi di pagamento di Wallee è disponibile nella Documentazione sui Pagamenti.

+
+

Nella sezione seguente, forniamo una panoramica di come gli stati di Wallee sono mappati nel grafico degli stati di Shopware per gli ordini e gli stati di pagamento.

+
+
+

+ 7.1Mappatura degli Stati degli Ordini di Shopware

+
+
+
+

Attualmente non modifichiamo lo stato dell’Ordine. Modifichiamo solo lo stato del Pagamento e lo stato della Spedizione.

+
+
+

+ 7.1.1Osservazioni Generali Riguardo agli Stati degli Ordini

+
+
+
+

Raccomandiamo di modificare lo stato dell’Ordine solo quando lo stato del Pagamento ha raggiunto uno stato finale.

+
+
+
+
+

+ 7.2Mappatura dello Stato di Pagamento di Shopware

+
+
+
+

Di seguito trovate un diagramma che mostra la macchina a stati di Shopware per lo stato di pagamento, incluse informazioni aggiuntive per le transizioni di stato.

+
+
+shopware 6 stage graph order +
+
+
    +
  1. +

    Se la transazione è Authorized in VR Payment, lo stato di pagamento dell’ordine di Shopware è contrassegnato come In Progress.

    +
  2. +
  3. +

    Se la transazione fallisce prima o durante il processo di autorizzazione, lo stato di pagamento dell’ordine di Shopware è contrassegnato come Failed.

    +
  4. +
  5. +

    Se la transazione fallisce dopo l’autorizzazione, lo stato di pagamento dell’ordine di Shopware è contrassegnato come Cancelled.

    +
  6. +
  7. +

    Se la fattura della transazione in VR Payment è contrassegnata come Paid o Not Applicable, the lo stato di pagamento dell’ordine di Shopware è contrassegnato come Paid.

    +
  8. +
+
+
+

+ 7.2.1Osservazioni Generali Riguardo agli Stati di Pagamento

+
+
+
+

Raccomandiamo di non modificare manualmente lo stato di pagamento. Se lo fate, potrebbe essere modificato nuovamente dal plugin.

+
+
+
+
+

+ 7.3Mappatura dello Stato di Spedizione di Shopware

+
+
+
+

Di seguito trovate un diagramma che mostra la macchina a stati di Shopware per lo stato di spedizione, incluse informazioni aggiuntive per le transizioni di stato.

+
+
+shopware 6 stage graph delivery +
+
+
    +
  1. +

    Se la transazione ha lo stato Confirmed in VR Payment, lo stato di spedizione dell’ordine di Shopware è contrassegnato come On Hold..

    +
  2. +
  3. +

    Se la transazione in VR Payment è contrassegnata come Fulfill, lo stato di spedizione dell’ordine di Shopware è contrassegnato come Open.

    +
  4. +
  5. +

    Se la transazione è in stato Decline, Failed or Voided, lo stato di spedizione dell’ordine di Shopware è contrassegnato come Cancelled.

    +
  6. +
+
+
+
+
+

+ 8Gestione delle Transazioni

+
+
+
+

Potete acquisire, annullare e rimborsare le transazioni direttamente dal backend di Shopware. Si prega di notare che se rimborsate, annullate o acquisite transazioni all’interno di VR Payment, gli eventi verranno sincronizzati in Shopware. Tuttavia, ci sono alcune limitazioni (vedere sotto).

+
+
+

+ 8.1Completare (Acquisire) un Ordine

+
+
+
+

YAvete la possibilità che per le vostre transazioni il pagamento venga autorizzato solo dopo l’inserimento dell’ordine. All’interno della configurazione del connettore avete l’opzione, se il metodo di pagamento lo supporta, di definire se il pagamento debba essere completato immediatamente o differito.

+
+

Per acquisire una transazione, aprite l’ordine e cliccate sul pulsante Complete.

+
+ + + + + +
+
Note
+
+Quando il completamento è in sospeso in Wallee, l’ordine rimarrà nello stato "in sospeso". +
+
+
+capture transaction +
+
+

Completamento Differito del Pagamento

+
+

I rivenditori spesso si trovano nella situazione in cui desiderano solo autorizzare le transazioni e avviare il processo di evasione solo quando tutti gli articoli sono spedibili. Questo è possibile anche con VR Payment.

+
+

Tuttavia, è necessario seguire determinati processi. Se avete configurato il completamento del pagamento come differito, dovreste acquisire la transazione prima di avviare la spedizione, poiché può sempre accadere che un completamento fallisca. Se volete essere sicuri di non spedire articoli per i quali non siete stati pagati, dovreste posticipare la spedizione fino al raggiungimento dello stato di evasione. Inizialmente, la transazione sarà nello stato Authorized in VR Payment e In Progress in Shopware. Se desiderate avviare il processo di evasione, assicuratevi di avviare il processo di completamento come descritto sopra. Una volta che il completamento è andato a buon fine, l’ordine passerà allo stato Fulfill in VR Payment e allo stato Paid in Shopware. Ora potete avviare il processo di evasione.

+
+
+
+

+ 8.2Annullare una transazione

+
+
+
+

Per annullare una transazione, apri l’ordine e clicca sul pulsante Cancel authorization.

+
+ + + + + +
+
Note
+
+Puoi annullare solo le transazioni che non sono ancora state completate +
+
+
+void transaction +
+
+
+
+

+ 8.3Rimborso di una Transazione

+
+
+
+

Avete la possibilità di rimborsare le transazioni già completate. Per farlo, aprite l’ordine acquisito. Cliccando sui 3 punti (…) su una voce di riga, potete rimborsare la voce di riga parzialmente (se ha una quantità superiore a 1) o potete rimborsare l’intera voce di riga. Nel caso in cui il metodo di pagamento non supporti i rimborsi, non vedrete la possibilità di emettere rimborsi online.

+
+
+refund transaction +
+
+

Potete effettuare tutti i rimborsi individuali che desiderate fino a raggiungere l’importo totale dell’ordine originale. Lo stato dell’ordine passerà quindi automaticamente a "completato".

+
+ + + + + +
+
Note
+
+Potrebbe volerci un po' di tempo prima che vediate il rimborso in Shopware. I rimborsi saranno visibili solo una volta che saranno stati elaborati con successo. +
+
+
+
+

+ 8.4Ordini in Attesa

+
+
+
+

La spedizione non dovrebbe essere effettuata mentre lo stato di spedizione è Hold. Questo accade quando la transazione in Wallee non ha raggiunto lo stato di evasione.

+
+

Ci sono essenzialmente due motivi per cui questo può accadere:

+
+
    +
  • +

    La transazione non è completata. In questo caso, dovete completare la transazione come scritto sopra.

    +
  • +
  • +

    Non siamo in grado di dire se dovete evadere l’ordine. La decisione di spedizione viene presa automaticamente. Se ciò non accade entro il periodo di tempo definito, Wallee genererà un’attività manuale che dovreste osservare e seguire le istruzioni.

    +
  • +
+
+

Potete trovare maggiori informazioni sulle attività manuali nella nostra Documentazione sulle Attività Manuali..

+
+
+
+

+ 8.5Limitazioni della Sincronizzazione tra Wallee e Shopware

+
+
+
+

Si prega di notare che le acquisizioni, le annullazioni e i rimborsi effettuati in Wallee vengono sincronizzati. Tuttavia, ci sono alcune limitazioni. All’interno di Wallee, potete modificare contemporaneamente il prezzo unitario e la quantità. Questo non può essere fatto nel backend di Shopware. Raccomandiamo pertanto di effettuare sempre i rimborsi all’interno del backend di Shopware e non all’interno di Wallee. Se un rimborso non può essere sincronizzato, verrà inviato al processore, ma potrebbe essere che non lo vediate all’interno del vostro backend di Shopware.

+
+

Potete trovare maggiori informazioni sui Rimborsi in Wallee nella nostra Documentazione sui Rimborsi..

+
+
+
+

+ 8.6Tokenization

+
+
+
+

INel caso in cui il metodo di pagamento supporti la tokenizzazione, potete memorizzare i dettagli di pagamento dei vostri clienti per acquisti futuri. Per utilizzare questa funzionalità, assicuratevi che la Modalità di One-Click-Payment Mode inella configurazione del vostro metodo di pagamento sia impostata su allow o force.

+
+ + + + + +
+
Note
+
+La tokenizzazione non è disponibile per i checkout degli ospiti. +
+
+
+
+

+ 8.7Key Features

+
+
+
+
    +
  • +

    iFrame Integration: Incorpora i moduli di pagamento direttamente nel tuo checkout.

    +
  • +
  • +

    Refunds & Captures: Attiva rimborsi totali/parziali e acquisizioni da Shopware o dal Portale VR Payment.

    +
  • +
  • +

    Multi-Store Support: Gestisci le configurazioni su più negozi.

    +
  • +
  • +

    Automatic Updates: I metodi di pagamento si sincronizzano dinamicamente tramite l’API VR Payment.

    +
  • +
+
+
+
+

+ 8.8Risoluzione dei Problemi

+
+
+
+
    +
  • +

    Logs: Controlla i log dei pagamenti con:

    +
    +
    +
    tail -f var/log/whitelabelname_payment*.log
    +
    +
    +
  • +
  • +

    Problemi Comuni:

    +
    +
      +
    • +

      Assicurati che composer update whitelabelname/shopware-6 venga eseguito dopo gli aggiornamenti.

      +
    • +
    • +

      Verifica che le credenziali API corrispondano al tuo account VR Payment.

      +
    • +
    +
    +
  • +
+
+
+
+

+ 8.9FAQs

+
+
+
+

D: Come posso essere sicuro che la connessione tra il portale e il negozio funzioni?

+
+

R: Dovrete verificare se i webhook sono stati creati correttamente. Per farlo, navigate nel vostro space all’interno del portale, andate su Impostazioni → Generale → Webhook Listener.

+
+
+webhooks +
+
+
+webhook listeners +
+
+

D: Questo plugin supporta i one-click payments?

+
+

R: Sì, tramite tokenizzazione nel portale VR Payment.

+
+

D: Come gestisco la conformità PCI?

+
+

R: Il plugin utilizza l’integrazione iFrame, riducendo i requisiti PCI a SAQ-A.

+
+

D: Il plugin supporta Apple Pay?

+
+

R: Sì, il plugin supporta wallet come Apple Pay.

+
+
+
+
+

+ 9Changelog

+
+
+
+

Per gli aggiornamenti specifici della versione, consultate le Release di GitHub.

+
+
+
+

+ 10Contribuzione

+
+
+
+

Segnalate i problemi tramite le Issue di GitHub.

+
+

Seguite la Guida Base per lo Sviluppo di Plugin per Shopware.

+
+
+
+

+ 11Support

+
+
+
+

se hai bisogno di aiuto contatta il nostro supporto.

+
+
+
+
+ +
+
+ + + + + + + + diff --git a/src/Core/Api/Configuration/Controller/ConfigurationController.php b/src/Core/Api/Configuration/Controller/ConfigurationController.php new file mode 100644 index 0000000..221090c --- /dev/null +++ b/src/Core/Api/Configuration/Controller/ConfigurationController.php @@ -0,0 +1,238 @@ + ['api']])] +class ConfigurationController extends AbstractController { + + /** + * @var \VRPaymentPayment\Core\Api\WebHooks\Service\WebHooksService + */ + protected $webHooksService; + + /** + * @var \VRPaymentPayment\Core\Api\Space\Service\SpaceService + */ + protected $spaceService; + + /** + * @var \Psr\Log\LoggerInterface + */ + protected $logger; + + /** + * @var \VRPaymentPayment\Core\Settings\Service\SettingsService + */ + protected $settingsService; + + /** + * @var \VRPaymentPayment\Core\Util\PaymentMethodUtil + */ + private $paymentMethodUtil; + + /** + * @var \VRPaymentPayment\Core\Api\PaymentMethodConfiguration\Service\PaymentMethodConfigurationService + */ + private $paymentMethodConfigurationService; + + /** + * @param PaymentMethodUtil $paymentMethodUtil + * @param PaymentMethodConfigurationService $paymentMethodConfigurationService + * @param WebHooksService $webHooksService + * @param SpaceService $spaceService + * @param SettingsService $settingsService + */ + public function __construct( + PaymentMethodUtil $paymentMethodUtil, + PaymentMethodConfigurationService $paymentMethodConfigurationService, + WebHooksService $webHooksService, + SpaceService $spaceService, + SettingsService $settingsService + ) + { + $this->webHooksService = $webHooksService; + $this->spaceService = $spaceService; + $this->paymentMethodUtil = $paymentMethodUtil; + $this->paymentMethodConfigurationService = $paymentMethodConfigurationService; + $this->settingsService = $settingsService; + } + + /** + * @param \Psr\Log\LoggerInterface $logger + * @internal + * @required + * + */ + public function setLogger(LoggerInterface $logger): void + { + $this->logger = $logger; + } + + /** + * Set VRPaymentPayment as the default payment for a give sales channel + * + * @param \Symfony\Component\HttpFoundation\Request $request + * @param \Shopware\Core\Framework\Context $context + * @return \Symfony\Component\HttpFoundation\JsonResponse + * + */ + #[Route("/api/_action/vrpayment/configuration/set-vrpayment-as-sales-channel-payment-default", + name: "api.action.vrpayment.configuration.set-vrpayment-as-sales-channel-payment-default", + methods: ['POST'])] + public function setVRPaymentAsSalesChannelPaymentDefault(Request $request, Context $context): JsonResponse + { + $salesChannelId = $request->request->get('salesChannelId'); + $salesChannelId = ($salesChannelId == 'null') ? null : $salesChannelId; + + $this->paymentMethodUtil->setVRPaymentAsDefaultPaymentMethod($context, $salesChannelId); + return new JsonResponse([]); + } + + /** + * Register web hooks + * + * @param \Symfony\Component\HttpFoundation\Request $request + * @return \Symfony\Component\HttpFoundation\JsonResponse + * @throws \VRPayment\Sdk\ApiException + * @throws \VRPayment\Sdk\Http\ConnectionException + * @throws \VRPayment\Sdk\VersioningException + * + */ + #[Route("/api/_action/vrpayment/configuration/register-web-hooks", + name: "api.action.vrpayment.configuration.register-web-hooks", + methods: ['POST'])] + public function registerWebHooks(Request $request): JsonResponse + { + $settings = $this->settingsService->getSettings(); + if ($settings->isWebhooksUpdateEnabled() === false) { + $this->logger->info('Webhooks update disabled by settings'); + return new JsonResponse([]); + } + + $salesChannelId = $request->request->get('salesChannelId'); + $salesChannelId = ($salesChannelId == 'null') ? null : $salesChannelId; + + $result = $this->webHooksService->setSalesChannelId($salesChannelId)->install(); + + return new JsonResponse(['result' => $result]); + } + + /** + * Test API connection + * If the API data is incorrect, an entry must appear in the event log file in the Shopware folder /var/log/ + * @see https://developer.shopware.com/docs/resources/guidelines/testing/store/quality-guidelines-plugins/#every-app-accessing-external-api-services + * + * @param \Symfony\Component\HttpFoundation\Request $request + * @return \Symfony\Component\HttpFoundation\JsonResponse + * @throws \VRPayment\Sdk\ApiException + * @throws \VRPayment\Sdk\Http\ConnectionException + * @throws \VRPayment\Sdk\VersioningException + * + */ + #[Route("/api/_action/vrpayment/configuration/check-api-connection", + name: "api.action.vrpayment.configuration.check-api-connection", + methods: ['POST'])] + public function checkApiConnection(Request $request): JsonResponse + { + $spaceId = (int)$request->request->getInt('spaceId'); + $userId = (int)$request->request->getInt('userId'); + $applicationId = $request->request->get('applicationId'); + + $result = $this->spaceService + ->setSpaceId($spaceId) + ->setUserId($userId) + ->setApplicationId($applicationId) + ->checkSpace(); + + if (null === $result) { + $this->logger->error('API test connection was failed. Wrong credentials'); + return new JsonResponse([['result' => 400]]); + } + + $this->logger->info('API test connection was successfully tested.'); + return new JsonResponse(['result' => 200]); + } + + /** + * Synchronize payment method configurations + * + * @param \Symfony\Component\HttpFoundation\Request $request + * @param \Shopware\Core\Framework\Context $context + * @return \Symfony\Component\HttpFoundation\JsonResponse + * + */ + #[Route("/api/_action/vrpayment/configuration/synchronize-payment-method-configuration", + name: "api.action.vrpayment.configuration.synchronize-payment-method-configuration", + methods: ['POST'])] + public function synchronizePaymentMethodConfiguration(Request $request, Context $context): JsonResponse + { + $settings = $this->settingsService->getSettings(); + if ($settings->isPaymentsUpdateEnabled() === false) { + $this->logger->info('Payment methods update disabled by settings'); + return new JsonResponse([]); + } + + $salesChannelId = $request->request->get('salesChannelId'); + $salesChannelId = ($salesChannelId == 'null') ? null : $salesChannelId; + $status = Response::HTTP_OK; + try { + $result = $this->paymentMethodConfigurationService->setSalesChannelId($salesChannelId)->synchronize($context); + } catch (\Exception $exception) { + $status = Response::HTTP_NOT_ACCEPTABLE; + $result = [ + 'errorTitle' => $exception->getMessage(), + 'errorMessage' => $exception->getTraceAsString() + ]; + $this->logger->emergency($exception->getTraceAsString()); + } + + return new JsonResponse(['result' => $result], $status); + } + + /** + * Install OrderDeliveryStates + * + * @param \Shopware\Core\Framework\Context $context + * @return \Symfony\Component\HttpFoundation\JsonResponse + * + */ + #[Route("/api/_action/vrpayment/configuration/install-order-delivery-states", + name: "api.action.vrpayment.configuration.install-order-delivery-states", + methods: ['POST'])] + public function installOrderDeliveryStates(Context $context): JsonResponse + { + /** + * @var \VRPaymentPayment\Core\Api\OrderDeliveryState\Service\OrderDeliveryStateService $orderDeliveryStateService + */ + $orderDeliveryStateService = $this->container->get(OrderDeliveryStateService::class); + $orderDeliveryStateService->install($context); + + return new JsonResponse([]); + } +} diff --git a/src/Core/Api/OrderDeliveryState/Command/OrderDeliveryStateCommand.php b/src/Core/Api/OrderDeliveryState/Command/OrderDeliveryStateCommand.php new file mode 100644 index 0000000..bfa3607 --- /dev/null +++ b/src/Core/Api/OrderDeliveryState/Command/OrderDeliveryStateCommand.php @@ -0,0 +1,59 @@ +orderDeliveryStateService = $orderDeliveryStateService; + } + + /** + * @param \Symfony\Component\Console\Input\InputInterface $input + * @param \Symfony\Component\Console\Output\OutputInterface $output + * @return int + */ + protected function execute(InputInterface $input, OutputInterface $output): int + { + $output->writeln('Install VRPaymentPayment extra delivery states...'); + $this->orderDeliveryStateService->install(Context::createDefaultContext()); + return 0; + } + + /** + * Configures the current command. + */ + protected function configure() + { + $this->setDescription('Installs VRPaymentPayment extra delivery states.') + ->setHelp('This command installs VRPaymentPayment extra delivery states.'); + } + +} diff --git a/src/Core/Api/OrderDeliveryState/Handler/OrderDeliveryStateHandler.php b/src/Core/Api/OrderDeliveryState/Handler/OrderDeliveryStateHandler.php new file mode 100644 index 0000000..6eae659 --- /dev/null +++ b/src/Core/Api/OrderDeliveryState/Handler/OrderDeliveryStateHandler.php @@ -0,0 +1,88 @@ +stateMachineRegistry = $stateMachineRegistry; + } + + /** + * @param string $entityId + * @param \Shopware\Core\Framework\Context $context + */ + public function hold(string $entityId, Context $context): void + { + $this->stateMachineRegistry->transition( + new Transition( + OrderDeliveryDefinition::ENTITY_NAME, + $entityId, + self::ACTION_HOLD, + 'stateId' + ), + $context + ); + } + + /** + * @param string $entityId + * @param \Shopware\Core\Framework\Context $context + */ + public function unhold(string $entityId, Context $context): void + { + $this->stateMachineRegistry->transition( + new Transition( + OrderDeliveryDefinition::ENTITY_NAME, + $entityId, + self::ACTION_UNHOLD, + 'stateId' + ), + $context + ); + } + + /** + * @param string $entityId + * @param \Shopware\Core\Framework\Context $context + */ + public function cancel(string $entityId, Context $context): void + { + $this->stateMachineRegistry->transition( + new Transition( + OrderDeliveryDefinition::ENTITY_NAME, + $entityId, + StateMachineTransitionActions::ACTION_CANCEL, + 'stateId' + ), + $context + ); + } +} \ No newline at end of file diff --git a/src/Core/Api/OrderDeliveryState/Service/OrderDeliveryStateService.php b/src/Core/Api/OrderDeliveryState/Service/OrderDeliveryStateService.php new file mode 100644 index 0000000..720a398 --- /dev/null +++ b/src/Core/Api/OrderDeliveryState/Service/OrderDeliveryStateService.php @@ -0,0 +1,197 @@ +container = $container; + $this->localeCodeProvider = $this->container->get(LocaleCodeProvider::class); + $this->stateMachineRepository = $this->container->get('state_machine.repository'); + $this->stateMachineStateRepository = $this->container->get('state_machine_state.repository'); + $this->stateMachineTransitionRepository = $this->container->get('state_machine_transition.repository'); + } + + /** + * @param \Shopware\Core\Framework\Context $context + */ + public function install(Context $context): void + { + $stateMachineId = $this->getStateMachineEntity($context); + $holdStateId = $this->getHoldStateId($stateMachineId, $context); + $openStateId = $this->getOpenStateId($stateMachineId, $context); + + $this->upsertHoldTransition($stateMachineId, $openStateId, $holdStateId, $context); + $this->upsertUnholdTransition($stateMachineId, $holdStateId, $openStateId, $context); + + } + + /** + * @param \Shopware\Core\Framework\Context $context + * + * @return \Shopware\Core\System\StateMachine\StateMachineEntity + */ + protected function getStateMachineEntity(Context $context): string + { + $stateMachineCriteria = (new Criteria()) + ->addFilter(new EqualsFilter('technicalName', OrderDeliveryStates::STATE_MACHINE)); + return $this->stateMachineRepository->search($stateMachineCriteria, $context)->first()->getId(); + } + + /** + * @param string $stateMachineId + * @param \Shopware\Core\Framework\Context $context + * + * @return string + */ + protected function getHoldStateId(string $stateMachineId, Context $context): string + { + $holdStateMachineStateCriteria = (new Criteria()) + ->addFilter( + new EqualsFilter('technicalName', OrderDeliveryStateHandler::STATE_HOLD), + new EqualsFilter('stateMachineId', $stateMachineId) + ); + + $holdStateMachineStateEntity = $this->stateMachineStateRepository->search($holdStateMachineStateCriteria, $context)->first(); + + $holdStateId = is_null($holdStateMachineStateEntity) ? Uuid::randomHex() : $holdStateMachineStateEntity->getId(); + + if (is_null($holdStateMachineStateEntity)) { + $translations = $this->localeCodeProvider->getAvailableTranslations('vrpayment.deliveryState.hold', 'Hold', $context); + $data = [ + 'id' => $holdStateId, + 'technicalName' => OrderDeliveryStateHandler::STATE_HOLD, + 'stateMachineId' => $stateMachineId, + 'translations' => $translations, + ]; + $this->stateMachineStateRepository->upsert([$data], $context); + } + + return $holdStateId; + } + + /** + * @param string $stateMachineId + * @param \Shopware\Core\Framework\Context $context + * + * @return string + */ + protected function getOpenStateId(string $stateMachineId, Context $context): string + { + $stateMachineStateCriteria = (new Criteria()) + ->addFilter( + new EqualsFilter('technicalName', OrderDeliveryStates::STATE_OPEN), + new EqualsFilter('stateMachineId', $stateMachineId) + ); + + return $this->stateMachineStateRepository->search($stateMachineStateCriteria, $context)->first()->getId(); + } + + /** + * @param string $stateMachineId + * @param string $openStateId + * @param string $holdStateId + * @param \Shopware\Core\Framework\Context $context + */ + protected function upsertHoldTransition(string $stateMachineId, string $openStateId, string $holdStateId, Context $context): void + { + $translations = $this->localeCodeProvider->getAvailableTranslations('vrpayment.deliveryState.hold','Hold', $context); + + $this->upsertTransition(OrderDeliveryStateHandler::ACTION_HOLD, $stateMachineId, $openStateId, $holdStateId, $translations, $context); + } + + /** + * @param string $actionName + * @param string $stateMachineId + * @param string $fromStateId + * @param string $toStateId + * @param array $translations + * @param \Shopware\Core\Framework\Context $context + */ + protected function upsertTransition(string $actionName, string $stateMachineId, string $fromStateId, string $toStateId, array $translations, Context $context): void + { + $criteria = (new Criteria()) + ->addFilter( + new EqualsFilter('actionName', $actionName), + new EqualsFilter('stateMachineId', $stateMachineId), + new EqualsFilter('fromStateId', $fromStateId), + new EqualsFilter('toStateId', $toStateId) + ); + + $stateMachineTransitionEntity = $this->stateMachineTransitionRepository->search($criteria, $context)->first(); + $transitionId = is_null($stateMachineTransitionEntity) ? Uuid::randomHex() : $stateMachineTransitionEntity->getId(); + + if (is_null($stateMachineTransitionEntity)) { + $data = [ + 'id' => $transitionId, + 'actionName' => $actionName, + 'stateMachineId' => $stateMachineId, + 'fromStateId' => $fromStateId, + 'toStateId' => $toStateId, + 'translations' => $translations, + ]; + $this->stateMachineTransitionRepository->upsert([$data], $context); + } + + } + + /** + * @param string $stateMachineId + * @param string $openStateId + * @param string $holdStateId + * @param \Shopware\Core\Framework\Context $context + */ + protected function upsertUnholdTransition(string $stateMachineId, string $holdStateId, string $openStateId, Context $context): void + { + $translations = $this->localeCodeProvider->getAvailableTranslations('vrpayment.deliveryState.unhold','Unhold',$context); + + $this->upsertTransition(OrderDeliveryStateHandler::ACTION_UNHOLD, $stateMachineId, $holdStateId, $openStateId, $translations, $context); + } +} \ No newline at end of file diff --git a/src/Core/Api/PaymentMethodConfiguration/Command/PaymentMethodConfigurationCommand.php b/src/Core/Api/PaymentMethodConfiguration/Command/PaymentMethodConfigurationCommand.php new file mode 100644 index 0000000..89a1424 --- /dev/null +++ b/src/Core/Api/PaymentMethodConfiguration/Command/PaymentMethodConfigurationCommand.php @@ -0,0 +1,62 @@ +paymentMethodConfigurationService = $paymentMethodConfigurationService; + } + + /** + * @param \Symfony\Component\Console\Input\InputInterface $input + * @param \Symfony\Component\Console\Output\OutputInterface $output + * @return int + * @throws \VRPayment\Sdk\ApiException + * @throws \VRPayment\Sdk\Http\ConnectionException + * @throws \VRPayment\Sdk\VersioningException + */ + protected function execute(InputInterface $input, OutputInterface $output): int + { + $output->writeln('Fetch VRPaymentPayment space available payment methods...'); + $this->paymentMethodConfigurationService->synchronize(Context::createDefaultContext()); + return 0; + } + + /** + * Configures the current command. + */ + protected function configure() + { + $this->setDescription('Fetches VRPaymentPayment space available payment methods.') + ->setHelp('This command fetches VRPaymentPayment space available payment methods.'); + } + +} diff --git a/src/Core/Api/PaymentMethodConfiguration/Command/PaymentMethodDefaultCommand.php b/src/Core/Api/PaymentMethodConfiguration/Command/PaymentMethodDefaultCommand.php new file mode 100644 index 0000000..3e9e309 --- /dev/null +++ b/src/Core/Api/PaymentMethodConfiguration/Command/PaymentMethodDefaultCommand.php @@ -0,0 +1,61 @@ +paymentMethodUtil = $paymentMethodUtil; + } + + /** + * @param \Symfony\Component\Console\Input\InputInterface $input + * @param \Symfony\Component\Console\Output\OutputInterface $output + * @return int + */ + protected function execute(InputInterface $input, OutputInterface $output): int + { + $output->writeln('Set VRPaymentPayment as default payment method...'); + $context = Context::createDefaultContext(); + $this->paymentMethodUtil->setVRPaymentAsDefaultPaymentMethod($context); + $this->paymentMethodUtil->disableSystemPaymentMethods($context); + return 0; + } + + /** + * Configures the current command. + */ + protected function configure() + { + $this->setDescription('Sets VRPaymentPayment as default payment method.') + ->setHelp('This command updates VRPaymentPayment as default payment method for all SalesChannels.'); + } + +} diff --git a/src/Core/Api/PaymentMethodConfiguration/Entity/PaymentMethodConfigurationEntity.php b/src/Core/Api/PaymentMethodConfiguration/Entity/PaymentMethodConfigurationEntity.php new file mode 100644 index 0000000..8c9d6b3 --- /dev/null +++ b/src/Core/Api/PaymentMethodConfiguration/Entity/PaymentMethodConfigurationEntity.php @@ -0,0 +1,165 @@ +data; + } + + /** + * @param array $data + */ + public function setData(array $data): void + { + $this->data = $data; + } + + /** + * @return \Shopware\Core\Checkout\Payment\PaymentMethodEntity|null + */ + public function getPaymentMethod(): ?PaymentMethodEntity + { + return $this->paymentMethod; + } + + /** + * @param \Shopware\Core\Checkout\Payment\PaymentMethodEntity $paymentMethod + */ + public function setPaymentMethod(PaymentMethodEntity $paymentMethod): void + { + $this->paymentMethod = $paymentMethod; + } + + /** + * @return int + */ + public function getPaymentMethodConfigurationId(): int + { + return $this->paymentMethodConfigurationId; + } + + /** + * @param int $paymentMethodConfigurationId + */ + public function setPaymentMethodConfigurationId(int $paymentMethodConfigurationId): void + { + $this->paymentMethodConfigurationId = $paymentMethodConfigurationId; + } + + /** + * @return string + */ + public function getPaymentMethodId(): string + { + return $this->paymentMethodId; + } + + /** + * @param string $paymentMethodId + */ + public function setPaymentMethodId(string $paymentMethodId): void + { + $this->paymentMethodId = $paymentMethodId; + } + + /** + * @return string + */ + public function getSortOrder(): string + { + return $this->sortOrder; + } + + /** + * @param string $sortOrder + */ + public function setSortOrder(string $sortOrder): void + { + $this->sortOrder = $sortOrder; + } + + /** + * @return int + */ + public function getSpaceId(): int + { + return $this->spaceId; + } + + /** + * @param int $spaceId + */ + public function setSpaceId(int $spaceId): void + { + $this->spaceId = $spaceId; + } + + /** + * @return string + */ + public function getState(): string + { + return $this->state; + } + + /** + * @param string $state + */ + public function setState(string $state): void + { + $this->state = $state; + } +} \ No newline at end of file diff --git a/src/Core/Api/PaymentMethodConfiguration/Entity/PaymentMethodConfigurationEntityCollection.php b/src/Core/Api/PaymentMethodConfiguration/Entity/PaymentMethodConfigurationEntityCollection.php new file mode 100644 index 0000000..28df1ba --- /dev/null +++ b/src/Core/Api/PaymentMethodConfiguration/Entity/PaymentMethodConfigurationEntityCollection.php @@ -0,0 +1,29 @@ +addFlags(new PrimaryKey(), new Required()), + (new JsonField('data', 'data'))->addFlags(new Required()), + (new IntField('payment_method_configuration_id', 'paymentMethodConfigurationId'))->addFlags(new Required()), + (new FkField('payment_method_id', 'paymentMethodId', PaymentMethodDefinition::class))->addFlags(new Required()), + (new IntField('sort_order', 'sortOrder'))->addFlags(new Required()), + (new IntField('space_id', 'spaceId'))->addFlags(new Required()), + (new StringField('state', 'state'))->addFlags(new Required()), + new OneToOneAssociationField('paymentMethod', 'payment_method_id', 'id', PaymentMethodDefinition::class, true), + new CreatedAtField(), + new UpdatedAtField(), + ]); + } + + /** + * @return string + */ + public function getCollectionClass(): string + { + return PaymentMethodConfigurationEntityCollection::class; + } + + /** + * @return string + */ + public function getEntityClass(): string + { + return PaymentMethodConfigurationEntity::class; + } + +} \ No newline at end of file diff --git a/src/Core/Api/PaymentMethodConfiguration/Service/PaymentMethodConfigurationService.php b/src/Core/Api/PaymentMethodConfiguration/Service/PaymentMethodConfigurationService.php new file mode 100644 index 0000000..60ec19b --- /dev/null +++ b/src/Core/Api/PaymentMethodConfiguration/Service/PaymentMethodConfigurationService.php @@ -0,0 +1,678 @@ +settingsService = $settingsService; + $this->container = $container; + $this->mediaSerializer = $mediaSerializer; + $this->serializerRegistry = $serializerRegistry; + $this->salesChannelPaymentRepository = $salesChannelPaymentRepository; + $this->paymentMethodRepository = $paymentMethodRepository; + $this->mediaRepository = $mediaRepository; + $this->mediaFolderRepository = $mediaFolderRepository; + $this->mediaDefaultFolderRepository = $mediaDefaultFolderRepository; + $this->ruleRepository = $ruleRepository; + $this->vRPaymentPaymentMethodConfigurationRepository = $vRPaymentPaymentMethodConfigurationRepository; + $this->localeCodeProvider = $this->container->get(LocaleCodeProvider::class); + } + + /** + * @param \Psr\Log\LoggerInterface $logger + * + * @internal + * @required + * + */ + public function setLogger(LoggerInterface $logger): void + { + $this->logger = $logger; + } + + /** + * @return \VRPayment\Sdk\ApiClient + */ + public function getApiClient(): ApiClient + { + return $this->apiClient; + } + + /** + * @param \VRPayment\Sdk\ApiClient $apiClient + * + * @return \VRPaymentPayment\Core\Api\PaymentMethodConfiguration\Service\PaymentMethodConfigurationService + */ + public function setApiClient(ApiClient $apiClient): PaymentMethodConfigurationService + { + $this->apiClient = $apiClient; + return $this; + } + + /** + * @param \Shopware\Core\Framework\Context $context + * + * @return array + * @throws \VRPayment\Sdk\ApiException + * @throws \VRPayment\Sdk\Http\ConnectionException + * @throws \VRPayment\Sdk\VersioningException + */ + public function synchronize(Context $context): array + { + // Configuration + $settings = $this->settingsService->getSettings($this->getSalesChannelId()); + $this->setSpaceId($settings->getSpaceId())->setApiClient($settings->getApiClient()); + + $this->disablePaymentMethodConfigurations($context); + $this->enablePaymentMethodConfigurations($context); + $this->disableOrphanedPaymentMethods(); + return []; + } + + /** + * Get sales channel id + * + * @return string|null + */ + public function getSalesChannelId(): ?string + { + return $this->salesChannelId; + } + + /** + * Set sales channel id + * + * @param string|null $salesChannelId + * + * @return \VRPaymentPayment\Core\Api\PaymentMethodConfiguration\Service\PaymentMethodConfigurationService + */ + public function setSalesChannelId(?string $salesChannelId = null): PaymentMethodConfigurationService + { + $this->salesChannelId = $salesChannelId; + return $this; + } + + /** + * @param \Shopware\Core\Framework\Context $context + */ + private function disablePaymentMethodConfigurations(Context $context): void + { + $data = []; + $paymentMethodData = []; + $salesChannelPaymentMethodData = []; + + $criteria = (new Criteria())->addFilter(new EqualsFilter('state', 'ACTIVE')); + + /** + * @var $vRPaymentPMConfigurationRepository + */ + $vRPaymentPMConfigurationRepository = $this->container->get(PaymentMethodConfigurationEntityDefinition::ENTITY_NAME . '.repository'); + + $paymentMethodConfigurationEntities = $vRPaymentPMConfigurationRepository + ->search($criteria, $context) + ->getEntities(); + + if (!empty($paymentMethodConfigurationEntities)) { + + /** + * @var $paymentMethodConfigurationEntity \VRPaymentPayment\Core\Api\PaymentMethodConfiguration\Entity\PaymentMethodConfigurationEntity + */ + foreach ($paymentMethodConfigurationEntities as $paymentMethodConfigurationEntity) { + $data[] = [ + 'id' => $paymentMethodConfigurationEntity->getId(), + 'state' => CreationEntityState::INACTIVE, + ]; + + $paymentMethodData[] = [ + 'id' => $paymentMethodConfigurationEntity->getId(), + 'active' => false, + ]; + } + + try { + $this->vRPaymentPaymentMethodConfigurationRepository->update($data, $context); + $this->paymentMethodRepository->update($paymentMethodData, $context); + } catch (\Exception $exception) { + $this->logger->critical($exception->getMessage()); + } + + } + + } + + /** + * Full proof method to disable any orphaned payment methods + * + */ + protected function disableOrphanedPaymentMethods(): void + { + try { + $query = "UPDATE payment_method + SET active=0 + WHERE handler_identifier=:handler_identifier AND id NOT IN ( + SELECT payment_method_id FROM vrpayment_payment_method_configuration + )"; + + $params = [ + 'handler_identifier' => VRPaymentPaymentHandler::class, + ]; + + $connection = $this->container->get(Connection::class); + $connection->executeQuery($query, $params); + } catch (\Exception $exception) { + $this->logger->critical($exception->getMessage()); + } + } + + /** + * @param string $paymentMethodId + * @param bool $active + * @param \Shopware\Core\Framework\Context $context + */ + protected function setPaymentMethodIsActive(string $paymentMethodId, bool $active, Context $context): void + { + $paymentMethod = [ + 'id' => $paymentMethodId, + 'active' => $active, + ]; + $this->paymentMethodRepository->update([$paymentMethod], $context); + } + + /** + * Enable payment methods from VRPayment API + * + * @param \Shopware\Core\Framework\Context $context + * + * @throws \VRPayment\Sdk\ApiException + * @throws \VRPayment\Sdk\Http\ConnectionException + * @throws \VRPayment\Sdk\VersioningException + */ + private function enablePaymentMethodConfigurations(Context $context): void + { + $paymentMethodConfigurations = $this->getPaymentMethodConfigurations(); + $this->logger->debug('Updating payment methods', $paymentMethodConfigurations); + + /** + * @var $paymentMethodConfiguration \VRPayment\Sdk\Model\PaymentMethodConfiguration + */ + foreach ($paymentMethodConfigurations as $paymentMethodConfiguration) { + + $paymentMethodConfigurationEntity = $this->getPaymentMethodConfigurationEntity( + $paymentMethodConfiguration->getSpaceId(), + $paymentMethodConfiguration->getId(), + $context + ); + + $id = is_null($paymentMethodConfigurationEntity) ? Uuid::randomHex() : $paymentMethodConfigurationEntity->getId(); + + $data = [ + 'id' => $id, + 'paymentMethodConfigurationId' => $paymentMethodConfiguration->getId(), + 'paymentMethodId' => $id, + 'data' => json_decode(strval($paymentMethodConfiguration), true), + 'sortOrder' => $paymentMethodConfiguration->getSortOrder(), + 'spaceId' => $paymentMethodConfiguration->getSpaceId(), + 'state' => CreationEntityState::ACTIVE, + ]; + + $this->upsertPaymentMethod($id, $paymentMethodConfiguration, $context); + + try { + $this->container->get(PaymentMethodConfigurationEntityDefinition::ENTITY_NAME . '.repository')->upsert([$data], $context); + } catch (\Exception $e) { + $this->logger->error($e->getMessage(), [$e->getTraceAsString()]); + } + + } + } + + /** + * Fetch active merchant payment methods from VRPayment API + * + * @return \VRPayment\Sdk\Model\PaymentMethodConfiguration[] + * @throws \VRPayment\Sdk\ApiException + * @throws \VRPayment\Sdk\Http\ConnectionException + * @throws \VRPayment\Sdk\VersioningException + */ + private function getPaymentMethodConfigurations(): array + { + $entityQueryFilter = (new EntityQueryFilter()) + ->setOperator(CriteriaOperator::EQUALS) + ->setFieldName('state') + ->setType(EntityQueryFilterType::LEAF) + ->setValue(CreationEntityState::ACTIVE); + + $entityQuery = (new EntityQuery())->setFilter($entityQueryFilter); + + $paymentMethodConfigurations = $this->apiClient->getPaymentMethodConfigurationService()->search( + $this->getSpaceId(), + $entityQuery + ); + + usort($paymentMethodConfigurations, function (PaymentMethodConfiguration $item1, PaymentMethodConfiguration $item2) { + return $item1->getSortOrder() <=> $item2->getSortOrder(); + }); + + return $paymentMethodConfigurations; + } + + /** + * @return int + */ + public function getSpaceId(): int + { + return $this->spaceId; + } + + /** + * @param int $spaceId + * + * @return \VRPaymentPayment\Core\Api\PaymentMethodConfiguration\Service\PaymentMethodConfigurationService + */ + public function setSpaceId(int $spaceId): PaymentMethodConfigurationService + { + $this->spaceId = $spaceId; + return $this; + } + + /** + * @param int $spaceId + * @param int $paymentMethodConfigurationId + * @param \Shopware\Core\Framework\Context $context + * + * @return \VRPaymentPayment\Core\Api\PaymentMethodConfiguration\Entity\PaymentMethodConfigurationEntity|null + */ + protected function getPaymentMethodConfigurationEntity( + int $spaceId, + int $paymentMethodConfigurationId, + Context $context + ): ?PaymentMethodConfigurationEntity + { + $criteria = (new Criteria())->addFilter( + new EqualsFilter('spaceId', $spaceId), + new EqualsFilter('paymentMethodConfigurationId', $paymentMethodConfigurationId) + ); + + return $this->vRPaymentPaymentMethodConfigurationRepository + ->search($criteria, $context) + ->getEntities() + ->first(); + } + + /** + * @param int $spaceId + * @param Context $context + * @return array + */ + public function getAllPaymentMethodConfigurations(int $spaceId, Context $context): array + { + $criteria = (new Criteria())->addFilter(new EqualsFilter('spaceId', $spaceId)); + + $configurations = $this->vRPaymentPaymentMethodConfigurationRepository + ->search($criteria, $context) + ->getEntities(); + + return $configurations->getElements(); + } + + /** + * Update or insert Payment Method + * + * @param string $id + * @param \VRPayment\Sdk\Model\PaymentMethodConfiguration $paymentMethodConfiguration + * @param \Shopware\Core\Framework\Context $context + * + * @throws \VRPayment\Sdk\ApiException + * @throws \VRPayment\Sdk\Http\ConnectionException + * @throws \VRPayment\Sdk\VersioningException + */ + protected function upsertPaymentMethod( + string $id, + PaymentMethodConfiguration $paymentMethodConfiguration, + Context $context + ): void + { + /** @var PluginIdProvider $pluginIdProvider */ + $pluginIdProvider = $this->container->get(PluginIdProvider::class); + $pluginId = $pluginIdProvider->getPluginIdByBaseClass( + VRPaymentPayment::class, + $context + ); + + $data = [ + 'id' => $id, + 'handlerIdentifier' => VRPaymentPaymentHandler::class, + 'pluginId' => $pluginId, + 'position' => $paymentMethodConfiguration->getSortOrder() - 100, + 'afterOrderEnabled' => true, + 'active' => true, + 'translations' => $this->getPaymentMethodConfigurationTranslation($paymentMethodConfiguration, $context), + 'technicalName' => $paymentMethodConfiguration->getName(), + ]; + + $data['mediaId'] = $this->upsertMedia($id, $paymentMethodConfiguration, $context); + + $data = array_filter($data); + + try { + $this->paymentMethodRepository->upsert([$data], $context); + } catch (\Exception $e) { + $this->logger->error($e->getMessage(), [$e->getTraceAsString()]); + } + + } + + /** + * @param \VRPayment\Sdk\Model\PaymentMethodConfiguration $paymentMethodConfiguration + * @param \Shopware\Core\Framework\Context $context + * + * @return array + * @throws \VRPayment\Sdk\ApiException + * @throws \VRPayment\Sdk\Http\ConnectionException + * @throws \VRPayment\Sdk\VersioningException + */ + protected function getPaymentMethodConfigurationTranslation(PaymentMethodConfiguration $paymentMethodConfiguration, Context $context): array + { + $translations = []; + $locales = $this->localeCodeProvider->getAvailableLocales($context); + foreach ($locales as $locale) { + $translations[$locale] = [ + 'name' => $this->translate($paymentMethodConfiguration->getResolvedTitle(), $locale) ?? $paymentMethodConfiguration->getName(), + 'description' => $this->translate($paymentMethodConfiguration->getResolvedDescription(), $locale) ?? '', + ]; + } + return $translations; + } + + /** + * @param array $translatedString + * @param string $locale + * + * @return string|null + * @throws \VRPayment\Sdk\ApiException + * @throws \VRPayment\Sdk\Http\ConnectionException + * @throws \VRPayment\Sdk\VersioningException + */ + protected function translate(array $translatedString, string $locale): ?string + { + $translation = null; + + if (isset($translatedString[$locale])) { + $translation = $translatedString[$locale]; + } + + if (is_null($translation)) { + + $primaryLanguage = $this->findPrimaryLanguage($locale); + if (!is_null($primaryLanguage) && isset($translatedString[$primaryLanguage->getIetfCode()])) { + $translation = $translatedString[$primaryLanguage->getIetfCode()]; + } + + if (is_null($translation) && isset($translatedString['en-US'])) { + $translation = $translatedString['en-US']; + } + } + + return $translation; + } + + /** + * Returns the primary language in the given group. + * + * @param $code + * + * @return \VRPayment\Sdk\Model\RestLanguage|null + * @throws \VRPayment\Sdk\ApiException + * @throws \VRPayment\Sdk\Http\ConnectionException + * @throws \VRPayment\Sdk\VersioningException + */ + protected function findPrimaryLanguage(string $code): ?RestLanguage + { + $code = substr($code, 0, 2); + foreach ($this->getLanguages() as $language) { + if (($language->getIso2Code() == $code) && $language->getPrimaryOfGroup()) { + return $language; + } + } + return null; + } + + /** + * + * @return array + * @throws \VRPayment\Sdk\ApiException + * @throws \VRPayment\Sdk\Http\ConnectionException + * @throws \VRPayment\Sdk\VersioningException + */ + protected function getLanguages(): array + { + if (is_null($this->languages)) { + $this->languages = $this->apiClient->getLanguageService()->all(); + } + return $this->languages; + } + + /** + * Upload Payment Method icons + * + * @param string $id + * @param \VRPayment\Sdk\Model\PaymentMethodConfiguration $paymentMethodConfiguration + * @param \Shopware\Core\Framework\Context $context + * + * @return string|null + */ + protected function upsertMedia(string $id, PaymentMethodConfiguration $paymentMethodConfiguration, Context $context): ?string + { + try { + $existingRecord = $this->getMediaDefaultFolderForPaymentMethod($paymentMethodConfiguration, $context); + + if ($existingRecord->count() > 0) { + $id = $existingRecord->first()->getId(); + } + + $this->mediaDefaultFolderRepository->upsert([ + [ + 'id' => $id, + 'associationFields' => [], + 'entity' => 'payment_method_' . $paymentMethodConfiguration->getId(), + ], + ], $context); + + $this->mediaFolderRepository->upsert([ + [ + 'id' => $id, + 'defaultFolderId' => $id, + 'name' => $paymentMethodConfiguration->getName(), + 'useParentConfiguration' => false, + 'configuration' => [], + ], + ], $context); + + /** + * @var \Shopware\Core\Content\Media\MediaDefinition + */ + $mediaDefinition = $this->container->get(MediaDefinition::class); + $this->mediaSerializer->setRegistry($this->serializerRegistry); + $data = [ + 'id' => $id, + 'title' => $paymentMethodConfiguration->getName(), + 'url' => $paymentMethodConfiguration->getResolvedImageUrl(), + 'mediaFolderId' => $id, + ]; + $data = $this->mediaSerializer->deserialize(new Config([], [], []), $mediaDefinition, $data); + $this->mediaRepository->upsert([$data], $context); + return $id; + } catch (\Exception $e) { + $this->logger->critical($e->getMessage(), [$e->getTraceAsString()]); + return null; + } + } + + /** + * Retrieves media default folder for a given payment method configuration. + * + * @param PaymentMethodConfiguration $paymentMethodConfiguration The payment method configuration to check. + * @param Context $context The current context. + * + * @return EntitySearchResult The search result for the media default folder. + */ + private function getMediaDefaultFolderForPaymentMethod(PaymentMethodConfiguration $paymentMethodConfiguration, Context $context): ?EntitySearchResult + { + $criteria = new Criteria(); + $criteria->addFilter(new EqualsFilter('entity', 'payment_method_' . $paymentMethodConfiguration->getId())); + return $this->mediaDefaultFolderRepository->search($criteria, $context); + } + +} diff --git a/src/Core/Api/Refund/Controller/RefundController.php b/src/Core/Api/Refund/Controller/RefundController.php new file mode 100644 index 0000000..b6b8661 --- /dev/null +++ b/src/Core/Api/Refund/Controller/RefundController.php @@ -0,0 +1,153 @@ + ['api']])] +class RefundController extends AbstractController +{ + /** + * @var \VRPaymentPayment\Core\Api\Refund\Service\RefundService + */ + protected $refundService; + + /** + * @var \VRPaymentPayment\Core\Settings\Service\SettingsService + */ + protected $settingsService; + + /** + * @var \Psr\Log\LoggerInterface + */ + protected $logger; + + /** + * RefundController constructor. + * + * @param \VRPaymentPayment\Core\Api\Refund\Service\RefundService $refundService + * @param \VRPaymentPayment\Core\Settings\Service\SettingsService $settingsService + */ + public function __construct(RefundService $refundService, SettingsService $settingsService) + { + $this->settingsService = $settingsService; + $this->refundService = $refundService; + } + + /** + * @param \Psr\Log\LoggerInterface $logger + * @internal + * @required + * + */ + public function setLogger(LoggerInterface $logger): void + { + $this->logger = $logger; + } + + /** + * @param \Symfony\Component\HttpFoundation\Request $request + * @param \Shopware\Core\Framework\Context $context + * @return \Symfony\Component\HttpFoundation\Response + * @throws \VRPayment\Sdk\ApiException + * @throws \VRPayment\Sdk\Http\ConnectionException + * @throws \VRPayment\Sdk\VersioningException + */ + #[Route("/api/_action/vrpayment/refund/create-refund/", + name: "api.action.vrpayment.refund.create-refund", + methods: ['POST'])] + public function createRefund(Request $request, Context $context): Response + { + $salesChannelId = $request->request->get('salesChannelId'); + $transactionId = $request->request->get('transactionId'); + $quantity = (int)$request->request->get('quantity'); + $lineItemId = $request->request->get('lineItemId'); + + $settings = $this->settingsService->getSettings($salesChannelId); + $apiClient = $settings->getApiClient(); + + $transaction = $apiClient->getTransactionService()->read($settings->getSpaceId(), $transactionId); + $refund = $this->refundService->create($transaction, $context, $lineItemId, $quantity); + if ($refund === null) { + return new Response('Refund was not created. Please check the refund amound or if the item was not refunded before', Response::HTTP_BAD_REQUEST); + } + + return new Response(null, Response::HTTP_NO_CONTENT); + } + + /** + * @param \Symfony\Component\HttpFoundation\Request $request + * @param \Shopware\Core\Framework\Context $context + * @return \Symfony\Component\HttpFoundation\Response + * @throws \VRPayment\Sdk\ApiException + * @throws \VRPayment\Sdk\Http\ConnectionException + * @throws \VRPayment\Sdk\VersioningException + */ + #[Route("/api/_action/vrpayment/refund/create-refund-by-amount/", + name: "api.action.vrpayment.refund.create.refund.by.amount", + methods: ['POST'])] + public function createRefundByAmount(Request $request, Context $context): Response + { + $salesChannelId = $request->request->get('salesChannelId'); + $transactionId = $request->request->get('transactionId'); + $refundableAmount = $request->request->get('refundableAmount'); + + $settings = $this->settingsService->getSettings($salesChannelId); + $apiClient = $settings->getApiClient(); + + $transaction = $apiClient->getTransactionService()->read($settings->getSpaceId(), $transactionId); + $refund = $this->refundService->createRefundByAmount($transaction, $refundableAmount, $context); + + if ($refund === null) { + return new Response('refundExceedsAmount', Response::HTTP_BAD_REQUEST); + } + + return new Response(null, Response::HTTP_NO_CONTENT); + } + + /** + * @param \Symfony\Component\HttpFoundation\Request $request + * @param \Shopware\Core\Framework\Context $context + * @return \Symfony\Component\HttpFoundation\Response + * @throws \VRPayment\Sdk\ApiException + * @throws \VRPayment\Sdk\Http\ConnectionException + * @throws \VRPayment\Sdk\VersioningException + */ + #[Route("/api/_action/vrpayment/refund/create-partial-refund/", + name: "api.action.vrpayment.refund.create.partial.refund", + methods: ['POST'])] + public function createPartialRefund(Request $request, Context $context): Response + { + $salesChannelId = $request->request->get('salesChannelId'); + $transactionId = $request->request->get('transactionId'); + $refundableAmount = $request->request->get('refundableAmount'); + $lineItemId = $request->request->get('lineItemId'); + + $settings = $this->settingsService->getSettings($salesChannelId); + $apiClient = $settings->getApiClient(); + + $transaction = $apiClient->getTransactionService()->read($settings->getSpaceId(), $transactionId); + $this->refundService->createPartialRefund($transaction, $context, $lineItemId, $refundableAmount); + + return new Response(null, Response::HTTP_NO_CONTENT); + } +} diff --git a/src/Core/Api/Refund/Entity/RefundEntity.php b/src/Core/Api/Refund/Entity/RefundEntity.php new file mode 100644 index 0000000..e8fe94a --- /dev/null +++ b/src/Core/Api/Refund/Entity/RefundEntity.php @@ -0,0 +1,145 @@ +data; + } + + /** + * @param array $data + */ + public function setData(array $data): void + { + $this->data = $data; + } + + /** + * @return int + */ + public function getRefundId(): int + { + return $this->refundId; + } + + /** + * @param int $refundId + */ + public function setRefundId(int $refundId): void + { + $this->refundId = $refundId; + } + + /** + * @return int + */ + public function getSpaceId(): int + { + return $this->spaceId; + } + + /** + * @param int $spaceId + */ + public function setSpaceId(int $spaceId): void + { + $this->spaceId = $spaceId; + } + + /** + * @return string + */ + public function getState(): string + { + return $this->state; + } + + /** + * @param string $state + */ + public function setState(string $state): void + { + $this->state = $state; + } + + /** + * + * @return \VRPaymentPayment\Core\Api\Transaction\Entity\TransactionEntityDefinition|null + */ + public function getTransaction(): ?TransactionEntityDefinition + { + return $this->transaction; + } + + /** + * @param \VRPaymentPayment\Core\Api\Transaction\Entity\TransactionEntityDefinition $transaction + */ + public function setTransaction(TransactionEntityDefinition $transaction): void + { + $this->transaction = $transaction; + } + + /** + * @return int + */ + public function getTransactionId(): int + { + return $this->transactionId; + } + + /** + * @param int $transactionId + */ + public function setTransactionId(int $transactionId): void + { + $this->transactionId = $transactionId; + } +} \ No newline at end of file diff --git a/src/Core/Api/Refund/Entity/RefundEntityCollection.php b/src/Core/Api/Refund/Entity/RefundEntityCollection.php new file mode 100644 index 0000000..c3d8520 --- /dev/null +++ b/src/Core/Api/Refund/Entity/RefundEntityCollection.php @@ -0,0 +1,57 @@ +filter(function (RefundEntity $refund) use ($transactionId) { + return $refund->getTransactionId() === $transactionId; + }); + } + + /** + * Get by refund id + * + * @param int $refundId + * @return \VRPaymentPayment\Core\Api\Refund\Entity\RefundEntity|null + */ + public function getByRefundId(int $refundId): ?RefundEntity + { + foreach ($this->getIterator() as $element) { + if ($element->getRefundId() === $refundId) { + return $element; + } + } + + return null; + } + + /** + * @return string + */ + protected function getExpectedClass(): string + { + return RefundEntity::class; + } +} \ No newline at end of file diff --git a/src/Core/Api/Refund/Entity/RefundEntityDefinition.php b/src/Core/Api/Refund/Entity/RefundEntityDefinition.php new file mode 100644 index 0000000..9c80bee --- /dev/null +++ b/src/Core/Api/Refund/Entity/RefundEntityDefinition.php @@ -0,0 +1,70 @@ +addFlags(new PrimaryKey(), new Required()), + (new JsonField('data', 'data'))->addFlags(new Required()), + (new IntField('refund_id', 'refundId'))->addFlags(new Required()), + (new IntField('space_id', 'spaceId'))->addFlags(new Required()), + (new StringField('state', 'state'))->addFlags(new Required()), + (new IntField('transaction_id', 'transactionId'))->addFlags(new Required()), + new ManyToOneAssociationField('transaction', 'transaction_id', TransactionEntityDefinition::class, 'transaction_id'), + new CreatedAtField(), + new UpdatedAtField(), + ]); + } + + /** + * @return string + */ + public function getCollectionClass(): string + { + return RefundEntityCollection::class; + } + + /** + * @return string + */ + public function getEntityClass(): string + { + return RefundEntity::class; + } + +} \ No newline at end of file diff --git a/src/Core/Api/Refund/Service/RefundService.php b/src/Core/Api/Refund/Service/RefundService.php new file mode 100644 index 0000000..defb2f8 --- /dev/null +++ b/src/Core/Api/Refund/Service/RefundService.php @@ -0,0 +1,244 @@ +container = $container; + $this->settingsService = $settingsService; + } + + /** + * @param \Psr\Log\LoggerInterface $logger + * + * @internal + * @required + * + */ + public function setLogger(LoggerInterface $logger): void + { + $this->logger = $logger; + } + + /** + * The pay function will be called after the customer completed the order. + * Allows to process the order and store additional information. + * + * A redirect to the url will be performed + * + * @param \VRPayment\Sdk\Model\Transaction $transaction + * @param string|null $lineItemId + * @param int $quantity + * @param \Shopware\Core\Framework\Context $context + * + * @return \VRPayment\Sdk\Model\Refund|null + * @throws \Exception + */ + public function create(Transaction $transaction, Context $context, ?string $lineItemId, int $quantity): ?Refund + { + try { + $transactionEntity = $this->getTransactionEntityByTransactionId($transaction->getId(), $context); + $settings = $this->settingsService->getSettings($transactionEntity->getSalesChannel()->getId()); + $apiClient = $settings->getApiClient(); + $refundPayloadClass = new RefundPayload(); + $refundPayloadClass->setLogger($this->logger); + + $refundPayload = $refundPayloadClass->get($transaction, $lineItemId, $quantity); + + if (!is_null($refundPayload)) { + $refund = $apiClient->getRefundService()->refund($settings->getSpaceId(), $refundPayload); + $this->upsert($refund, $context); + return $refund; + } + } catch (\Exception $exception) { + $this->logger->critical($exception->getMessage()); + } + return null; + } + + /** + * The pay function will be called after the customer completed the order. + * Allows to process the order and store additional information. + * + * A redirect to the url will be performed + * + * @param \VRPayment\Sdk\Model\Transaction $transaction + * @param float $refundableAmount + * @param \Shopware\Core\Framework\Context $context + * + * @return \VRPayment\Sdk\Model\Refund|null + * @throws \Exception + */ + public function createRefundByAmount(Transaction $transaction, float $refundableAmount, Context $context): ?Refund + { + try { + $transactionEntity = $this->getTransactionEntityByTransactionId($transaction->getId(), $context); + $settings = $this->settingsService->getSettings($transactionEntity->getSalesChannel()->getId()); + $apiClient = $settings->getApiClient(); + $refundPayloadClass = new RefundPayload(); + $refundPayloadClass->setLogger($this->logger); + + $refundPayload = $refundPayloadClass->getByAmount($transaction, $refundableAmount); + + if (!is_null($refundPayload)) { + $refund = $apiClient->getRefundService()->refund($settings->getSpaceId(), $refundPayload); + $this->upsert($refund, $context); + return $refund; + } + } catch (\Exception $exception) { + $this->logger->critical($exception->getMessage()); + } + return null; + } + + /** + * The pay function will be called after the customer completed the order. + * Allows to process the order and store additional information. + * + * A redirect to the url will be performed + * + * @param \VRPayment\Sdk\Model\Transaction $transaction + * @param \Shopware\Core\Framework\Context $context + * @param string $lineItemId + * @param float $amount + * + * @return \VRPayment\Sdk\Model\Refund|null + * @throws \Exception + */ + public function createPartialRefund(Transaction $transaction, Context $context, string $lineItemId, float $amount): ?Refund + { + try { + $transactionEntity = $this->getTransactionEntityByTransactionId($transaction->getId(), $context); + $settings = $this->settingsService->getSettings($transactionEntity->getSalesChannel()->getId()); + $apiClient = $settings->getApiClient(); + $refundPayloadClass = new RefundPayload(); + $refundPayloadClass->setLogger($this->logger); + + $refundPayload = $refundPayloadClass->getForPartial($transaction, $lineItemId, $amount); + + if (!is_null($refundPayload)) { + $refund = $apiClient->getRefundService()->refund($settings->getSpaceId(), $refundPayload); + $this->upsert($refund, $context); + return $refund; + } + } catch (\Exception $exception) { + $this->logger->critical($exception->getMessage()); + } + return null; + } + + /** + * Get transaction entity by VRPayment transaction id + * + * @param int $transactionId + * @param \Shopware\Core\Framework\Context $context + * + * @return \VRPaymentPayment\Core\Api\Transaction\Entity\TransactionEntity + */ + public function getTransactionEntityByTransactionId(int $transactionId, Context $context): TransactionEntity + { + return $this->container->get(TransactionEntityDefinition::ENTITY_NAME . '.repository') + ->search( + (new Criteria())->addFilter(new EqualsFilter('transactionId', $transactionId)), + $context + ) + ->first(); + } + + /** + * Persist VRPayment transaction + * + * @param \Shopware\Core\Framework\Context $context + * @param \VRPayment\Sdk\Model\Refund $refund + */ + public function upsert(Refund $refund, Context $context): void + { + $refundEntity = $this->getByRefundId($refund->getId(), $context); + $id = is_null($refundEntity) ? Uuid::randomHex() : $refundEntity->getId(); + try { + + $data = [ + 'id' => $id, + 'data' => json_decode(strval($refund), true), + 'refundId' => $refund->getId(), + 'spaceId' => $refund->getLinkedSpaceId(), + 'state' => $refund->getState(), + 'transactionId' => $refund->getTransaction()->getId(), + ]; + + $data = array_filter($data); + $this->container->get('vrpayment_refund.repository')->upsert([$data], $context); + + } catch (\Exception $exception) { + $this->logger->critical(__CLASS__ . ' : ' . __FUNCTION__ . ' : ' . $exception->getMessage()); + } + } + + /** + * Get refund entity by VRPayment refund id + * + * @param int $refundId + * @param \Shopware\Core\Framework\Context $context + * + * @return \VRPaymentPayment\Core\Api\Refund\Entity\RefundEntity|null + */ + public function getByRefundId(int $refundId, Context $context): ?RefundEntity + { + return $this->container->get('vrpayment_refund.repository') + ->search( + (new Criteria())->addFilter(new EqualsFilter('refundId', $refundId)), + $context + ) + ->first(); + } + +} diff --git a/src/Core/Api/Space/Service/SpaceService.php b/src/Core/Api/Space/Service/SpaceService.php new file mode 100644 index 0000000..6728ef4 --- /dev/null +++ b/src/Core/Api/Space/Service/SpaceService.php @@ -0,0 +1,179 @@ +logger = $logger; + } + + /** + * @return \VRPayment\Sdk\ApiClient + */ + public function getApiClient(): ApiClient + { + return $this->apiClient; + } + + /** + * @param \VRPayment\Sdk\ApiClient $apiClient + * + * @return \VRPaymentPayment\Core\Api\Space\Service\SpaceService + */ + public function setApiClient(ApiClient $apiClient): SpaceService + { + $this->apiClient = $apiClient; + return $this; + } + + /** + * @return int + */ + public function getSpaceId(): int + { + return $this->spaceId; + } + + /** + * @param int $spaceId + * + * @return \VRPaymentPayment\Core\Api\Space\Service\SpaceService + */ + public function setSpaceId(int $spaceId): SpaceService + { + $this->spaceId = $spaceId; + return $this; + } + + /** + * Get user id + * @return int + */ + public function getUserId(): int + { + return $this->userId; + } + + /** + * @param int $userId + * + * @return \VRPaymentPayment\Core\Api\Space\Service\SpaceService + */ + public function setUserId(int $userId): SpaceService + { + $this->userId = $userId; + return $this; + } + + /** + * Get user key credential + * @return string + */ + public function getApplicationId(): string + { + return $this->applicationId; + } + + /** + * @param string $applicationId + * + * @return \VRPaymentPayment\Core\Api\Space\Service\SpaceService + */ + public function setApplicationId(string $applicationId): SpaceService + { + $this->applicationId = $applicationId; + return $this; + } + + /** + * Check Space + * Reads the entity with the given space id and user credentials and returns it. + * If the user credentials are not valid, an exception is thrown. + * The purpose of this method is simply to validate that a user has access + * with their credentials to a space on the portal. + * @see On the portal /doc/api/web-service#space-service--read + * + * @return Space|null + * @throws \VRPayment\Sdk\ApiException + * @throws \VRPayment\Sdk\Http\ConnectionException + * @throws \VRPayment\Sdk\VersioningException + */ + public function checkSpace(): ?Space + { + // Configuration + $this->setApiClient(new ApiClient($this->getUserId(), $this->getApplicationId())); + + return $this->read(); + } + + /** + * Read Space + * + * @return Space|null + */ + protected function read(): ?Space + { + $returnValue = null; + try { + $returnValue = $this->apiClient->getSpaceService()->read($this->getSpaceId()); + } catch (\Exception $exception) { + $this->logger->critical($exception->getTraceAsString()); + } + + return $returnValue; + } + +} \ No newline at end of file diff --git a/src/Core/Api/Transaction/Controller/TransactionCompletionController.php b/src/Core/Api/Transaction/Controller/TransactionCompletionController.php new file mode 100644 index 0000000..c3bccc8 --- /dev/null +++ b/src/Core/Api/Transaction/Controller/TransactionCompletionController.php @@ -0,0 +1,92 @@ + ['api']])] +class TransactionCompletionController extends AbstractController { + + /** + * @var \VRPaymentPayment\Core\Settings\Service\SettingsService + */ + protected $settingsService; + + /** + * @var \Psr\Log\LoggerInterface + */ + protected $logger; + + /** + * TransactionCompletionController constructor. + * + * @param \VRPaymentPayment\Core\Settings\Service\SettingsService $settingsService + */ + public function __construct(SettingsService $settingsService) + { + $this->settingsService = $settingsService; + } + + /** + * @param \Psr\Log\LoggerInterface $logger + * @internal + * @required + * + */ + public function setLogger(LoggerInterface $logger): void + { + $this->logger = $logger; + } + + /** + * @param \Symfony\Component\HttpFoundation\Request $request + * @return \Symfony\Component\HttpFoundation\JsonResponse + * @throws \VRPayment\Sdk\ApiException + * @throws \VRPayment\Sdk\Http\ConnectionException + * @throws \VRPayment\Sdk\VersioningException + * + */ + #[Route("/api/_action/vrpayment/transaction-completion/create-transaction-completion/", + name: "api.action.vrpayment.transaction-completion.create-transaction-completion", + methods: ['POST'])] + public function createTransactionCompletion(Request $request): JsonResponse + { + $salesChannelId = $request->request->get('salesChannelId'); + $transactionId = $request->request->get('transactionId'); + + $settings = $this->settingsService->getSettings($salesChannelId); + $apiClient = $settings->getApiClient(); + + + $transaction = $apiClient->getTransactionService()->read($settings->getSpaceId(), $transactionId); + if ($transaction->getState() == TransactionState::AUTHORIZED) { + $transactionCompletion = $apiClient->getTransactionCompletionService()->completeOnline($settings->getSpaceId(), $transaction->getId()); + return new JsonResponse(strval($transactionCompletion), Response::HTTP_OK, [], true); + } + + return new JsonResponse( + [ + 'message' => strtr('Transaction is in state {state}, it can not be completed at this time', ['{state}' => $transaction->getState()]), + ], + Response::HTTP_NOT_ACCEPTABLE + ); + } +} diff --git a/src/Core/Api/Transaction/Controller/TransactionController.php b/src/Core/Api/Transaction/Controller/TransactionController.php new file mode 100644 index 0000000..560d3cd --- /dev/null +++ b/src/Core/Api/Transaction/Controller/TransactionController.php @@ -0,0 +1,165 @@ + ['api']])] +class TransactionController extends AbstractController { + + /** + * @var \VRPaymentPayment\Core\Settings\Service\SettingsService + */ + protected $settingsService; + + /** + * @var \Psr\Log\LoggerInterface + */ + protected $logger; + + /** + * @var \VRPaymentPayment\Core\Api\Transaction\Service\TransactionService + */ + protected $transactionService; + + /** + * TransactionController constructor. + * + * @param \VRPaymentPayment\Core\Settings\Service\SettingsService $settingsService + * @param \VRPaymentPayment\Core\Api\Transaction\Service\TransactionService $transactionService + */ + public function __construct(SettingsService $settingsService, TransactionService $transactionService) + { + $this->settingsService = $settingsService; + $this->transactionService = $transactionService; + } + + /** + * @param \Psr\Log\LoggerInterface $logger + * + * @internal + * @required + * + */ + public function setLogger(LoggerInterface $logger): void + { + $this->logger = $logger; + } + + /** + * @param \Symfony\Component\HttpFoundation\Request $request + * @param \Shopware\Core\Framework\Context $context + * + * @return \Symfony\Component\HttpFoundation\JsonResponse + */ + #[Route("/api/_action/vrpayment/transaction/get-transaction-data/", + name: "api.action.vrpayment.transaction.get-transaction-data", + methods: ['POST'])] + public function getTransactionData(Request $request, Context $context): JsonResponse + { + $transactionId = $request->request->get('transactionId'); + + $transaction = $this->transactionService->getByTransactionId(intval($transactionId), $context); + $refundCollection = $this->transactionService->getRefundEntityCollectionByTransactionId(intval($transactionId), $context); + + $refunds = []; + foreach ($refundCollection as $refundEntity) { + $refunds[] = $refundEntity ? $refundEntity->getData() : []; + } + + return new JsonResponse([ + 'refunds' => $refunds, + 'transactions' => [$transaction ? $transaction->getData() : []], + ]); + } + + /** + * @param string $salesChannelId + * @param int $transactionId + * + * @return \Symfony\Component\HttpFoundation\Response + * @throws \VRPayment\Sdk\ApiException + * @throws \VRPayment\Sdk\Http\ConnectionException + * @throws \VRPayment\Sdk\VersioningException + * + */ + #[Route("/api/_action/vrpayment/transaction/get-invoice-document/{salesChannelId}/{transactionId}", + name: "api.action.vrpayment.transaction.get-invoice-document", + methods: ['GET'], + defaults: ["csrf_protected" => false, "auth_required" => false])] + public function getInvoiceDocument(string $salesChannelId, int $transactionId): Response + { + $settings = $this->settingsService->getSettings($salesChannelId); + $apiClient = $settings->getApiClient(); + + $invoiceDocument = $apiClient->getTransactionService()->getInvoiceDocument($settings->getSpaceId(), $transactionId); + $forceDownload = true; + $filename = preg_replace('/[\x00-\x1F\x7F-\xFF]/', '_', $invoiceDocument->getTitle()) . '.pdf'; + $disposition = HeaderUtils::makeDisposition( + $forceDownload ? HeaderUtils::DISPOSITION_ATTACHMENT : HeaderUtils::DISPOSITION_INLINE, + $filename, + $filename + ); + $response = new Response(base64_decode($invoiceDocument->getData())); + $response->headers->set('Content-Type', $invoiceDocument->getMimeType()); + $response->headers->set('Content-Disposition', $disposition); + + return $response; + } + + /** + * @param string $salesChannelId + * @param int $transactionId + * + * @return \Symfony\Component\HttpFoundation\Response + * @throws \VRPayment\Sdk\ApiException + * @throws \VRPayment\Sdk\Http\ConnectionException + * @throws \VRPayment\Sdk\VersioningException + * + */ + #[Route("/api/_action/vrpayment/transaction/get-packing-slip/{salesChannelId}/{transactionId}", + name: "api.action.vrpayment.transaction.get-packing-slip", + methods: ['GET'], + defaults: ["csrf_protected" => false, "auth_required" => false])] + public function getPackingSlip(string $salesChannelId, int $transactionId): Response + { + $settings = $this->settingsService->getSettings($salesChannelId); + $apiClient = $settings->getApiClient(); + + $invoiceDocument = $apiClient->getTransactionService()->getPackingSlip($settings->getSpaceId(), $transactionId); + $forceDownload = true; + $filename = preg_replace('/[\x00-\x1F\x7F-\xFF]/', '_', $invoiceDocument->getTitle()) . '.pdf'; + $disposition = HeaderUtils::makeDisposition( + $forceDownload ? HeaderUtils::DISPOSITION_ATTACHMENT : HeaderUtils::DISPOSITION_INLINE, + $filename, + $filename + // only printable ascii + + ); + $response = new Response(base64_decode($invoiceDocument->getData())); + $response->headers->set('Content-Type', $invoiceDocument->getMimeType()); + $response->headers->set('Content-Disposition', $disposition); + + return $response; + } +} diff --git a/src/Core/Api/Transaction/Controller/TransactionVoidController.php b/src/Core/Api/Transaction/Controller/TransactionVoidController.php new file mode 100644 index 0000000..bcf4fcf --- /dev/null +++ b/src/Core/Api/Transaction/Controller/TransactionVoidController.php @@ -0,0 +1,90 @@ + ['api']])] +class TransactionVoidController extends AbstractController { + + /** + * @var \VRPaymentPayment\Core\Settings\Service\SettingsService + */ + protected $settingsService; + + /** + * @var \Psr\Log\LoggerInterface + */ + protected $logger; + + /** + * TransactionVoidController constructor. + * + * @param \VRPaymentPayment\Core\Settings\Service\SettingsService $settingsService + */ + public function __construct(SettingsService $settingsService) + { + $this->settingsService = $settingsService; + } + + /** + * @param \Psr\Log\LoggerInterface $logger + * @internal + * @required + * + */ + public function setLogger(LoggerInterface $logger): void + { + $this->logger = $logger; + } + + /** + * @param \Symfony\Component\HttpFoundation\Request $request + * @return \Symfony\Component\HttpFoundation\JsonResponse + * @throws \VRPayment\Sdk\ApiException + * @throws \VRPayment\Sdk\Http\ConnectionException + * @throws \VRPayment\Sdk\VersioningException + * + */ + #[Route("/api/_action/vrpayment/transaction-void/create-transaction-void/", + name: "api.action.vrpayment.transaction-void.create-transaction-void", + methods: ['POST'])] + public function createTransactionVoid(Request $request): JsonResponse + { + $salesChannelId = $request->request->get('salesChannelId'); + $transactionId = $request->request->get('transactionId'); + + $settings = $this->settingsService->getSettings($salesChannelId); + $apiClient = $settings->getApiClient(); + + $transaction = $apiClient->getTransactionService()->read($settings->getSpaceId(), $transactionId); + if ($transaction->getState() == TransactionState::AUTHORIZED) { + $transactionVoid = $apiClient->getTransactionVoidService()->voidOnline($settings->getSpaceId(), $transaction->getId()); + return new JsonResponse(strval($transactionVoid), Response::HTTP_OK, [], true); + } + + return new JsonResponse( + [ + 'message' => strtr('Transaction is in state {state}, it can not be completed at this time.', ['{state}' => $transaction->getState()]), + ], + Response::HTTP_NOT_ACCEPTABLE + ); + } +} diff --git a/src/Core/Api/Transaction/Entity/TransactionEntity.php b/src/Core/Api/Transaction/Entity/TransactionEntity.php new file mode 100644 index 0000000..a2e661e --- /dev/null +++ b/src/Core/Api/Transaction/Entity/TransactionEntity.php @@ -0,0 +1,321 @@ +confirmationEmailSent; + } + + /** + * @param bool $confirmationEmailSent + */ + public function setConfirmationEmailSent(bool $confirmationEmailSent): void + { + $this->confirmationEmailSent = $confirmationEmailSent; + } + + /** + * @return array + */ + public function getData(): array + { + return $this->data; + } + + /** + * @param array $data + */ + public function setData(array $data): void + { + $this->data = $data; + } + + /** + * @return string + */ + public function getPaymentMethodId(): string + { + return $this->paymentMethodId; + } + + /** + * @param string $paymentMethodId + */ + public function setPaymentMethodId(string $paymentMethodId): void + { + $this->paymentMethodId = $paymentMethodId; + } + + /** + * @return string + */ + public function getOrderId(): string + { + return $this->orderId; + } + + /** + * @param string $orderId + */ + public function setOrderId(string $orderId): void + { + $this->orderId = $orderId; + } + + /** + * @return string + */ + public function getOrderTransactionId(): string + { + return $this->orderTransactionId; + } + + /** + * @param string $orderTransactionId + */ + public function setOrderTransactionId(string $orderTransactionId): void + { + $this->orderTransactionId = $orderTransactionId; + } + + /** + * @return int + */ + public function getSpaceId(): int + { + return $this->spaceId; + } + + /** + * @param int $spaceId + */ + public function setSpaceId(int $spaceId): void + { + $this->spaceId = $spaceId; + } + + /** + * @return string + */ + public function getState(): string + { + return $this->state; + } + + /** + * @param string $state + */ + public function setState(string $state): void + { + $this->state = $state; + } + + /** + * @return string + */ + public function getSalesChannelId(): string + { + return $this->salesChannelId; + } + + /** + * @param string $salesChannelId + */ + public function setSalesChannelId(string $salesChannelId): void + { + $this->salesChannelId = $salesChannelId; + } + + /** + * @return int + */ + public function getTransactionId(): int + { + return $this->transactionId; + } + + /** + * @param int $transactionId + */ + public function setTransactionId(int $transactionId): void + { + $this->transactionId = $transactionId; + } + + /** + * @return \Shopware\Core\Checkout\Payment\PaymentMethodEntity + */ + public function getPaymentMethod(): PaymentMethodEntity + { + return $this->paymentMethod; + } + + /** + * @param \Shopware\Core\Checkout\Payment\PaymentMethodEntity $paymentMethod + */ + public function setPaymentMethod(PaymentMethodEntity $paymentMethod): void + { + $this->paymentMethod = $paymentMethod; + } + + /** + * @return \Shopware\Core\Checkout\Order\OrderEntity + */ + public function getOrder(): OrderEntity + { + return $this->order; + } + + /** + * @param \Shopware\Core\Checkout\Order\OrderEntity $order + */ + public function setOrder(OrderEntity $order): void + { + $this->order = $order; + } + + /** + * @return \Shopware\Core\Checkout\Order\Aggregate\OrderTransaction\OrderTransactionEntity + */ + public function getOrderTransaction(): OrderTransactionEntity + { + return $this->orderTransaction; + } + + /** + * @param \Shopware\Core\Checkout\Order\Aggregate\OrderTransaction\OrderTransactionEntity $orderTransaction + */ + public function setOrderTransaction(OrderTransactionEntity $orderTransaction): void + { + $this->orderTransaction = $orderTransaction; + } + + /** + * @return \VRPaymentPayment\Core\Api\Refund\Entity\RefundEntityCollection|null + */ + public function getRefunds(): ?RefundEntityCollection + { + return $this->refunds; + } + + /** + * @param \VRPaymentPayment\Core\Api\Refund\Entity\RefundEntityCollection $refunds + */ + public function setRefunds(RefundEntityCollection $refunds): void + { + $this->refunds = $refunds; + } + + /** + * @return \Shopware\Core\System\SalesChannel\SalesChannelEntity + */ + public function getSalesChannel(): SalesChannelEntity + { + return $this->salesChannel; + } + + /** + * @param \Shopware\Core\System\SalesChannel\SalesChannelEntity $salesChannel + */ + public function setSalesChannel(SalesChannelEntity $salesChannel): void + { + $this->salesChannel = $salesChannel; + } +} diff --git a/src/Core/Api/Transaction/Entity/TransactionEntityCollection.php b/src/Core/Api/Transaction/Entity/TransactionEntityCollection.php new file mode 100644 index 0000000..7714125 --- /dev/null +++ b/src/Core/Api/Transaction/Entity/TransactionEntityCollection.php @@ -0,0 +1,46 @@ +getIterator() as $element) { + if ($element->getTransactionId() === $transactionId) { + return $element; + } + } + + return null; + } + + /** + * @return string + */ + protected function getExpectedClass(): string + { + return TransactionEntity::class; + } +} \ No newline at end of file diff --git a/src/Core/Api/Transaction/Entity/TransactionEntityDefinition.php b/src/Core/Api/Transaction/Entity/TransactionEntityDefinition.php new file mode 100644 index 0000000..3dcb045 --- /dev/null +++ b/src/Core/Api/Transaction/Entity/TransactionEntityDefinition.php @@ -0,0 +1,89 @@ +addFlags(new PrimaryKey(), new Required()), + new BoolField('confirmation_email_sent', 'confirmationEmailSent'), + new StringField('erp_merchant_id', 'erpMerchantId'), + (new JsonField('data', 'data'))->addFlags(new Required()), + (new FkField('payment_method_id', 'paymentMethodId', PaymentMethodDefinition::class))->addFlags(new Required()), + (new FkField('order_id', 'orderId', OrderDefinition::class))->addFlags(new Required()), + (new FkField('order_transaction_id', 'orderTransactionId', OrderTransactionDefinition::class))->addFlags(new Required()), + (new IntField('space_id', 'spaceId'))->addFlags(new Required()), + (new StringField('state', 'state'))->addFlags(new Required()), + (new FkField('sales_channel_id', 'salesChannelId', SalesChannelDefinition::class))->addFlags(new Required()), + (new IntField('transaction_id', 'transactionId'))->addFlags(new Required()), + new OneToOneAssociationField('paymentMethod', 'payment_method_id', 'id', PaymentMethodDefinition::class, true), + new OneToOneAssociationField('order', 'order_id', 'id', OrderDefinition::class, true), + new OneToOneAssociationField('orderTransaction', 'order_transaction_id', 'id', OrderTransactionDefinition::class, true), + (new OneToManyAssociationField('refunds', RefundEntityDefinition::class, 'transaction_id', 'transaction_id'))->addFlags(new CascadeDelete()), + new OneToOneAssociationField('salesChannel', 'sales_channel_id', 'id', SalesChannelDefinition::class, true), + (new ReferenceVersionField(OrderDefinition::class))->addFlags(new ApiAware(), new Required()), + new CreatedAtField(), + new UpdatedAtField(), + ]); + } + + /** + * @return string + */ + public function getCollectionClass(): string + { + return TransactionEntityCollection::class; + } + + /** + * @return string + */ + public function getEntityClass(): string + { + return TransactionEntity::class; + } + +} diff --git a/src/Core/Api/Transaction/Service/OrderMailService.php b/src/Core/Api/Transaction/Service/OrderMailService.php new file mode 100644 index 0000000..966d22e --- /dev/null +++ b/src/Core/Api/Transaction/Service/OrderMailService.php @@ -0,0 +1,261 @@ +container = $container; + $this->mailService = $mailService; + } + + /** + * @param \Psr\Log\LoggerInterface $logger + * + * @internal + * @required + * + */ + public function setLogger(LoggerInterface $logger): void + { + $this->logger = $logger; + } + + /** + * @param string $orderId + * @param \Shopware\Core\Framework\Context $context + */ + public function send(string $orderId, Context $context): void + { + try { + + $transactionEntity = $this->getTransactionEntityByOrderId($orderId, $context); + if ($transactionEntity->isConfirmationEmailSent()) { + return; + } + + $order = $this->getOrder($orderId, $context); + if (is_null($order->getOrderCustomer())) { + return; + } + + $languageIdChain[] = $order->getLanguageId(); + $contextLanguageIdChain = $context->getLanguageIdChain(); + foreach ($contextLanguageIdChain as $languageId) { + $contextLanguageIdChain[] = $languageId; + } + array_unique($languageIdChain); + + $context->assign(['languageIdChain' => $languageIdChain,]); + + $templateData = [ + 'order' => $order, + self::EMAIL_ORIGIN_IS_VRPAYMENT => true, + ]; + + $data = $this->getData($order, $context); + + foreach ($data as $datum){ + $this->mailService->send($datum, $context, $templateData); + } + $this->markTransactionEntityConfirmationEmailAsSent($orderId, $context); + + + } catch (\Exception $exception) { + $this->logger->critical($exception->getMessage()); + } + } + + /** + * Get transaction entity by orderId + * + * @param string $orderId + * @param \Shopware\Core\Framework\Context $context + * + * @return \VRPaymentPayment\Core\Api\Transaction\Entity\TransactionEntity + */ + protected function getTransactionEntityByOrderId(string $orderId, Context $context): TransactionEntity + { + return $this->container->get(TransactionEntityDefinition::ENTITY_NAME . '.repository') + ->search(new Criteria([$orderId]), $context) + ->get($orderId); + } + + /** + * @param string $orderId + * @param \Shopware\Core\Framework\Context $context + * + * @return \Shopware\Core\Checkout\Order\OrderEntity + */ + protected function getOrder(string $orderId, Context $context): OrderEntity + { + $orderCriteria = (new Criteria([$orderId]))->addAssociations([ + 'addresses', + 'addresses.country', + 'currency', + 'deliveries', + 'deliveries.shippingCosts', + 'deliveries.shippingMethod', + 'deliveries.shippingOrderAddress', + 'deliveries.shippingOrderAddress.country', + 'documents', + 'language', + 'lineItems', + 'orderCustomer', + 'orderCustomer.customer', + 'orderCustomer.salutation', + 'salesChannel', + 'stateMachineState', + 'tags', + 'transactions', + 'transactions.paymentMethod', + ]); + + /** @var OrderEntity|null $order */ + $order = $this->container->get('order.repository')->search($orderCriteria, $context)->first(); + if (is_null($order)) { + throw CartException::orderNotFound($orderId); + } + return $order; + } + + + /** + * @param \Shopware\Core\Checkout\Order\OrderEntity $order + * @param \Shopware\Core\Framework\Context $context + * + * @return array + */ + protected function getData(OrderEntity $order, Context $context): array + { + $data = []; + + /** + * @var + */ + /** @var \Shopware\Core\Framework\Event\EventAction\EventActionCollection $eventActionEntities */ + $eventActionEntities = $this->getBusinessEvents($order, $context); + $customerRecipient = [ + $order->getOrderCustomer()->getEmail() => $order->getOrderCustomer()->getFirstName() . ' ' . $order->getOrderCustomer()->getLastName(), + ]; + + foreach ($eventActionEntities as $eventActionEntity) { + + $eventConfig = $eventActionEntity->getConfig(); + $mailTemplateId = $eventConfig['mail_template_id']; + $recipients = !empty($eventConfig['recipients']) ? $eventConfig['recipients'] : $customerRecipient; + $mailTemplate = $this->getMailTemplateById($context, $mailTemplateId); + + $data[] = [ + 'recipients' => $recipients, + 'senderName' => $mailTemplate->getTranslation('senderName'), + 'salesChannelId' => $order->getSalesChannelId(), + 'templateId' => $mailTemplateId, + 'customFields' => $mailTemplate->getCustomFields(), + 'contentHtml' => $mailTemplate->getTranslation('contentHtml'), + 'contentPlain' => $mailTemplate->getTranslation('contentPlain'), + 'subject' => $mailTemplate->getTranslation('subject'), + ]; + } + + return $data; + } + + protected function getBusinessEvents(OrderEntity $order, Context $context): EventActionCollection + { + $criteria = (new Criteria()) + ->addAssociations([ + 'rules', + 'salesChannels', + ]) + ->addFilter(new EqualsFilter('eventName', CheckoutOrderPlacedEvent::EVENT_NAME)) + ->addFilter(new EqualsFilter('active', true)) + ->addFilter(new NotFilter(NotFilter::CONNECTION_AND, [new EqualsFilter('config.mail_template_id', null)])) + ->addFilter(new OrFilter([ + new EqualsFilter('salesChannels.id', $order->getSalesChannelId()), + new EqualsFilter('salesChannels.id', null), + ])); + + + /** @var EventActionCollection $events */ + $events = $this->container->get('event_action.repository') + ->search($criteria, $context) + ->getEntities(); + return $events; + } + + /** + * @param \Shopware\Core\Framework\Context $context + * @param string $id + * + * @return \Shopware\Core\Content\MailTemplate\MailTemplateEntity + */ + protected function getMailTemplateById(Context $context, string $id): MailTemplateEntity + { + $criteria = (new Criteria([$id]))->addAssociations(['media', 'media.media', 'salesChannels', 'mailTemplateType']); + + $mailTemplateEntity = $this->container->get('mail_template.repository')->search($criteria, $context)->first(); + + return $mailTemplateEntity; + } + + /** + * @param string $orderId + * @param \Shopware\Core\Framework\Context $context + */ + protected function markTransactionEntityConfirmationEmailAsSent(string $orderId, Context $context) + { + $this->container->get(TransactionEntityDefinition::ENTITY_NAME . '.repository')->upsert([['id' => $orderId, 'confirmationEmailSent' => true]], $context); + } +} diff --git a/src/Core/Api/Transaction/Service/TransactionService.php b/src/Core/Api/Transaction/Service/TransactionService.php new file mode 100644 index 0000000..20851c0 --- /dev/null +++ b/src/Core/Api/Transaction/Service/TransactionService.php @@ -0,0 +1,750 @@ +container = $container; + $this->localeCodeProvider = $localeCodeProvider; + $this->settingsService = $settingsService; + } + + /** + * @param \Psr\Log\LoggerInterface $logger + * + * @internal + * @required + * + */ + public function setLogger(LoggerInterface $logger): void + { + $this->logger = $logger; + } + + /** + * The pay function will be called after the customer completed the order. + * Allows to process the order and store additional information. + * + * A redirect to the url will be performed + * + * @param \Shopware\Core\Checkout\Payment\Cart\PaymentTransactionStruct $transaction + * @param \Shopware\Core\System\SalesChannel\SalesChannelContext $salesChannelContext + * + * @return string + * @throws \VRPayment\Sdk\ApiException + * @throws \VRPayment\Sdk\Http\ConnectionException + * @throws \VRPayment\Sdk\VersioningException + */ + public function create( + PaymentTransactionStruct $transaction, + SalesChannelContext $salesChannelContext + ): string + { + $criteria = new Criteria([$transaction->getOrderTransactionId()]); + $criteria->addAssociation('order'); + $orderTransaction = $this->container->get('order_transaction.repository')->search($criteria, $salesChannelContext->getContext())->first(); + + $salesChannelId = $salesChannelContext->getSalesChannel()->getId(); + $settings = $this->settingsService->getSettings($salesChannelId); + $apiClient = $settings->getApiClient(); + + $transactionId = $_SESSION['transactionId'] ?? null; + if ($transactionId !== null) { + $pendingTransaction = $this->read($_SESSION['transactionId'], $salesChannelId); + } + + if ($transactionId === null || $pendingTransaction === null || $pendingTransaction->getState() !== TransactionState::PENDING) { + unset($_SESSION['transactionId']); + $pendingTransactionId = $this->createPendingTransaction($salesChannelContext); + $pendingTransaction = $this->read($pendingTransactionId, $salesChannelId); + } + + $transactionPayloadClass = (new TransactionPayload( + $this->container, + $this->localeCodeProvider, + $salesChannelContext, + $settings, + $transaction + )); + $transactionPayloadClass->setLogger($this->logger); + $transactionPayload = $transactionPayloadClass->get($pendingTransaction->getVersion()); + + $createdTransaction = $apiClient->getTransactionService() + ->confirm($settings->getSpaceId(), $transactionPayload); + + $this->addVRPaymentTransactionId( + $transaction, + $salesChannelContext->getContext(), + $createdTransaction->getId(), + $settings->getSpaceId() + ); + + $redirectUrl = $this->container->get('router')->generate( + 'frontend.vrpayment.checkout.pay', + ['orderId' => $orderTransaction->getOrder()->getId(),], + UrlGeneratorInterface::ABSOLUTE_URL + ); + + if ($settings->getIntegration() == Integration::PAYMENT_PAGE) { + $redirectUrl = $apiClient->getTransactionPaymentPageService() + ->paymentPageUrl($settings->getSpaceId(), $createdTransaction->getId()); + } + + $this->upsert( + $createdTransaction, + $salesChannelContext->getContext(), + $orderTransaction->getPaymentMethodId(), + $orderTransaction->getOrder()->getSalesChannelId() + ); + $_SESSION['transactionId'] = null; + $_SESSION['arrayOfPossibleMethods'] = null; + $_SESSION['addressCheck'] = null; + $_SESSION['currencyCheck'] = null; + + + $this->holdDelivery($orderTransaction->getOrder()->getId(), $salesChannelContext->getContext()); + + return $redirectUrl; + } + + /** + * @param \Shopware\Core\Checkout\Payment\Cart\PaymentTransactionStruct $transaction + * @param \Shopware\Core\Framework\Context $context + * @param int $vrpaymentTransactionId + * @param int $spaceId + */ + protected function addVRPaymentTransactionId( + PaymentTransactionStruct $transaction, + Context $context, + int $vrpaymentTransactionId, + int $spaceId + ): void + { + $data = [ + 'id' => $transaction->getOrderTransactionId(), + 'customFields' => [ + TransactionPayload::ORDER_TRANSACTION_CUSTOM_FIELDS_VRPAYMENT_TRANSACTION_ID => $vrpaymentTransactionId, + TransactionPayload::ORDER_TRANSACTION_CUSTOM_FIELDS_VRPAYMENT_SPACE_ID => $spaceId, + ], + ]; + $this->container->get('order_transaction.repository')->update([$data], $context); + } + + /** + * Persist VRPayment transaction + * + * @param \VRPayment\Sdk\Model\Transaction $transaction + * @param \Shopware\Core\Framework\Context $context + * @param string|null $paymentMethodId + * @param string|null $salesChannelId + */ + public function upsert( + Transaction $transaction, + Context $context, + string $paymentMethodId = null, + string $salesChannelId = null + ): void + { + try { + + $transactionId = $transaction->getId(); + $transactionMetaData = $transaction->getMetaData(); + + if (!$salesChannelId) { + $salesChannelId = $transactionMetaData['salesChannelId'] ?? ''; + } + + $orderId = $transactionMetaData[TransactionPayload::VRPAYMENT_METADATA_ORDER_ID]; + $orderTransactionId = $transactionMetaData[TransactionPayload::VRPAYMENT_METADATA_ORDER_TRANSACTION_ID]; + + $dataParamValue = json_decode(strval($transaction), true); + $brandName = ''; + if (isset($dataParamValue['paymentConnectorConfiguration'])) { + $brandName = $dataParamValue['paymentConnectorConfiguration'] + ? $dataParamValue['paymentConnectorConfiguration']['name'] + : ''; + } + $dataParamValue['brandName'] = $brandName; + + $paymentMethodName = ''; + if (isset($dataParamValue['paymentConnectorConfiguration'])) { + $paymentMethodName = $dataParamValue['paymentConnectorConfiguration'] + ? $dataParamValue['paymentConnectorConfiguration']['paymentMethodConfiguration']['name'] + : ''; + } + $dataParamValue['paymentMethodName'] = $paymentMethodName; + + $chargeAttempt = $this->getChargeAttempt($salesChannelId, $transactionId); + + $erpMerchantId = null; + if ($chargeAttempt) { + $creditCardHolder = $this->getChargeAttemptAdditionalData($chargeAttempt, self::CARD_HOLDER_KEY); + $dataParamValue['creditCardHolder'] = $creditCardHolder ? $creditCardHolder[0] : ''; + + $pseudoCardNumber = $this->getChargeAttemptAdditionalData($chargeAttempt, self::PSEUDO_CODE_KEY); + $dataParamValue['pseudoCardNumber'] = $pseudoCardNumber ? $pseudoCardNumber[0] : ''; + + $payId = $this->getChargeAttemptAdditionalData($chargeAttempt, self::PAY_ID_KEY); + $dataParamValue['payId'] = $payId ? $payId[0] : ''; + + $dataParamValue['customerName'] = isset($transactionMetaData[TransactionPayload::VRPAYMENT_METADATA_CUSTOMER_NAME]) + ? $transactionMetaData[TransactionPayload::VRPAYMENT_METADATA_CUSTOMER_NAME] + : ''; + + $creditCardValidity = $this->getChargeAttemptAdditionalData($chargeAttempt, self::CARD_VALIDITY_KEY); + + if (isset($creditCardValidity['cardExpireMonth']) && isset($creditCardValidity['cardExpireYear'])) { + $creditCardExpireMonth = $creditCardValidity['cardExpireMonth'] ?? null; + if (!empty($creditCardExpireMonth)) { + $dataParamValue['cardExpireMonth'] = sprintf("%02d", $creditCardExpireMonth); + } + $creditCardExpireYear = $creditCardValidity['cardExpireYear'] ?? null; + if (!empty($creditCardExpireYear)) { + $dataParamValue['cardExpireYear'] = $creditCardExpireYear; + } + } + + $erpMerchantId = $this->getChargeAttemptAdditionalData($chargeAttempt, self::ADDITIONAL_TRANSACTION_DETAILS_ORDER_ID_KEY); + $erpMerchantId = $erpMerchantId ? $erpMerchantId[0] : null; + } + + $data = [ + 'id' => $orderId, + 'erpMerchantId' => $erpMerchantId, + 'data' => $dataParamValue, + 'paymentMethodId' => $paymentMethodId, + 'orderId' => $orderId, + 'orderTransactionId' => $orderTransactionId, + 'spaceId' => $transaction->getLinkedSpaceId(), + 'state' => $transaction->getState(), + 'salesChannelId' => $salesChannelId, + 'transactionId' => $transaction->getId(), + ]; + + $data = array_filter($data); + $this->container->get(TransactionEntityDefinition::ENTITY_NAME . '.repository')->upsert([$data], $context); + + } catch (\Exception $exception) { + $this->logger->critical(__CLASS__ . ' : ' . __FUNCTION__ . ' : ' . $exception->getMessage()); + } + } + + /** + * Hold delivery + * + * @param string $orderId + * @param \Shopware\Core\Framework\Context $context + */ + private function holdDelivery(string $orderId, Context $context) + { + try { + /** + * @var OrderDeliveryStateHandler $orderDeliveryStateHandler + */ + $orderEntity = $this->getOrderEntity($orderId, $context); + $orderDeliveryStateHandler = $this->container->get(OrderDeliveryStateHandler::class); + if (null !== $orderEntity->getDeliveries()->last()) { + $orderDeliveryStateHandler->hold($orderEntity->getDeliveries()->last()->getId(), $context); + } + } catch (\Exception $exception) { + $this->logger->critical($exception->getTraceAsString()); + } + } + + /** + * Get order + * + * @param String $orderId + * @param \Shopware\Core\Framework\Context $context + * + * @return \Shopware\Core\Checkout\Order\OrderEntity + */ + protected function getOrderEntity(string $orderId, Context $context): OrderEntity + { + try { + $criteria = (new Criteria([$orderId]))->addAssociations(['deliveries']); + $order = $this->container->get('order.repository')->search( + $criteria, + $context + )->first(); + if (is_null($order)) { + throw CartException::orderNotFound($orderId); + } + return $order; + } catch (\Exception $e) { + throw CartException::orderNotFound($orderId); + } + + } + + /** + * Get transaction entity by orderId + * + * @param string $orderId + * @param \Shopware\Core\Framework\Context $context + * + * @return \VRPaymentPayment\Core\Api\Transaction\Entity\TransactionEntity + */ + public function getByOrderId(string $orderId, Context $context): TransactionEntity + { + return $this->container->get(TransactionEntityDefinition::ENTITY_NAME . '.repository') + ->search(new Criteria([$orderId]), $context) + ->get($orderId); + } + + /** + * Read transaction from VRPayment API + * + * @param int $transactionId + * @param string $salesChannelId + * + * @return \VRPayment\Sdk\Model\Transaction + * @throws \VRPayment\Sdk\ApiException + * @throws \VRPayment\Sdk\Http\ConnectionException + * @throws \VRPayment\Sdk\VersioningException + */ + public function read(int $transactionId, string $salesChannelId): Transaction + { + $settings = $this->settingsService->getSettings($salesChannelId); + return $settings->getApiClient()->getTransactionService()->read($settings->getSpaceId(), $transactionId); + } + + /** + * Get transaction entity by VRPayment transaction id + * + * @param int $transactionId + * @param \Shopware\Core\Framework\Context $context + * + * @return \VRPaymentPayment\Core\Api\Transaction\Entity\TransactionEntity|null + */ + public function getByTransactionId(int $transactionId, Context $context): ?TransactionEntity + { + return $this->container->get(TransactionEntityDefinition::ENTITY_NAME . '.repository') + ->search( + (new Criteria())->addFilter(new EqualsFilter('transactionId', $transactionId)) + ->addAssociations(['refunds']), $context + ) + ->first(); + } + + /** + * Get transaction entity by VRPayment order transaction id + * + * @param string $transactionId + * @param \Shopware\Core\Framework\Context $context + * + * @return \VRPaymentPayment\Core\Api\Transaction\Entity\TransactionEntity|null + */ + public function getByOrderTransactionId(string $orderTransactionId, Context $context): ?TransactionEntity + { + return $this->container->get(TransactionEntityDefinition::ENTITY_NAME . '.repository') + ->search( + (new Criteria())->addFilter(new EqualsFilter('orderTransactionId', $orderTransactionId)) + ->addAssociations(['refunds']), $context + ) + ->first(); + } + + /** + * Get transaction entity by VRPayment transaction id + * + * @param int $transactionId + * @param \Shopware\Core\Framework\Context $context + * + * @return \VRPaymentPayment\Core\Api\Refund\Entity\RefundEntityCollection + */ + public function getRefundEntityCollectionByTransactionId(int $transactionId, Context $context): ?RefundEntityCollection + { + return $this->container->get(RefundEntityDefinition::ENTITY_NAME . '.repository') + ->search( + (new Criteria())->addFilter(new EqualsFilter('transactionId', $transactionId)), $context + ) + ->getEntities(); + } + + /** + * @param string $orderId + * @param float $invoicePaidAmount + * @param Context $context + * @return void + */ + public function updateOrderTotalPriceByInvoiceTotal(string $orderId, float $invoicePaidAmount, Context $context): void + { + $price = $this->getOrderEntity($orderId, $context)->getPrice(); + + if ($price->getTotalPrice() === $invoicePaidAmount) { + return; + } + + $data = [ + 'id' => $orderId, + 'price' => [ + 'netPrice' => $price->getNetPrice(), + 'rawTotal' => $price->getRawTotal(), + 'taxRules' => $price->getTaxRules(), + 'taxStatus' => $price->getTaxStatus(), + 'totalPrice' => $invoicePaidAmount, + 'positionPrice' => $price->getPositionPrice(), + 'calculatedTaxes' => $price->getCalculatedTaxes() + ], + ]; + + $this->container->get('order.repository')->update([$data], $context); + } + + /** + * @param SalesChannelContext $salesChannelContext + * @param CheckoutConfirmPageLoadedEvent|null $event + * @return int + */ + public function createPendingTransaction(SalesChannelContext $salesChannelContext, ?CheckoutConfirmPageLoadedEvent $event = null): int + { + $expiredTransaction = true; + $transactionId = $_SESSION['transactionId'] ?? null; + $settings = $this->settingsService->getValidSettings($salesChannelContext->getSalesChannel()->getId()); + + if ($transactionId) { + $transactionService = $settings->getApiClient()->getTransactionService(); + $pendingTransaction = $transactionService->read($settings->getSpaceId(), $transactionId); + $failedStates = [ + TransactionState::DECLINE, + TransactionState::FAILED, + TransactionState::VOIDED, + ]; + if (!in_array($pendingTransaction->getState(), $failedStates)) { + $expiredTransaction = false; + } + } + + if (!$transactionId || $expiredTransaction) { + $settings = $this->settingsService->getValidSettings($salesChannelContext->getSalesChannel()->getId()); + + $customer = $salesChannelContext->getCustomer(); + $customerBillingAddress = $customer->getActiveBillingAddress(); + + $billingAddress = new AddressCreate(); + + $customerAddressEntity = $customer->getActiveBillingAddress(); + + $familyName = ""; + if (!empty($customerAddressEntity->getLastName())) { + $familyName = $customerAddressEntity->getLastName(); + } else { + if (!empty($customer->getLastName())) { + $familyName = $customer->getLastName(); + } + } + $billingAddress->setFamilyName($familyName); + + $givenName = ""; + if (!empty($customerAddressEntity->getFirstName())) { + $givenName = $customerAddressEntity->getFirstName(); + } else { + if (!empty($customer->getFirstName())) { + $givenName = $customer->getFirstName(); + } + } + $billingAddress->setGivenName($givenName); + $billingAddress->setOrganizationName($customerBillingAddress->getCompany()); + $billingAddress->setPhoneNumber($customerAddressEntity->getPhoneNumber()); + $billingAddress->setCountry($customerBillingAddress->getCountry()->getIso()); + $postalState = $customerBillingAddress?->getCountryState()?->getName() ?? ''; + if (empty($postalState)) { + $postalState = $customerBillingAddress?->getCountryState()?->getShortCode() ?? ''; + } + $billingAddress->setPostalState($postalState); + $billingAddress->setPostCode($customerBillingAddress->getZipcode()); + $billingAddress->setStreet($customerBillingAddress->getStreet()); + $billingAddress->setEmailAddress($customer->getEmail()); + + + if (!empty($customer->getBirthday())) { + $birthday = new \DateTime(); + $birthday->setTimestamp($customer->getBirthday()->getTimestamp()); + $birthday = $birthday->format('Y-m-d'); + $billingAddress->setDateOfBirth($birthday); + } + + $salutation = ""; + if (!( + empty($customerAddressEntity->getSalutation()) || + empty($customerAddressEntity->getSalutation()->getDisplayName()) + )) { + $salutation = $customerAddressEntity->getSalutation()->getDisplayName(); + } else { + if (!empty($customer->getSalutation())) { + $salutation = $customer->getSalutation()->getDisplayName(); + + } + } + + $billingAddress->setGender(strtolower($customerAddressEntity->getSalutation()->getSalutationKey()) === 'mr' ? Gender::MALE : Gender::FEMALE); + $billingAddress->setSalutation($salutation); + + $lineItems = []; + if ($event) { + $cartLineItems = $event->getPage()->getCart()->getLineItems()->getElements(); + foreach ($cartLineItems as $cartLineItem) { + if ($cartLineItem->getType() === CustomProductsLineItemTypes::LINE_ITEM_TYPE_CUSTOMIZED_PRODUCTS) { + continue; + } + $lineItems[] = $this->createTempLineItem($cartLineItem); + } + } + + $customerId = ""; + if ($customer->getGuest() === false) { + $customerId = $customer->getCustomerNumber(); + } + + $protocol = isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? 'https://' : 'http://'; + $homeUrl = $protocol . $_SERVER['HTTP_HOST']; + $currency = $salesChannelContext->getCurrency()->getIsoCode(); + $transactionPayload = (new TransactionCreate()) + ->setBillingAddress($billingAddress) + ->setLineItems($lineItems) + ->setCurrency($currency) + ->setSpaceViewId($settings->getSpaceViewId()) + ->setAutoConfirmationEnabled(false) + ->setChargeRetryEnabled(false) + ->setCustomerEmailAddress($customer->getEmail()) + ->setCustomerId($customerId) + ->setSuccessUrl($homeUrl . '?success') + ->setFailedUrl($homeUrl . '?fail'); + + $transactionService = $settings->getApiClient()->getTransactionService(); + $transaction = $transactionService->create($settings->getSpaceId(), $transactionPayload); + $transactionId = $transaction->getId(); + $_SESSION['transactionId'] = $transactionId; + } + + return $transactionId; + } + + /** + * @param SalesChannelContext $salesChannelContext + * @param int $transactionId + * @return void + */ + public function updateTempTransaction(SalesChannelContext $salesChannelContext, int $transactionId): void + { + $pendingTransaction = new TransactionPending(); + $pendingTransaction->setId($transactionId); + + $settings = $this->settingsService->getValidSettings($salesChannelContext->getSalesChannel()->getId()); + $transaction = $settings->getApiClient()->getTransactionService()->read($settings->getSpaceId(), $transactionId); + $pendingTransaction->setVersion($transaction->getVersion()); + + $customerBillingAddress = $salesChannelContext->getCustomer()->getActiveBillingAddress(); + + $billingAddress = new AddressCreate(); + $billingAddress->setStreet($customerBillingAddress->getStreet()); + $billingAddress->setCity($customerBillingAddress->getCity()); + $billingAddress->setCountry($customerBillingAddress->getCountry()->getIso()); + $billingAddress->setPostCode($customerBillingAddress->getZipcode()); + + $postalState = $customerBillingAddress?->getCountryState()?->getName() ?? ''; + if (empty($postalState)) { + $postalState = $customerBillingAddress?->getCountryState()?->getShortCode() ?? ''; + } + + $billingAddress->setPostalState($postalState); + $billingAddress->setOrganizationName($customerBillingAddress->getCompany()); + + $currency = $salesChannelContext->getCurrency()->getIsoCode(); + $pendingTransaction->setCurrency($currency); + $pendingTransaction->setBillingAddress($billingAddress); + + $settings->getApiClient()->getTransactionService() + ->update($settings->getSpaceId(), $pendingTransaction); + } + + /** + * @param ChargeAttempt|null $chargeAttempt + * @param string $descriptorKey + * @return array + */ + private function getChargeAttemptAdditionalData(?ChargeAttempt $chargeAttempt, string $descriptorKey): array + { + if (!$chargeAttempt) { + return []; + } + + $labels = $chargeAttempt->getLabels() ?? []; + + if (empty($labels)) { + return []; + } + + foreach ($labels as $label) { + $descriptor = $label->getDescriptor(); + if ((string)$descriptor->getId() !== $descriptorKey) { + continue; + } + + switch ($descriptorKey) { + case self::CARD_HOLDER_KEY: + return [$label->getContentAsString()]; + + case self::PSEUDO_CODE_KEY: + return [$label->getContentAsString()]; + + case self::PAY_ID_KEY: + return [$label->getContentAsString()]; + + case self::ADDITIONAL_TRANSACTION_DETAILS_ORDER_ID_KEY: + return [$label->getContentAsString()]; + + case self::CARD_VALIDITY_KEY: + $validityYear = ''; + $validityMonth = ''; + foreach ($label->getContent() as $cardValidityItem) { + if (strlen((string)$cardValidityItem) === 1 || strlen((string)$cardValidityItem) === 2) { + $validityMonth = $cardValidityItem; + } elseif (strlen((string)$cardValidityItem) === 4) { + $validityYear = $cardValidityItem; + } + } + + if (empty($validityYear) || empty($validityMonth)) { + return []; + } + + return [ + 'cardExpireMonth' => $validityMonth, + 'cardExpireYear' => $validityYear, + ]; + } + } + + return []; + } + + /** + * @param string $salesChannelId + * @param int $transactionId + * @return ChargeAttempt|null + */ + private function getChargeAttempt(string $salesChannelId, int $transactionId): ?ChargeAttempt + { + /** @noinspection PhpParamsInspection */ + $entityQueryFilter = (new EntityQueryFilter()) + ->setType(EntityQueryFilterType::LEAF) + ->setOperator(CriteriaOperator::EQUALS) + ->setFieldName('charge.transaction') + ->setValue($transactionId); + + $query = (new EntityQuery())->setFilter($entityQueryFilter); + + $settings = $this->settingsService->getSettings($salesChannelId); + + $chargeAttempts = $settings->getApiClient()->getChargeAttemptService()->search($settings->getSpaceId(), $query); + + return $chargeAttempts ? $chargeAttempts[0] : null; + } + + /** + * @param LineItem $productData + * @return LineItemCreate + */ + private function createTempLineItem(LineItem $productData): LineItemCreate + { + $lineItem = new LineItemCreate(); + $lineItem->setName($productData->getLabel()); + $lineItem->setUniqueId($productData->getId()); + $lineItem->setSku($productData->getId()); + $lineItem->setQuantity($productData->getQuantity()); + $lineItem->setAmountIncludingTax($productData->getPrice()->getUnitPrice()); + $lineItem->setType(LineItemType::PRODUCT); + + return $lineItem; + } +} diff --git a/src/Core/Api/WebHooks/Command/WebHooksCommand.php b/src/Core/Api/WebHooks/Command/WebHooksCommand.php new file mode 100644 index 0000000..7292b51 --- /dev/null +++ b/src/Core/Api/WebHooks/Command/WebHooksCommand.php @@ -0,0 +1,62 @@ +webHooksService = $webHooksService; + } + + /** + * @param \Symfony\Component\Console\Input\InputInterface $input + * @param \Symfony\Component\Console\Output\OutputInterface $output + * + * @return int + * @throws \VRPayment\Sdk\ApiException + * @throws \VRPayment\Sdk\Http\ConnectionException + * @throws \VRPayment\Sdk\VersioningException + */ + protected function execute(InputInterface $input, OutputInterface $output): int + { + $output->writeln('Install VRPaymentPayment webhooks...'); + $this->webHooksService->install(); + return 0; + } + + /** + * Configures the current command. + */ + protected function configure() + { + $this->setDescription('Install VRPaymentPayment webhooks.') + ->setHelp('This command installs VRPaymentPayment webhooks.'); + } + +} diff --git a/src/Core/Api/WebHooks/Controller/WebHookController.php b/src/Core/Api/WebHooks/Controller/WebHookController.php new file mode 100644 index 0000000..5658540 --- /dev/null +++ b/src/Core/Api/WebHooks/Controller/WebHookController.php @@ -0,0 +1,727 @@ + ['api']])] +class WebHookController extends AbstractController { + + /** + * @var \Doctrine\DBAL\Connection + */ + protected $connection; + + /** + * @var \Psr\Log\LoggerInterface + */ + protected $logger; + + /** + * @var \VRPaymentPayment\Core\Api\Transaction\Service\OrderMailService + */ + protected $orderMailService; + + /** + * @var \Shopware\Core\Checkout\Order\Aggregate\OrderTransaction\OrderTransactionStateHandler + */ + protected $orderTransactionStateHandler; + + /** + * @var \VRPaymentPayment\Core\Api\PaymentMethodConfiguration\Service\PaymentMethodConfigurationService + */ + protected $paymentMethodConfigurationService; + + /** + * @var \VRPaymentPayment\Core\Settings\Struct\Settings + */ + protected $settings; + + /** + * @var \VRPaymentPayment\Core\Settings\Service\SettingsService + */ + protected $settingsService; + + /** + * @var \VRPaymentPayment\Core\Api\Refund\Service\RefundService + */ + protected $refundService; + + /** + * @var \VRPaymentPayment\Core\Api\Transaction\Service\TransactionService + */ + protected $transactionService; + + /** + * Transaction Final States + * + * @var array + */ + protected $transactionFinalStates = [ + OrderTransactionStates::STATE_CANCELLED, + OrderTransactionStates::STATE_PAID, + OrderTransactionStates::STATE_REFUNDED, + ]; + /** + * Transaction Failed States + * + * @var array + */ + protected $transactionFailedStates = [ + TransactionState::DECLINE, + TransactionState::FAILED, + TransactionState::VOIDED, + ]; + + protected $vrpaymentTransactionSuccessStates = [ + TransactionState::AUTHORIZED, + TransactionState::COMPLETED, + TransactionState::FULFILL, + ]; + + /** + * @var \Shopware\Core\Checkout\Order\OrderEntity + */ + private $orderEntity; + + /** + * @var \Shopware\Core\Checkout\Order\SalesChannel\OrderService + */ + private $orderService; + + /** + * @var \VRPaymentPayment\Core\Api\WebHooks\Strategy\WebHookStrategyManager + */ + private $webHookStrategyManager; + + const LINE_ITEM_TYPE_FEE = 'FEE'; + + /** + * WebHookController constructor. + * + * @param \Doctrine\DBAL\Connection $connection + * @param \Shopware\Core\Checkout\Order\Aggregate\OrderTransaction\OrderTransactionStateHandler $orderTransactionStateHandler + * @param \Shopware\Core\Checkout\Order\SalesChannel\OrderService $orderService + * @param \VRPaymentPayment\Core\Api\PaymentMethodConfiguration\Service\PaymentMethodConfigurationService $paymentMethodConfigurationService + * @param \VRPaymentPayment\Core\Api\Refund\Service\RefundService $refundService + * @param \VRPaymentPayment\Core\Api\Transaction\Service\OrderMailService $orderMailService + * @param \VRPaymentPayment\Core\Api\Transaction\Service\TransactionService $transactionService + * @param \VRPaymentPayment\Core\Settings\Service\SettingsService $settingsService + * @param \VRPaymentPayment\Core\Api\WebHooks\Strategy\WebHookStrategyManager $settingsService + */ + public function __construct( + Connection $connection, + OrderTransactionStateHandler $orderTransactionStateHandler, + OrderService $orderService, + PaymentMethodConfigurationService $paymentMethodConfigurationService, + RefundService $refundService, + OrderMailService $orderMailService, + TransactionService $transactionService, + SettingsService $settingsService, + WebHookStrategyManager $webHookStrategyManager + ) + { + $this->connection = $connection; + $this->orderTransactionStateHandler = $orderTransactionStateHandler; + $this->paymentMethodConfigurationService = $paymentMethodConfigurationService; + $this->refundService = $refundService; + $this->orderMailService = $orderMailService; + $this->transactionService = $transactionService; + $this->settingsService = $settingsService; + $this->orderService = $orderService; + $this->webHookStrategyManager = $webHookStrategyManager; + } + + /** + * @param \Psr\Log\LoggerInterface $logger + * + * @internal + * @required + * + */ + public function setLogger(LoggerInterface $logger): void + { + $this->logger = $logger; + } + + /** + * This is the method VRPayment calls + * + * @param \Symfony\Component\HttpFoundation\Request $request + * @param \Shopware\Core\Framework\Context $context + * @param string $salesChannelId + * + * @return \Symfony\Component\HttpFoundation\JsonResponse|\Symfony\Component\HttpFoundation\Response + */ + #[Route( + path: "/api/_action/vrpayment/webHook/callback/{salesChannelId}", + name: "api.action.vrpayment.webhook.update", + options: ["seo" => false], + defaults: [ + "csrf_protected" => false, + "XmlHttpRequest" => true, + "auth_required" => false, + ], + methods: ["POST"], + )] + public function callback(Request $request, Context $context, string $salesChannelId): Response + { + $status = Response::HTTP_UNPROCESSABLE_ENTITY; + $callBackData = new WebHookRequest(); + try { + // Configuration + $salesChannelId = $salesChannelId == 'null' ? null : $salesChannelId; + $this->settings = $this->settingsService->getSettings($salesChannelId); + $signature = $request->server->get('HTTP_X_SIGNATURE'); + $requestJson = json_decode($request->getContent(), true); + $apiClient = $this->settings->getApiClient(); + $callBackData->assign($requestJson); + + // Handling of payloads without a signature (legacy method). + // Deprecated since 3.0.12 + if (empty($signature)) { + switch ($callBackData->getListenerEntityTechnicalName()) { + case WebHookRequest::PAYMENT_METHOD_CONFIGURATION: + return $this->updatePaymentMethodConfiguration($context, $salesChannelId); + case WebHookRequest::REFUND: + return $this->updateRefund($callBackData, $context); + case WebHookRequest::TRANSACTION: + return $this->updateTransaction($callBackData, $context); + case WebHookRequest::TRANSACTION_INVOICE: + return $this->updateTransactionInvoice($callBackData, $context); + default: + $this->logger->warning(__CLASS__ . ' : ' . __FUNCTION__ . ' : Listener not implemented : ', $callBackData->jsonSerialize()); + } + } + + // Handling of payloads with a valid signature. + // This payload signed has the transaction state + if (!empty($signature) && $apiClient->getWebhookEncryptionService()->isContentValid($signature, $request->getContent())) { + return $this->webHookStrategyManager->process($callBackData, $context, $salesChannelId); + } + + $status = Response::HTTP_OK; + } catch (\Exception $exception) { + $this->logger->critical(__CLASS__ . ' : ' . __FUNCTION__ . ' : ' . $exception->getMessage(), $callBackData->jsonSerialize()); + } + return new JsonResponse(['data' => $callBackData], $status); + } + + /** + * Handle VRPayment Payment Method Configuration callback + * + * @param \Shopware\Core\Framework\Context $context + * @param string $salesChannelId + * + * @return \Symfony\Component\HttpFoundation\Response + * @throws \VRPayment\Sdk\ApiException + * @throws \VRPayment\Sdk\Http\ConnectionException + * @throws \VRPayment\Sdk\VersioningException + * @deprecated 6.1.8 No longer used by internal code and not recommended. + * @see WebHookPaymentMethodConfigurationStrategy + */ + private function updatePaymentMethodConfiguration(Context $context, string $salesChannelId = null): Response + { + $result = $this->paymentMethodConfigurationService->setSalesChannelId($salesChannelId)->synchronize($context); + + return new JsonResponse(['result' => $result]); + } + + /** + * Handle VRPayment Refund callback + * + * @param \VRPaymentPayment\Core\Api\WebHooks\Struct\WebHookRequest $callBackData + * @param \Shopware\Core\Framework\Context $context + * + * @return \Symfony\Component\HttpFoundation\Response + * @deprecated 6.1.8 No longer used by internal code and not recommended. + * @see WebHookRefundStrategy + */ + public function updateRefund(WebHookRequest $callBackData, Context $context): Response + { + $status = Response::HTTP_UNPROCESSABLE_ENTITY; + + try { + /** + * @var \VRPayment\Sdk\Model\Transaction $transaction + */ + $refund = $this->settings->getApiClient()->getRefundService() + ->read($callBackData->getSpaceId(), $callBackData->getEntityId()); + $orderId = $refund->getTransaction()->getMetaData()[TransactionPayload::VRPAYMENT_METADATA_ORDER_ID]; + + if(!empty($orderId)) { + + $this->executeLocked($orderId, $context, function () use ($orderId, $refund, $context) { + + $this->refundService->upsert($refund, $context); + + $orderTransactionId = $refund->getTransaction()->getMetaData()[TransactionPayload::VRPAYMENT_METADATA_ORDER_TRANSACTION_ID]; + $orderTransaction = $this->getOrderTransaction($orderId, $context); + if ( + in_array( + $orderTransaction->getStateMachineState()?->getTechnicalName(), + [ + OrderTransactionStates::STATE_PAID, + OrderTransactionStates::STATE_PARTIALLY_PAID, + ] + ) && + ($refund->getState() == RefundState::SUCCESSFUL) + ) { + if ($refund->getAmount() == $orderTransaction->getAmount()->getTotalPrice()) { + $this->orderTransactionStateHandler->refund($orderTransactionId, $context); + } else { + if ($refund->getAmount() < $orderTransaction->getAmount()->getTotalPrice()) { + $this->orderTransactionStateHandler->refundPartially($orderTransactionId, $context); + } + } + } elseif ($orderTransaction->getStateMachineState()?->getTechnicalName() + === OrderTransactionStates::STATE_PARTIALLY_REFUNDED && + ($refund->getState() == RefundState::SUCCESSFUL) + ) { + $transactionByOrderTransactionId = $this->transactionService->getByOrderTransactionId($orderTransactionId, $context); + $totalRefundedAmount = $this->getTotalRefundedAmount($transactionByOrderTransactionId->getTransactionId(), $context); + if (floatval($orderTransaction->getAmount()->getTotalPrice()) - $totalRefundedAmount <= 0) { + $this->orderTransactionStateHandler->refund($orderTransactionId, $context); + } + } + + }); + } + + $status = Response::HTTP_OK; + } catch (CartException $exception) { + $status = Response::HTTP_OK; + $this->logger->info(__CLASS__ . ' : ' . __FUNCTION__ . ' : ' . $exception->getMessage(), $callBackData->jsonSerialize()); + } catch (IllegalTransitionException $exception) { + $status = Response::HTTP_OK; + $this->logger->info(__CLASS__ . ' : ' . __FUNCTION__ . ' : ' . $exception->getMessage(), $callBackData->jsonSerialize()); + } catch (\Exception $exception) { + $this->logger->critical(__CLASS__ . ' : ' . __FUNCTION__ . ' : ' . $exception->getMessage(), $callBackData->jsonSerialize()); + } + + return new JsonResponse(['data' => $callBackData->jsonSerialize()], $status); + } + + /** + * @param int $transactionId + * @param Context $context + * @return float + */ + private function getTotalRefundedAmount(int $transactionId, Context $context): float + { + $amount = 0; + $refunds = $this->transactionService->getRefundEntityCollectionByTransactionId($transactionId, $context); + foreach ($refunds as $refund) { + $amount += floatval($refund->getData()['amount'] ?? 0); + } + + return (float) (string) $amount; + } + + /** + * @param string $orderId + * @param Context $context + * @param callable $operation + * + * @return mixed + * @throws \Exception + */ + private function executeLocked(string $orderId, Context $context, callable $operation) + { + //$this->connection->setTransactionIsolation(TransactionIsolationLevel::READ_COMMITTED); + //$this->connection->beginTransaction(); + try { + + $data = [ + 'id' => $orderId, + 'vrpayment_lock' => date('Y-m-d H:i:s'), + ]; + + $order = $this->container->get('order.repository')->search(new Criteria([$orderId]), $context)->first(); + + if(empty($order)){ + throw CartException::orderNotFound($orderId); + } + + $this->container->get('order.repository')->upsert([$data], $context); + + $result = $operation(); + + //$this->connection->commit(); + return $result; + } catch (\Exception $exception) { + //$this->connection->rollBack(); + throw $exception; + } + } + + /** + * @param String $orderId + * @param \Shopware\Core\Framework\Context $context + * + * @return OrderTransactionEntity + * @deprecated 6.1.8 No longer used by internal code and not recommended. + * @see WebHookTransactionStrategy + */ + private function getOrderTransaction(String $orderId, Context $context): OrderTransactionEntity + { + return $this->getOrderEntity($orderId, $context)->getTransactions()->last(); + } + + /** + * Get order + * + * @param String $orderId + * @param \Shopware\Core\Framework\Context $context + * + * @return \Shopware\Core\Checkout\Order\OrderEntity + * @deprecated 6.1.8 No longer used by internal code and not recommended. + * @see WebHookTransactionStrategy + */ + private function getOrderEntity(string $orderId, Context $context): OrderEntity + { + if (is_null($this->orderEntity)) { + $criteria = (new Criteria([$orderId])) + ->addAssociations(['deliveries', 'transactions']); + $criteria->getAssociation('transactions') + ->addSorting(new FieldSorting('createdAt', FieldSorting::ASCENDING)); + + try { + $this->orderEntity = $this->container->get('order.repository')->search( + $criteria, + $context + )->first(); + if (is_null($this->orderEntity)) { + throw CartException::orderNotFound($orderId); + } + } catch (\Exception $e) { + throw CartException::orderNotFound($orderId); + } + } + + return $this->orderEntity; + } + + /** + * Handle VRPayment Transaction callback + * + * @param \VRPaymentPayment\Core\Api\WebHooks\Struct\WebHookRequest $callBackData + * @param \Shopware\Core\Framework\Context $context + * + * @return \Symfony\Component\HttpFoundation\Response + * @deprecated 6.1.8 No longer used by internal code and not recommended. + * @see WebHookTransactionStrategy + */ + private function updateTransaction(WebHookRequest $callBackData, Context $context): Response + { + $status = Response::HTTP_UNPROCESSABLE_ENTITY; + + try { + /** + * @var \VRPayment\Sdk\Model\Transaction $transaction + * @var \Shopware\Core\Checkout\Order\OrderEntity $order + */ + $transaction = $this->settings->getApiClient() + ->getTransactionService() + ->read($callBackData->getSpaceId(), $callBackData->getEntityId()); + $orderId = $transaction->getMetaData()[TransactionPayload::VRPAYMENT_METADATA_ORDER_ID]; + if(!empty($orderId) && !$transaction->getParent()) { + $this->executeLocked($orderId, $context, function () use ($orderId, $transaction, $context, $callBackData) { + $this->transactionService->upsert($transaction, $context); + $orderTransactionId = $transaction->getMetaData()[TransactionPayload::VRPAYMENT_METADATA_ORDER_TRANSACTION_ID]; + $orderTransaction = $this->getOrderTransaction($orderId, $context); + $this->logger->info("OrderId: {$orderId} Current state: {$orderTransaction->getStateMachineState()?->getTechnicalName()}"); + + if (!in_array( + $orderTransaction->getStateMachineState()?->getTechnicalName(), + $this->transactionFinalStates + )) { + switch ($transaction->getState()) { + case TransactionState::FAILED: + $this->orderTransactionStateHandler->fail($orderTransactionId, $context); + $this->unholdAndCancelDelivery($orderId, $context); + break; + case TransactionState::DECLINE: + case TransactionState::VOIDED: + $this->orderTransactionStateHandler->cancel($orderTransactionId, $context); + $this->unholdAndCancelDelivery($orderId, $context); + break; + case TransactionState::FULFILL: + $this->unholdDelivery($orderId, $context); + break; + case TransactionState::AUTHORIZED: + $this->orderTransactionStateHandler->process($orderTransactionId, $context); + $this->sendEmail($transaction, $context); + break; + default: + break; + } + } + + }); + } + $status = Response::HTTP_OK; + } catch (CartException $exception) { + $status = Response::HTTP_OK; + $this->logger->info(__CLASS__ . ' : ' . __FUNCTION__ . ' : ' . $exception->getMessage(), $callBackData->jsonSerialize()); + } catch (IllegalTransitionException $exception) { + $status = Response::HTTP_OK; + $this->logger->info(__CLASS__ . ' : ' . __FUNCTION__ . ' : ' . $exception->getMessage(), $callBackData->jsonSerialize()); + } catch (\Exception $exception) { + $this->logger->critical(__CLASS__ . ' : ' . __FUNCTION__ . ' : ' . $exception->getMessage(), $callBackData->jsonSerialize()); + } + + return new JsonResponse(['data' => $callBackData->jsonSerialize()], $status); + } + + /** + * @param \VRPayment\Sdk\Model\Transaction $transaction + * @param \Shopware\Core\Framework\Context $context + * @deprecated 6.1.8 No longer used by internal code and not recommended. + */ + protected function sendEmail(Transaction $transaction, Context $context): void + { + $orderId = $transaction->getMetaData()[TransactionPayload::VRPAYMENT_METADATA_ORDER_ID]; + if ($this->settings->isEmailEnabled() && in_array($transaction->getState(), $this->vrpaymentTransactionSuccessStates)) { + $this->orderMailService->send($orderId, $context); + } + } + + /** + * Handle VRPayment TransactionInvoice callback + * + * @param \VRPaymentPayment\Core\Api\WebHooks\Struct\WebHookRequest $callBackData + * @param \Shopware\Core\Framework\Context $context + * + * @return \Symfony\Component\HttpFoundation\Response + * @deprecated 6.1.8 No longer used by internal code and not recommended. + * @see WebHookTransactionInvoiceStrategy + */ + public function updateTransactionInvoice(WebHookRequest $callBackData, Context $context): Response + { + $status = Response::HTTP_UNPROCESSABLE_ENTITY; + + try { + /** + * @var \VRPayment\Sdk\Model\Transaction $transaction + * @var TransactionInvoice $transactionInvoice + */ + $transactionInvoice = $this->settings->getApiClient()->getTransactionInvoiceService() + ->read($callBackData->getSpaceId(), $callBackData->getEntityId()); + $orderId = $transactionInvoice->getCompletion() + ->getLineItemVersion() + ->getTransaction() + ->getMetaData()[TransactionPayload::VRPAYMENT_METADATA_ORDER_ID]; + if(!empty($orderId)) { + $this->executeLocked($orderId, $context, function () use ($orderId, $transactionInvoice, $context) { + + $orderTransactionId = $transactionInvoice->getCompletion() + ->getLineItemVersion() + ->getTransaction() + ->getMetaData()[TransactionPayload::VRPAYMENT_METADATA_ORDER_TRANSACTION_ID]; + $orderTransaction = $this->getOrderTransaction($orderId, $context); + $this->updatePriceIfAdditionalItemsExist($transactionInvoice, $orderTransaction, $context); + + if (!in_array( + $orderTransaction->getStateMachineState()?->getTechnicalName(), + $this->transactionFinalStates + )) { + switch ($transactionInvoice->getState()) { + case TransactionInvoiceState::DERECOGNIZED: + $this->orderTransactionStateHandler->cancel($orderTransactionId, $context); + break; + case TransactionInvoiceState::NOT_APPLICABLE: + case TransactionInvoiceState::PAID: + $this->orderTransactionStateHandler->paid($orderTransactionId, $context); + $this->unholdDelivery($orderTransactionId, $context); + break; + default: + break; + } + } + }); + } + $status = Response::HTTP_OK; + } catch (CartException $exception) { + $status = Response::HTTP_OK; + $this->logger->info(__CLASS__ . ' : ' . __FUNCTION__ . ' : ' . $exception->getMessage(), $callBackData->jsonSerialize()); + } catch (IllegalTransitionException $exception) { + $status = Response::HTTP_OK; + $this->logger->info(__CLASS__ . ' : ' . __FUNCTION__ . ' : ' . $exception->getMessage(), $callBackData->jsonSerialize()); + } catch (\Exception $exception) { + $this->logger->critical(__CLASS__ . ' : ' . __FUNCTION__ . ' : ' . $exception->getMessage(), $callBackData->jsonSerialize()); + } + + return new JsonResponse(['data' => $callBackData->jsonSerialize()], $status); + } + + /** + * Updates order table field price only if there are additional items added from portal side + * + * @param TransactionInvoice $transactionInvoice + * @param OrderTransactionEntity $orderTransaction + * @param Context $context + * @return void + */ + private function updatePriceIfAdditionalItemsExist(TransactionInvoice $transactionInvoice, OrderTransactionEntity $orderTransaction, Context $context): void + { + $completionLineItems = $transactionInvoice->getCompletion()->getLineItems(); + $lineItems = $transactionInvoice->getLineItems(); + + if (count($completionLineItems) !== count($lineItems)) { + $this->transactionService->updateOrderTotalPriceByInvoiceTotal( + $orderTransaction->getOrderId(), + $transactionInvoice->getOutstandingAmount(), + $context + ); + } + } + + /** + * Hold delivery + * + * @param string $orderId + * @param \Shopware\Core\Framework\Context $context + */ + private function unholdDelivery(string $orderId, Context $context): void + { + try { + $criteria = new Criteria([$orderId]); + $criteria->addAssociation('deliveries.stateMachineState'); + $order = $this->container->get('order.repository') + ->search($criteria, $context) + ->first(); + + if (!$order) { + $this->logger->info('Order not found: ' . $orderId); + return; + } + + /** @var OrderDeliveryEntity|null $orderDelivery */ + $orderDelivery = $order->getDeliveries()?->last(); + + if (null === $orderDelivery) { + $this->logger->info('No deliveries found for order: ' . $orderId); + return; + } + + $orderDeliveryState = $orderDelivery->getStateMachineState(); + if (!$orderDeliveryState) { + $this->logger->info('Order delivery state is null for order: ' . $orderId); + return; + } + + $technicalName = $orderDeliveryState->getTechnicalName(); + $this->logger->info('Order delivery state: ' . $technicalName); + + if ($technicalName !== OrderDeliveryStateHandler::STATE_HOLD) { + $this->logger->info('Order delivery is not on hold, skipping unhold process.'); + return; + } + + /** @var OrderDeliveryStateHandler $orderDeliveryStateHandler */ + $orderDeliveryStateHandler = $this->container->get(OrderDeliveryStateHandler::class); + $orderDeliveryStateHandler->unhold($orderDelivery->getId(), $context); + + $this->logger->info('Successfully unheld order delivery for order: ' . $orderId); + } catch (\Exception $exception) { + $this->logger->error('Error unholding order delivery: ' . $exception->getMessage(), $exception->getTrace()); + } + } + + /** + * Unhold and cancel delivery + * + * @param string $orderId + * @param \Shopware\Core\Framework\Context $context + */ + private function unholdAndCancelDelivery(string $orderId, Context $context): void + { + $order = $this->getOrderEntity($orderId, $context); + try { + $this->orderService->orderStateTransition( + $order->getId(), + StateMachineTransitionActions::ACTION_CANCEL, + new ParameterBag(), + $context + ); + } catch (\Exception $exception) { + $this->logger->info($exception->getMessage(), $exception->getTrace()); + } + + try { + /** + * @var OrderDeliveryStateHandler $orderDeliveryStateHandler + */ + $orderDeliveryStateHandler = $this->container->get(OrderDeliveryStateHandler::class); + /** + * @var OrderDeliveryEntity $orderDelivery + */ + $orderDelivery = $order->getDeliveries()?->last(); + + if (null === $orderDelivery) { + return; + } + if ($orderDelivery->getStateMachineState()?->getTechnicalName() !== OrderDeliveryStateHandler::STATE_HOLD){ + return; + } + $orderDeliveryId = $orderDelivery->getId(); + $orderDeliveryStateHandler->unhold($orderDeliveryId, $context); + $orderDeliveryStateHandler->cancel($orderDeliveryId, $context); + } catch (\Exception $exception) { + $this->logger->info($exception->getMessage(), $exception->getTrace()); + } + } +} diff --git a/src/Core/Api/WebHooks/Service/WebHooksService.php b/src/Core/Api/WebHooks/Service/WebHooksService.php new file mode 100644 index 0000000..8129e99 --- /dev/null +++ b/src/Core/Api/WebHooks/Service/WebHooksService.php @@ -0,0 +1,403 @@ + WebHooksService::TRANSACTION, + 'name' => 'Shopware6::WebHook::Transaction', + 'states' => [ + TransactionState::AUTHORIZED, + TransactionState::COMPLETED, + TransactionState::CONFIRMED, + TransactionState::DECLINE, + TransactionState::FAILED, + TransactionState::FULFILL, + TransactionState::PROCESSING, + TransactionState::VOIDED, + ], + 'notifyEveryChange' => false, + ], + /** + * Transaction Invoice WebHook Entity Id + * + * @link https://www.vr-payment.de//doc/api/webhook-entity/view/1472041816898 + */ + [ + 'id' => WebHooksService::TRANSACTION_INVOICE, + 'name' => 'Shopware6::WebHook::Transaction Invoice', + 'states' => [ + TransactionInvoiceState::NOT_APPLICABLE, + TransactionInvoiceState::PAID, + TransactionInvoiceState::DERECOGNIZED, + ], + 'notifyEveryChange' => false, + ], + /** + * Refund WebHook Entity Id + * + * @link https://www.vr-payment.de//doc/api/webhook-entity/view/1472041839405 + */ + [ + 'id' => WebHooksService::REFUND, + 'name' => 'Shopware6::WebHook::Refund', + 'states' => [ + RefundState::FAILED, + RefundState::SUCCESSFUL, + ], + 'notifyEveryChange' => false, + ], + /** + * Payment Method Configuration Id + * + * @link https://www.vr-payment.de//doc/api/webhook-entity/view/1472041857405 + */ + [ + 'id' => WebHooksService::PAYMENT_METHOD_CONFIGURATION, + 'name' => 'Shopware6::WebHook::Payment Method Configuration', + 'states' => [ + CreationEntityState::ACTIVE, + CreationEntityState::DELETED, + CreationEntityState::DELETING, + CreationEntityState::INACTIVE + ], + 'notifyEveryChange' => true, + ], + + ]; + + /** + * @var \Psr\Log\LoggerInterface + */ + protected $logger; + + /** + * @var ?string $salesChannelId + */ + private $salesChannelId; + + /** + * WebHooksService constructor. + * + * @param \VRPaymentPayment\Core\Settings\Service\SettingsService $settingsService + * @param \Symfony\Component\Routing\RouterInterface $router + */ + public function __construct(SettingsService $settingsService, RouterInterface $router) + { + $this->router = $router; + $this->settingsService = $settingsService; + $this->setWebHookEntitiesConfig(); + } + + /** + * Set webhook configs + */ + protected function setWebHookEntitiesConfig(): void + { + foreach ($this->webHookEntityArrayConfig as $item) { + $this->webHookEntitiesConfig[] = (new Entity()) + ->setId((int) $item['id']) + ->setName($item['name']) + ->setStates($item['states']) + ->setNotifyEveryChange($item['notifyEveryChange']); + } + } + + /** + * @param \Psr\Log\LoggerInterface $logger + * + * @internal + * @required + * + */ + public function setLogger(LoggerInterface $logger): void + { + $this->logger = $logger; + } + + /** + * @return \VRPayment\Sdk\ApiClient + */ + public function getApiClient(): ApiClient + { + return $this->apiClient; + } + + /** + * @param \VRPayment\Sdk\ApiClient $apiClient + * + * @return \VRPaymentPayment\Core\Api\WebHooks\Service\WebHooksService + */ + public function setApiClient(ApiClient $apiClient): WebHooksService + { + $this->apiClient = $apiClient; + return $this; + } + + /** + * @return int + */ + public function getSpaceId(): int + { + return $this->spaceId; + } + + /** + * @param int $spaceId + * + * @return \VRPaymentPayment\Core\Api\WebHooks\Service\WebHooksService + */ + public function setSpaceId(int $spaceId): WebHooksService + { + $this->spaceId = $spaceId; + return $this; + } + + /** + * Install WebHooks + * + * @return array + * @throws \VRPayment\Sdk\ApiException + * @throws \VRPayment\Sdk\Http\ConnectionException + * @throws \VRPayment\Sdk\VersioningException + */ + public function install(): array + { + // Configuration + $settings = $this->settingsService->getSettings($this->getSalesChannelId()); + $this->setSpaceId($settings->getSpaceId())->setApiClient($settings->getApiClient()); + + return $this->installListeners(); + } + + /** + * Get sales channel id + * + * @return string|null + */ + public function getSalesChannelId(): ?string + { + return $this->salesChannelId; + } + + /** + * Set sales channel id + * + * @param string|null $salesChannelId + * + * @return \VRPaymentPayment\Core\Api\WebHooks\Service\WebHooksService + */ + public function setSalesChannelId(?string $salesChannelId = null): WebHooksService + { + $this->salesChannelId = $salesChannelId; + return $this; + } + + /** + * Install Listeners + * + * @return array + */ + protected function installListeners(): array + { + $this->logger->info('Installing webhooks.'); + $returnValue = []; + try { + $webHookUrlId = $this->getOrCreateWebHookUrl()->getId(); + $installedWebHooks = $this->getInstalledWebHookListeners($webHookUrlId); + $webHookEntityIds = array_map(function (WebhookListener $webHook) { + return $webHook->getEntity(); + }, $installedWebHooks); + + + /** + * @var \VRPaymentPayment\Core\Api\WebHooks\Struct\Entity $data + */ + foreach ($this->webHookEntitiesConfig as $data) { + + if (in_array($data->getId(), $webHookEntityIds)) { + continue; + } + + $entity = (new WebhookListenerCreate()) + ->setName($data->getName()) + ->setEntity($data->getId()) + ->setNotifyEveryChange($data->isNotifyEveryChange()) + ->setState(CreationEntityState::CREATE) + ->setEntityStates($data->getStates()) + ->setEnablePayloadSignatureAndState( true ) + ->setUrl($webHookUrlId); + + $returnValue[] = $this->apiClient->getWebhookListenerService()->create($this->spaceId, $entity); + } + } catch (\Exception $exception) { + $this->logger->critical($exception->getTraceAsString()); + return $exception->getTrace(); + } + + return $returnValue; + } + + /** + * Create WebHook URL + * + * @return WebhookUrl + * @throws \VRPayment\Sdk\ApiException + * @throws \VRPayment\Sdk\Http\ConnectionException + * @throws \VRPayment\Sdk\VersioningException + */ + protected function getOrCreateWebHookUrl(): WebhookUrl + { + $url = $this->getWebHookCallBackUrl(); + /** @noinspection PhpParamsInspection */ + $entityQueryFilter = (new EntityQueryFilter()) + ->setType(EntityQueryFilterType::_AND) + ->setChildren([ + $this->getEntityFilter('state', CreationEntityState::ACTIVE), + $this->getEntityFilter('url', $url), + ]); + + $query = (new EntityQuery())->setFilter($entityQueryFilter)->setNumberOfEntities(1); + + $webHookUrls = $this->apiClient->getWebhookUrlService()->search($this->spaceId, $query); + + if (!empty($webHookUrls[0])) { + return $webHookUrls[0]; + } + + /** @noinspection PhpParamsInspection */ + $entity = (new WebhookUrlCreate()) + ->setName('Shopware6::WebHookURL') + ->setUrl($url) + ->setState(CreationEntityState::ACTIVE); + + return $this->apiClient->getWebhookUrlService()->create($this->spaceId, $entity); + } + + /** + * Creates and returns a new entity filter. + * + * @param string $fieldName + * @param $value + * @param string $operator + * + * @return \VRPayment\Sdk\Model\EntityQueryFilter + */ + protected function getEntityFilter(string $fieldName, $value, string $operator = CriteriaOperator::EQUALS): EntityQueryFilter + { + /** @noinspection PhpParamsInspection */ + return (new EntityQueryFilter()) + ->setType(EntityQueryFilterType::LEAF) + ->setOperator($operator) + ->setFieldName($fieldName) + ->setValue($value); + } + + /** + * Get web hook callback url + * + * @return string + */ + protected function getWebHookCallBackUrl(): string + { + return $this->router->generate( + 'api.action.vrpayment.webhook.update', + ['salesChannelId' => $this->getSalesChannelId() ?? 'null',], + UrlGeneratorInterface::ABSOLUTE_URL + ); + } + + /** + * @param int $webHookUrlId + * + * @return array + * @throws \VRPayment\Sdk\ApiException + * @throws \VRPayment\Sdk\Http\ConnectionException + * @throws \VRPayment\Sdk\VersioningException + */ + protected function getInstalledWebHookListeners(int $webHookUrlId): array + { + /** @noinspection PhpParamsInspection */ + $entityQueryFilter = (new EntityQueryFilter()) + ->setType(EntityQueryFilterType::_AND) + ->setChildren([ + $this->getEntityFilter('state', CreationEntityState::ACTIVE), + $this->getEntityFilter('url.id', $webHookUrlId), + ]); + + $query = (new EntityQuery())->setFilter($entityQueryFilter); + + return $this->apiClient->getWebhookListenerService()->search($this->spaceId, $query); + } + +} \ No newline at end of file diff --git a/src/Core/Api/WebHooks/Strategy/WebHookPaymentMethodConfigurationStrategy.php b/src/Core/Api/WebHooks/Strategy/WebHookPaymentMethodConfigurationStrategy.php new file mode 100644 index 0000000..e04327c --- /dev/null +++ b/src/Core/Api/WebHooks/Strategy/WebHookPaymentMethodConfigurationStrategy.php @@ -0,0 +1,47 @@ +paymentMethodConfigurationService + ->setSalesChannelId($this->getSalesChannelId()) + ->synchronize($this->getContext()); + + return new JsonResponse(['result' => $result]); + } +} diff --git a/src/Core/Api/WebHooks/Strategy/WebHookRefundStrategy.php b/src/Core/Api/WebHooks/Strategy/WebHookRefundStrategy.php new file mode 100644 index 0000000..1359aea --- /dev/null +++ b/src/Core/Api/WebHooks/Strategy/WebHookRefundStrategy.php @@ -0,0 +1,180 @@ +settings->getApiClient() + ->getRefundService() + ->read($request->getSpaceId(), $request->getEntityId()); + } + + /** + * @inheritDoc + */ + public function getOrderIdByTransaction($transaction): string + { + /** @var \VRPayment\Sdk\Model\Refund $transaction */ + return $transaction->getTransaction() + ->getMetaData()[TransactionPayload::VRPAYMENT_METADATA_ORDER_ID]; + } + + /** + * Check if the request state is applicable. + * + * This method checks if the state of the transaction from a webhook request + * is one of the predefined applicable states. + * + * @param WebHookRequest $request The webhook request containing the transaction state. + * @return bool Returns true if the state is applicable, false otherwise. + */ + public function isRequestStateApplicable(WebHookRequest $request): bool + { + $applicableStates = [ + RefundState::SUCCESSFUL, + ]; + + return in_array($request->getState(), $applicableStates); + } + + /** + * Processes the incoming webhook request that pertains to manual tasks. + * + * This method activates the manual task service to handle updates based on the data provided + * in the webhook request. It could involve marking tasks as completed, updating their status, or + * initiating sub-processes required as part of the task resolution. + * + * @param WebHookRequest $request The webhook request object containing all necessary data. + * @return Response The method does not return a value but updates the state of manual tasks based on the webhook data. + * @throws Exception Throws an exception if there is a failure in processing the manual task updates. + */ + public function process(WebHookRequest $request): Response + { + return $this->updateRefund($request, $this->getContext()); + } + + /** + * Processes the refund callback for a VRPayment transaction, updating the associated order transaction state based on the refund status. + * This method handles different refund scenarios, including full and partial refunds, and adjusts the order transaction state accordingly. + * It ensures transactional integrity by locking the order record during updates to prevent concurrent modifications. + * Logs the outcome of the operation and any exceptions encountered. + * + * @param WebHookRequest $request The webhook request data encapsulating the refund details. + * @param Context $context Shopware execution context, providing scope for operations like database access. + * + * @return Response Returns a JSON response indicating the outcome of the refund processing. + */ + public function updateRefund(WebHookRequest $request, Context $context): Response + { + $status = Response::HTTP_UNPROCESSABLE_ENTITY; + + try { + $refund = $this->getTransaction($request); + $orderId = $this->getOrderIdByTransaction($refund); + + if(!empty($orderId)) { + $this->executeLocked($orderId, $context, function () use ($orderId, $refund, $context, $request) { + if ($request->getListenerEntityTechnicalName() == WebHookRequest::REFUND && $request->getState() == RefundState::SUCCESSFUL) { + $this->refundService->upsert($refund, $context); + $orderTransactionId = $refund->getTransaction()->getMetaData()[TransactionPayload::VRPAYMENT_METADATA_ORDER_TRANSACTION_ID]; + $orderTransaction = $this->getOrderTransaction($orderId, $context); + + $transactionByOrderTransactionId = $this->transactionService->getByOrderTransactionId($orderTransactionId, $context); + $totalRefundedAmount = $this->getTotalRefundedAmount($transactionByOrderTransactionId->getTransactionId(), $context); + $leftToRefund = floatval($orderTransaction->getAmount()->getTotalPrice()) - $totalRefundedAmount; + if ($leftToRefund > 0) { + $this->orderTransactionStateHandler->refundPartially($orderTransactionId, $context); + } elseif ($leftToRefund === floatval(0)) { // This trick is used, because it's float type and 0 is int + $this->orderTransactionStateHandler->refund($orderTransactionId, $context); + } + } + }); + } + + $status = Response::HTTP_OK; + } catch (CartException $exception) { + $status = Response::HTTP_OK; + $this->logRequest($exception, $request, 'info'); + } catch (IllegalTransitionException $exception) { + $status = Response::HTTP_OK; + $this->logRequest($exception, $request, 'info'); + } catch (\Exception $exception) { + $this->logRequest($exception, $request, 'critical'); + } + + return new JsonResponse(['data' => $request->jsonSerialize()], $status); + } + + /** + * Calculates the total amount refunded for a specific transaction by summing up all refunds associated with it. + * This method queries the database for all refund records related to the transaction and aggregates their amounts. + * It ensures accurate financial calculations that are crucial for adjusting transaction states and reporting. + * + * @param int $transactionId The unique identifier of the transaction for which to calculate the total refunded amount. + * @param Context $context Shopware execution context, providing scope for operations like database access. + * + * @return float The total amount refunded for the specified transaction, converted to a float to ensure precision in calculations. + */ + private function getTotalRefundedAmount(int $transactionId, Context $context): float + { + $amount = 0; + $refunds = $this->transactionService->getRefundEntityCollectionByTransactionId($transactionId, $context); + foreach ($refunds as $refund) { + $amount += floatval($refund->getData()['amount'] ?? 0); + } + + return (float) (string) $amount; + } +} diff --git a/src/Core/Api/WebHooks/Strategy/WebHookStrategyBase.php b/src/Core/Api/WebHooks/Strategy/WebHookStrategyBase.php new file mode 100644 index 0000000..e77e898 --- /dev/null +++ b/src/Core/Api/WebHooks/Strategy/WebHookStrategyBase.php @@ -0,0 +1,498 @@ +connection = $connection; + $this->orderTransactionStateHandler = $orderTransactionStateHandler; + $this->paymentMethodConfigurationService = $paymentMethodConfigurationService; + $this->refundService = $refundService; + $this->orderMailService = $orderMailService; + $this->transactionService = $transactionService; + $this->settingsService = $settingsService; + $this->orderService = $orderService; + $this->container = $container; + $this->logger = $vrpaymentPaymentLogger; + } + + /** + * Sets the context for the current operation. + * + * This method assigns a new context to be used in subsequent operations within this instance. + * Passing a null value clears the current context. + * + * @param Context|null $context The new context to set, or null to clear the existing context. + * @return self Returns this instance to allow for method chaining. + */ + public function setContext(?Context $context): self + { + $this->context = $context; + return $this; + } + + /** + * Get the current context. + * + * This method returns the context that has been set for this instance, which may be used in various operations. + * If no context has been set, it returns null. + * + * @return Context|null The current context if set; otherwise, null. + */ + public function getContext(): ?Context + { + return $this->context; + } + + /** + * Sets the sales channel ID for this instance. + * + * This method updates the sales channel ID. This ID is used in various operations that are specific to a sales channel. + * + * @param string|null $salesChannelId The sales channel ID to be set. + * @return $this Provides a fluent interface by returning itself. + */ + public function setSalesChannelId(?string $salesChannelId): self + { + $this->salesChannelId = $salesChannelId; + return $this; + } + + /** + * Retrieves the current sales channel ID. + * + * This method returns the sales channel ID that has been set for this instance. If no ID has been set, it returns null. + * + * @return string|null The current sales channel ID if set; otherwise, null. + */ + public function getSalesChannelId(): ?string + { + return $this->salesChannelId; + } + + /** + * Updates the settings based on the current sales channel. + * + * This method fetches and applies the settings specific to the sales channel ID currently set for this instance. + * @return $this Provides a fluent interface by returning itself. + */ + public function setCurrentSettingsBySalesChannel(): self + { + $this->settings = $this->getSettingsBySalesChannel($this->getSalesChannelId()); + return $this; + } + + /** + * Get settings for a specific sales channel. + * + * This method accesses settings from the settings service using the provided sales channel ID. It returns configuration settings + * that are specific to the given sales channel. + * + * @param string|null $salesChannelId The ID of the sales channel for which settings are being requested. If null, it may default to system-wide settings or no settings. + * @return Settings The settings object containing configuration details for the specified sales channel. + */ + protected function getSettingsBySalesChannel(?string $salesChannelId): Settings + { + return $this->settingsService->getSettings($salesChannelId); + } + + /** + * Get an order entity based on the order ID. + * + * This method fetches an order entity from the database using the provided order ID and context. If the order entity has not + * been fetched before, it performs a database query to retrieve it and caches it for future use. + * + * @param string $orderId The unique identifier of the order. + * @param Context $context The context of the current operation, including scope and permissions. + * @return OrderEntity The order entity associated with the provided ID. + * @throws CartException If the order cannot be found. + */ + protected function getOrderEntity(string $orderId, Context $context): OrderEntity + { + if (is_null($this->orderEntity)) { + $criteria = (new Criteria([$orderId])) + ->addAssociations(['deliveries', 'transactions']); + $criteria->getAssociation('transactions') + ->addSorting(new FieldSorting('createdAt', FieldSorting::ASCENDING)); + + try { + $this->orderEntity = $this->container + ->get('order.repository') + ->search($criteria, $context) + ->first(); + if (is_null($this->orderEntity)) { + throw CartException::orderNotFound($orderId); + } + } catch (\Exception $e) { + throw CartException::orderNotFound($orderId); + } + } + + return $this->orderEntity; + } + + /** + * Get the last transaction associated with an order. + * + * This method accesses the last transaction of an order based on the provided order ID and context. + * + * @param string $orderId The unique identifier of the order. + * @param Context $context The context of the current operation, including scope and permissions. + * @return OrderTransactionEntity The last transaction entity of the specified order. + */ + protected function getOrderTransaction(String $orderId, Context $context): OrderTransactionEntity + { + return $this->getOrderEntity($orderId, $context)->getTransactions()->last(); + } + + /** + * Unholds the delivery of an order. + * + * This method changes the state of an order's last delivery from 'held' to 'released', allowing further processing like shipping. + * + * @param string $orderId The unique identifier of the order. + * @param Context $context The context of the current operation, including scope and permissions. + */ + /** + * Unholds the delivery of an order. + * + * This method changes the state of an order's last delivery from 'held' to 'released', allowing further processing like shipping. + * + * @param string $orderId The unique identifier of the order. + * @param Context $context The context of the current operation, including scope and permissions. + */ + protected function unholdDelivery(string $orderId, Context $context): void + { + try { + $criteria = new Criteria([$orderId]); + $criteria->addAssociation('deliveries.stateMachineState'); + $order = $this->container->get('order.repository') + ->search($criteria, $context) + ->first(); + + if (!$order) { + $this->logger->info('Order not found: ' . $orderId); + return; + } + + /** @var OrderDeliveryEntity|null $orderDelivery */ + $orderDelivery = $order->getDeliveries()?->last(); + + if (null === $orderDelivery) { + $this->logger->info('No deliveries found for order: ' . $orderId); + return; + } + + $orderDeliveryState = $orderDelivery->getStateMachineState(); + if (!$orderDeliveryState) { + $this->logger->info('Order delivery state is null for order: ' . $orderId); + return; + } + + $technicalName = $orderDeliveryState->getTechnicalName(); + $this->logger->info('Order delivery state: ' . $technicalName); + + if ($technicalName !== OrderDeliveryStateHandler::STATE_HOLD) { + $this->logger->info('Order delivery is not on hold, skipping unhold process.'); + return; + } + + /** @var OrderDeliveryStateHandler $orderDeliveryStateHandler */ + $orderDeliveryStateHandler = $this->container->get(OrderDeliveryStateHandler::class); + $orderDeliveryStateHandler->unhold($orderDelivery->getId(), $context); + + $this->logger->info('Successfully unheld order delivery for order: ' . $orderId); + } catch (\Exception $exception) { + $this->logger->error('Error unholding order delivery: ' . $exception->getMessage(), $exception->getTrace()); + } + } + + /** + * Releases any holds and cancels the delivery of an order. If the order's delivery is not on hold, this method does nothing. + * Any exceptions encountered during the process are logged for debugging. + * + * @param string $orderId The ID of the order to process. + * @param Context $context Shopware execution context for the current operation. + */ + protected function unholdAndCancelDelivery(string $orderId, Context $context): void + { + $order = $this->getOrderEntity($orderId, $context); + try { + $this->orderService->orderStateTransition( + $order->getId(), + StateMachineTransitionActions::ACTION_CANCEL, + new ParameterBag(), + $context + ); + } catch (\Exception $exception) { + $this->logger->info($exception->getMessage(), $exception->getTrace()); + } + + try { + + $orderDeliveryStateHandler = $this->container->get(OrderDeliveryStateHandler::class); + /** @var OrderDeliveryEntity $orderDelivery */ + $orderDelivery = $order->getDeliveries()?->last(); + + if (null === $orderDelivery) { + return; + } + if ($orderDelivery->getStateMachineState()?->getTechnicalName() !== OrderDeliveryStateHandler::STATE_HOLD){ + return; + } + $orderDeliveryId = $orderDelivery->getId(); + $orderDeliveryStateHandler->unhold($orderDeliveryId, $context); + $orderDeliveryStateHandler->cancel($orderDeliveryId, $context); + } catch (\Exception $exception) { + $this->logger->info($exception->getMessage(), $exception->getTrace()); + } + } + + /** + * Executes a locked operation on an order. + * + * This method ensures that the operation on the order is executed in a locked context, preventing other processes from interfering. + * It locks the order, performs the operation, and then commits or rolls back the transaction based on the success of the operation. + * + * @param string $orderId The unique identifier of the order. + * @param Context $context The context of the current operation, including scope and permissions. + * @param callable $operation The operation to execute on the order. + * @return mixed The result of the operation. + * @throws Exception If the operation fails. + */ + protected function executeLocked(string $orderId, Context $context, callable $operation) + { + try { + + $data = [ + 'id' => $orderId, + 'vrpayment_lock' => date('Y-m-d H:i:s'), + ]; + + $order = $this->container->get('order.repository')->search(new Criteria([$orderId]), $context)->first(); + + if(empty($order)){ + throw CartException::orderNotFound($orderId); + } + + $this->container->get('order.repository')->upsert([$data], $context); + + $result = $operation(); + + return $result; + } catch (\Exception $exception) { + throw $exception; + } + } + + /** + * Sends an email based on the transaction state. + * + * This method checks if the transaction state matches any of the successful states and sends an email if enabled in the settings. + * + * @param Transaction $transaction The transaction object containing the state and metadata. + * @param Context $context The context of the current operation, including scope and permissions. + * @param string $orderId The unique identifier of the order associated with the transaction. + */ + protected function sendEmail(Transaction $transaction, Context $context, string $orderId): void + { + $salesChannelId = $this->getSalesChannelId(); + $this->settings = $this->getSettingsBySalesChannel($salesChannelId); + if ($this->settings->isEmailEnabled() + && in_array($transaction->getState(), $this->vrpaymentTransactionSuccessStates)) { + $this->orderMailService->send($orderId, $context); + } + } + + /** + * Logs a message with dynamic retrieval of the class and method names from where it is called, and allows specifying the log level. + * + * This method captures the class and method that called it using a backtrace, which automates the process + * of logging without needing to manually specify the source of the log entry. It enhances error tracking + * and informational logging by providing precise source identification for a variety of log levels. + * + * @param \Throwable $exception The exception to log, providing the error details. + * @param WebHookRequest $request The HTTP request context, used for additional logging data. + * @param string $logLevel The level of the log entry ('info', 'critical', etc.), controlling how the log is processed. + */ + protected function logRequest(\Throwable $exception, WebHookRequest $request, string $logLevel = 'info'): void + { + $class = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2)[1]['class']; + $function = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2)[1]['function']; + $message = $class . ' : ' . $function . ' : ' . $exception->getMessage(); + + switch ($logLevel) { + case 'critical': + $this->logger->critical($message, $request->jsonSerialize()); + break; + case 'debug': + $this->logger->debug($message, $request->jsonSerialize()); + break; + case 'info': + default: + $this->logger->info($message, $request->jsonSerialize()); + break; + } + } +} diff --git a/src/Core/Api/WebHooks/Strategy/WebHookStrategyInterface.php b/src/Core/Api/WebHooks/Strategy/WebHookStrategyInterface.php new file mode 100644 index 0000000..5cc934e --- /dev/null +++ b/src/Core/Api/WebHooks/Strategy/WebHookStrategyInterface.php @@ -0,0 +1,35 @@ +strategies = $strategies; + $this->logger = $vrpaymentPaymentLogger; + } + + /** + * Resolves the appropriate strategy for handling the given webhook request based on webhook type. + * + * This method fetches the webhook entity using the listener entity ID from the request, checks if a corresponding + * strategy exists, and returns the strategy if found. + * + * @param WebHookRequest $request The incoming webhook request. + * @param Context $context The shopware context. + * @param string $salesChannelId The sales channel ID. + * @return WebhookStrategyInterface The strategy to handle the request. + * @throws Exception If no strategy can be resolved. + */ + private function resolveStrategy(WebHookRequest $request, Context $context, ?string $salesChannelId): ?WebHookStrategyInterface + { + // Check if the strategy exists for the retrieved transaction ID. + foreach ($this->strategies as $strategy) { + /** @var WebhookStrategyInterface $strategy */ + if ($strategy->match($request->getListenerEntityId())) { + $strategy + ->setContext($context) + ->setSalesChannelId($salesChannelId) + ->setCurrentSettingsBySalesChannel(); + return $strategy; + } + } + + return null; + } + + /** + * Processes the incoming webhook by delegating to the appropriate strategy. + * + * This method determines the type of the incoming webhook request and uses it + * to look up the corresponding strategy. If a strategy is found, it delegates the + * request processing to that strategy. If no strategy is found for the type, it + * throws an exception. + * + * @param WebHookRequest $request The incoming webhook request object. + * @param Context $context + * @param string $salesChannelId + * @return Response + * @throws Exception If no strategy is available for the webhook type provided in the request. + */ + public function process(WebHookRequest $request, Context $context, ?string $salesChannelId): Response + { + try { + $strategy = $this->resolveStrategy($request, $context, $salesChannelId); + + //If there is no strategy available + if (empty($strategy)) { + $this->logger->warning("No strategy available for the transaction ID: {transactionId}", [ + 'transactionId' => $request->getListenerEntityId(), + ]); + return new JsonResponse(['data' => $request->jsonSerialize()], Response::HTTP_OK); + } + + //This reduces the number of unnecessary api calls. + if (method_exists($strategy, "isRequestStateApplicable") && !$strategy->isRequestStateApplicable($request)) { + return new JsonResponse(['data' => $request->jsonSerialize()], Response::HTTP_OK); + } + + //If the request state applies for current strategy, then it will be processed. + return $strategy->process($request); + } catch ( Exception $e) { + throw $e; + } + } +} diff --git a/src/Core/Api/WebHooks/Strategy/WebHookTransactionInvoiceStrategy.php b/src/Core/Api/WebHooks/Strategy/WebHookTransactionInvoiceStrategy.php new file mode 100644 index 0000000..9053367 --- /dev/null +++ b/src/Core/Api/WebHooks/Strategy/WebHookTransactionInvoiceStrategy.php @@ -0,0 +1,196 @@ +settings->getApiClient() + ->getTransactionInvoiceService() + ->read($request->getSpaceId(), $request->getEntityId()); + } + + /** + * @inheritDoc + */ + public function getOrderIdByTransaction($transaction): string + { + /** @var \VRPayment\Sdk\Model\TransactionInvoice $transaction */ + return $transaction->getCompletion() + ->getLineItemVersion() + ->getTransaction() + ->getMetaData()[TransactionPayload::VRPAYMENT_METADATA_ORDER_ID]; + } + + /** + * Check if the request state is applicable. + * + * This method checks if the state of the transaction from a webhook request + * is one of the predefined applicable states. + * + * @param WebHookRequest $request The webhook request containing the transaction state. + * @return bool Returns true if the state is applicable, false otherwise. + */ + public function isRequestStateApplicable(WebHookRequest $request): bool + { + $applicableStates = [ + TransactionInvoiceState::DERECOGNIZED, + TransactionInvoiceState::NOT_APPLICABLE, + TransactionInvoiceState::PAID, + ]; + + return in_array($request->getState(), $applicableStates); + } + + /** + * Processes the incoming webhook request that pertains to manual tasks. + * + * This method activates the manual task service to handle updates based on the data provided + * in the webhook request. It could involve marking tasks as completed, updating their status, or + * initiating sub-processes required as part of the task resolution. + * + * @param WebHookRequest $request The webhook request object containing all necessary data. + * @return Response The method does not return a value but updates the state of manual tasks based on the webhook data. + * @throws Exception Throws an exception if there is a failure in processing the manual task updates. + */ + public function process(WebHookRequest $request): Response + { + return $this->updateTransactionInvoice($request, $this->getContext()); + } + + /** + * Processes the VRPayment TransactionInvoice webhook request by updating transaction and order states based on the invoice state. + * This method handles the entire lifecycle of the invoice processing within the system, from fetching transaction data, + * locking operations for safety, updating transaction statuses based on invoice changes, and handling order delivery states. + * + * @param WebHookRequest $request The data received from the webhook. + * @param Context $context The context within which this operation is performed, encapsulating scope-specific information like permissions and current store details. + * + * @return Response Returns a JSON response indicating the status of the operation, whether it was successful or resulted in an error. + */ + public function updateTransactionInvoice(WebHookRequest $request, Context $context): Response + { + $status = Response::HTTP_UNPROCESSABLE_ENTITY; + + try { + $transactionInvoice = $this->getTransaction($request); + $orderId = $this->getOrderIdByTransaction($transactionInvoice); + if(!empty($orderId)) { + $this->executeLocked($orderId, $context, function () use ($orderId, $transactionInvoice, $context, $request) { + + $orderTransactionId = $transactionInvoice->getCompletion() + ->getLineItemVersion() + ->getTransaction() + ->getMetaData()[TransactionPayload::VRPAYMENT_METADATA_ORDER_TRANSACTION_ID]; + $orderTransaction = $this->getOrderTransaction($orderId, $context); + $this->updatePriceIfAdditionalItemsExist($transactionInvoice, $orderTransaction, $context); + + if (!in_array( + $orderTransaction->getStateMachineState()?->getTechnicalName(), + $this->transactionFinalStates + )) { + switch ($request->getState()) { + case TransactionInvoiceState::DERECOGNIZED: + $this->orderTransactionStateHandler->cancel($orderTransactionId, $context); + break; + case TransactionInvoiceState::NOT_APPLICABLE: + case TransactionInvoiceState::PAID: + $this->orderTransactionStateHandler->paid($orderTransactionId, $context); + $this->unholdDelivery($orderTransactionId, $context); + break; + default: + break; + } + } + }); + } + $status = Response::HTTP_OK; + } catch (CartException $exception) { + $status = Response::HTTP_OK; + $this->logRequest($exception, $request, 'info'); + } catch (IllegalTransitionException $exception) { + $status = Response::HTTP_OK; + $this->logRequest($exception, $request, 'info'); + } catch (\Exception $exception) { + $this->logRequest($exception, $request, 'critical'); + } + + return new JsonResponse(['data' => $request->jsonSerialize()], $status); + } + + /** + * Updates the order's total price if there are additional items added to the transaction invoice compared to the completion invoice. + * This method checks for discrepancies between the line items listed in the transaction invoice and its completion part, + * adjusting the order's total price to reflect any additional items added on the portal side. + * + * @param TransactionInvoice $transactionInvoice The transaction invoice object containing detailed line items and completion details. + * @param OrderTransactionEntity $orderTransaction The order transaction entity linked to the invoice, used for updating order details. + * @param Context $context The operational context providing settings and environment for the operation. + */ + private function updatePriceIfAdditionalItemsExist( + TransactionInvoice $transactionInvoice, + OrderTransactionEntity $orderTransaction, + Context $context + ): void { + $completionLineItems = $transactionInvoice->getCompletion()->getLineItems(); + $lineItems = $transactionInvoice->getLineItems(); + + if (count($completionLineItems) !== count($lineItems)) { + $this->transactionService->updateOrderTotalPriceByInvoiceTotal( + $orderTransaction->getOrderId(), + $transactionInvoice->getOutstandingAmount(), + $context + ); + } + } +} diff --git a/src/Core/Api/WebHooks/Strategy/WebHookTransactionStrategy.php b/src/Core/Api/WebHooks/Strategy/WebHookTransactionStrategy.php new file mode 100644 index 0000000..a100f45 --- /dev/null +++ b/src/Core/Api/WebHooks/Strategy/WebHookTransactionStrategy.php @@ -0,0 +1,169 @@ +settings->getApiClient() + ->getTransactionService() + ->read($request->getSpaceId(), $request->getEntityId()); + } + + /** + * @inheritDoc + */ + public function getOrderIdByTransaction($transaction): string + { + /** @var \VRPayment\Sdk\Model\Transaction $transaction */ + return $transaction->getMetaData()[TransactionPayload::VRPAYMENT_METADATA_ORDER_ID]; + } + + /** + * Check if the request state is applicable. + * + * This method checks if the state of the transaction from a webhook request + * is one of the predefined applicable states. + * + * @param WebHookRequest $request The webhook request containing the transaction state. + * @return bool Returns true if the state is applicable, false otherwise. + */ + public function isRequestStateApplicable(WebHookRequest $request): bool + { + $applicableStates = [ + TransactionState::FAILED, + TransactionState::DECLINE, + TransactionState::VOIDED, + TransactionState::FULFILL, + TransactionState::AUTHORIZED, + ]; + + return in_array($request->getState(), $applicableStates); + } + + /** + * Process the webhook request. + * + * @param WebHookRequest $request The webhook request object. + * @return Response. + */ + public function process(WebHookRequest $request): Response + { + return $this->updateTransaction($request, $this->getContext()); + } + + /** + * Handles the processing of webhook callbacks related to VRPayment transactions. + * This method updates or handles transaction states based on the webhook data received. + * + * @param WebHookRequest $request The data received from the webhook, encapsulating the transaction details. + * @param Context $context The operational context providing settings and environment for transaction processing. + * @return Response Returns a JSON response indicating the result of the transaction update operation. + */ + private function updateTransaction(WebHookRequest $request, Context $context): Response + { + $status = Response::HTTP_UNPROCESSABLE_ENTITY; + + try { + /** @var \Shopware\Core\Checkout\Order\OrderEntity $order */ + $transaction = $this->getTransaction($request); + $orderId = $transaction->getMetaData()[TransactionPayload::VRPAYMENT_METADATA_ORDER_ID]; + if(!empty($orderId) && !$transaction->getParent()) { + $this->executeLocked($orderId, $context, function () use ($orderId, $transaction, $context, $request) { + $this->transactionService->upsert($transaction, $context); + $orderTransactionId = $transaction->getMetaData()[TransactionPayload::VRPAYMENT_METADATA_ORDER_TRANSACTION_ID]; + $orderTransaction = $this->getOrderTransaction($orderId, $context); + $this->logger->info("OrderId: {orderId} Current state: {state}", [ + 'orderId' => $orderId, + 'state' => $orderTransaction->getStateMachineState()?->getTechnicalName(), + ]); + + if (!in_array( + $orderTransaction->getStateMachineState()?->getTechnicalName(), + $this->transactionFinalStates + )) { + switch ($request->getState()) { + case TransactionState::FAILED: + $this->orderTransactionStateHandler->fail($orderTransactionId, $context); + $this->unholdAndCancelDelivery($orderId, $context); + break; + case TransactionState::DECLINE: + case TransactionState::VOIDED: + $this->orderTransactionStateHandler->cancel($orderTransactionId, $context); + $this->unholdAndCancelDelivery($orderId, $context); + break; + case TransactionState::FULFILL: + $this->unholdDelivery($orderId, $context); + break; + case TransactionState::AUTHORIZED: + $this->orderTransactionStateHandler->process($orderTransactionId, $context); + $this->sendEmail($transaction, $context, $orderId); + break; + default: + break; + } + } + + }); + } + $status = Response::HTTP_OK; + } catch (CartException $exception) { + $status = Response::HTTP_OK; + $this->logRequest($exception, $request, 'info'); + } catch (IllegalTransitionException $exception) { + $status = Response::HTTP_OK; + $this->logRequest($exception, $request, 'info'); + } catch (\Exception $exception) { + $this->logRequest($exception, $request, 'critical'); + } + + return new JsonResponse(['data' => $request->jsonSerialize()], $status); + } +} diff --git a/src/Core/Api/WebHooks/Strategy/WebhookStrategyActionsInterface.php b/src/Core/Api/WebHooks/Strategy/WebhookStrategyActionsInterface.php new file mode 100644 index 0000000..a2e25de --- /dev/null +++ b/src/Core/Api/WebHooks/Strategy/WebhookStrategyActionsInterface.php @@ -0,0 +1,61 @@ +id; + } + + /** + * @param int $id + * @return \VRPaymentPayment\Core\Api\WebHooks\Struct\Entity + */ + public function setId(int $id): Entity + { + $this->id = $id; + return $this; + } + + /** + * @return string + */ + public function getName(): string + { + return $this->name; + } + + /** + * @param string $name + * @return \VRPaymentPayment\Core\Api\WebHooks\Struct\Entity + */ + public function setName(string $name): Entity + { + $this->name = $name; + return $this; + } + + /** + * @return array + */ + public function getStates(): array + { + return $this->states; + } + + /** + * @param array $states + * @return \VRPaymentPayment\Core\Api\WebHooks\Struct\Entity + */ + public function setStates(array $states): Entity + { + $this->states = $states; + return $this; + } + + /** + * @return bool + */ + public function isNotifyEveryChange(): bool + { + return $this->notifyEveryChange; + } + + /** + * @param bool $notifyEveryChange + * @return \VRPaymentPayment\Core\Api\WebHooks\Struct\Entity + */ + public function setNotifyEveryChange(bool $notifyEveryChange): Entity + { + $this->notifyEveryChange = $notifyEveryChange; + return $this; + } + + +} diff --git a/src/Core/Api/WebHooks/Struct/WebHookRequest.php b/src/Core/Api/WebHooks/Struct/WebHookRequest.php new file mode 100644 index 0000000..682ce75 --- /dev/null +++ b/src/Core/Api/WebHooks/Struct/WebHookRequest.php @@ -0,0 +1,204 @@ +eventId; + } + + /** + * @param int $eventId + * @return WebHookRequest + */ + public function setEventId(int $eventId): WebHookRequest + { + $this->eventId = $eventId; + return $this; + } + + /** + * @return int + */ + public function getEntityId(): int + { + return $this->entityId; + } + + /** + * @param int $entityId + * @return WebHookRequest + */ + public function setEntityId(int $entityId): WebHookRequest + { + $this->entityId = $entityId; + return $this; + } + + /** + * @return int + */ + public function getListenerEntityId(): int + { + return $this->listenerEntityId; + } + + /** + * @param int $listenerEntityId + * @return WebHookRequest + */ + public function setListenerEntityId(int $listenerEntityId): WebHookRequest + { + $this->listenerEntityId = $listenerEntityId; + return $this; + } + + /** + * @return string + */ + public function getListenerEntityTechnicalName(): string + { + return $this->listenerEntityTechnicalName; + } + + /** + * @param string $listenerEntityTechnicalName + * @return WebHookRequest + */ + public function setListenerEntityTechnicalName(string $listenerEntityTechnicalName): WebHookRequest + { + $this->listenerEntityTechnicalName = $listenerEntityTechnicalName; + return $this; + } + + /** + * @return int + */ + public function getSpaceId(): int + { + return $this->spaceId; + } + + /** + * @param int $spaceId + * @return WebHookRequest + */ + public function setSpaceId(int $spaceId): WebHookRequest + { + $this->spaceId = $spaceId; + return $this; + } + + /** + * @return int + */ + public function getWebhookListenerId(): int + { + return $this->webhookListenerId; + } + + /** + * @param int $webhookListenerId + * @return WebHookRequest + */ + public function setWebhookListenerId(int $webhookListenerId): WebHookRequest + { + $this->webhookListenerId = $webhookListenerId; + return $this; + } + + /** + * @return string + */ + public function getTimestamp(): string + { + return $this->timestamp; + } + + /** + * @param string $timestamp + * @return WebHookRequest + */ + public function setTimestamp(string $timestamp): WebHookRequest + { + $this->timestamp = $timestamp; + return $this; + } + + /** + * @return string + */ + public function getState(): string + { + return $this->state; + } + + /** + * @param string $state + * @return WebHookRequest + */ + public function setState(string $state): WebHookRequest + { + $this->state = $state; + return $this; + } +} \ No newline at end of file diff --git a/src/Core/Checkout/Cart/CustomCartPersister.php b/src/Core/Checkout/Cart/CustomCartPersister.php new file mode 100644 index 0000000..584dd1e --- /dev/null +++ b/src/Core/Checkout/Cart/CustomCartPersister.php @@ -0,0 +1,59 @@ +inner = $inner; + } + + public function delete(string $token, SalesChannelContext $context): void + { + if (!$context->getContext()->hasState('do-cart-delete') && $this->isWhiteLabelPayment($context)) { + return; + } + + $this->inner->delete($token, $context); + } + + public function load(string $token, SalesChannelContext $context): Cart + { + return $this->inner->load($token, $context); + } + + public function save(Cart $cart, SalesChannelContext $context): void + { + $this->inner->save($cart, $context); + } + + public function replace(string $oldToken, string $newToken, SalesChannelContext $context): void + { + $this->inner->replace($oldToken, $newToken, $context); + } + + public function getDecorated(): AbstractCartPersister + { + return $this->inner; + } + + private function isWhiteLabelPayment(SalesChannelContext $context): bool + { + $paymentMethod = $context->getPaymentMethod(); + + if (!$paymentMethod instanceof PaymentMethodEntity) { + return false; + } + + return $paymentMethod->getHandlerIdentifier() === VRPaymentPaymentHandler::class; + } +} diff --git a/src/Core/Checkout/PaymentHandler/VRPaymentPaymentHandler.php b/src/Core/Checkout/PaymentHandler/VRPaymentPaymentHandler.php new file mode 100644 index 0000000..5de412d --- /dev/null +++ b/src/Core/Checkout/PaymentHandler/VRPaymentPaymentHandler.php @@ -0,0 +1,226 @@ +cartPersister = $cartPersister; + $this->transactionService = $transactionService; + $this->orderTransactionStateHandler = $orderTransactionStateHandler; + $this->salesChannelContextService = $salesChannelContextService; + $this->orderTransactionRepository = $orderTransactionRepository; + } + + /** + * @param \Psr\Log\LoggerInterface $logger + * + */ + public function setLogger(LoggerInterface $logger): void + { + $this->logger = $logger; + } + + /** + * The pay function will be called after the customer completed the order. + * Allows to process the order and store additional information. + * + * A redirect to the url will be performed + * + * @param \Symfony\Component\HttpFoundation\Request + * @param \Shopware\Core\Checkout\Payment\Cart\PaymentTransactionStruct $transaction + * @param \Shopware\Core\Framework\Context $context + * @param \Shopware\Core\Framework\Struct\Struct $validateStruct + * @return \Symfony\Component\HttpFoundation\RedirectResponse + */ + public function pay( + Request $request, + PaymentTransactionStruct $transaction, + Context $context, + ?Struct $validateStruct + ): RedirectResponse + { + try { + $orderTransactionId = $transaction->getOrderTransactionId(); + $orderTransaction = $this->orderTransactionRepository->search( + (new Criteria([$orderTransactionId])) + ->addAssociation('order'), $context + )->getEntities()->first(); + + $contextSource = $context->getSource(); + if ($contextSource instanceof SalesChannelApiSource) { + $salesChannelContextId = $contextSource->getSalesChannelId(); + } + + $parameters = new SalesChannelContextServiceParameters($salesChannelContextId, $request->getSession()->get("sw-context-token", Random::getAlphanumericString(32)), originalContext: $context); + $salesChannelContext = $this->salesChannelContextService->get($parameters); + $redirectUrl = $transaction->getReturnUrl(); + + if ($orderTransaction->getOrder()->getAmountTotal() > 0) { + $transactionId = $_SESSION['transactionId'] ?? null; + if ($transactionId === null) { + $this->transactionService->createPendingTransaction($transaction, $salesChannelContext); + } + $redirectUrl = $this->transactionService->create($transaction, $salesChannelContext); + } + return new RedirectResponse($redirectUrl); + + } catch (\Throwable $e) { + unset($_SESSION['transactionId']); + $errorMessage = 'An error occurred during the communication with external payment gateway : ' . $e->getMessage(); + $this->logger->critical($errorMessage); + throw PaymentException::customerCanceled($transaction->getOrderTransaction()->getId(), $errorMessage); + } + } + + /** + * The finalize function will be called when the user is redirected back to shop from the payment gateway. + * + * @param \Symfony\Component\HttpFoundation\Request $request + * @param \Shopware\Core\Checkout\Payment\Cart\PaymentTransactionStruct $transaction + * @param \Shopware\Core\Framework\Context $context + * @throws \VRPayment\Sdk\ApiException + * @throws \VRPayment\Sdk\Http\ConnectionException + * @throws \VRPayment\Sdk\VersioningException + * @throws \Exception when the payment was canceled by the customer + */ + public function finalize( + Request $request, + PaymentTransactionStruct $transaction, + Context $context + ): void + { + $orderTransactionId = $transaction->getOrderTransactionId(); + $orderTransaction = $this->orderTransactionRepository->search( + (new Criteria([$orderTransactionId])) + ->addAssociation('order'), $context + )->getEntities()->first(); + + if ($orderTransaction->getOrder()->getAmountTotal() > 0) { + $transactionEntity = $this->transactionService->getByOrderId( + $orderTransaction->getOrder()->getId(), + $context + ); + + $vRPaymentTransaction = $this->transactionService->read( + $transactionEntity->getTransactionId(), + $transactionEntity->getSalesChannelId() + ); + + if (in_array($vRPaymentTransaction->getState(), [TransactionState::FAILED])) { + $errorMessage = strtr('Customer canceled payment for :orderId on SalesChannel :salesChannelName', [ + ':orderId' => $orderTransaction->getOrder()->getId(), + ':salesChannelName' => $transactionEntity->getSalesChannelId(), + ]); + unset($_SESSION['transactionId']); + $this->logger->info($errorMessage); + throw PaymentException::customerCanceled($transaction->getOrderTransaction()->getId(), $errorMessage); + } + } else { + $this->orderTransactionStateHandler->paid($orderTransaction->getId(), $context); + } + + $token = $request->getSession()->get('sw-context-token'); + if ($token) { + $salesChannelId = $transactionEntity->getSalesChannelId(); + $parameters = new SalesChannelContextServiceParameters($salesChannelId, $token, originalContext: $context); + $salesChannelContext = $this->salesChannelContextService->get($parameters); + + $salesChannelContext->getContext()->addState('do-cart-delete'); + $this->logger->info('Clearing cart with token: ' . $token); + $this->cartPersister->delete($salesChannelContext->getToken(), $salesChannelContext); + } + } + + /** + * {@inheritDoc} + */ + public function supports( + PaymentHandlerType $type, + string $paymentMethodId, + Context $context + ): bool { + if ($type === PaymentHandlerType::RECURRING) { + return false; + } + return true; + } + +} diff --git a/src/Core/Settings/Command/CreateMerchantCommand.php b/src/Core/Settings/Command/CreateMerchantCommand.php new file mode 100644 index 0000000..3f6f100 --- /dev/null +++ b/src/Core/Settings/Command/CreateMerchantCommand.php @@ -0,0 +1,189 @@ +userRepository = $userRepository; + $this->userRoleRepository = $userRoleRepository; + $this->localeRepository = $localeRepository; + } + + /** + * Executes the command to create a new merchant user with a specific role. + * + * @param InputInterface $input + * @param OutputInterface $output + * @return int Command::SUCCESS on success, Command::FAILURE on failure + */ + protected function execute(InputInterface $input, OutputInterface $output): int + { + $output->writeln('Creating VRPaymentPayment merchant with custom role...'); + + $firstName = $input->getOption('firstName'); + $lastName = $input->getOption('lastName'); + $email = $input->getOption('email') ?? 'merchant@merchant.com'; + $password = $input->getOption('password') ?? 'merchant123'; + + $context = Context::createDefaultContext(); + + // Check if user already exists + $criteria = new Criteria(); + $criteria->addFilter(new EqualsFilter('email', $email)); + $existingUser = $this->userRepository->search($criteria, $context)->first(); + + if ($existingUser) { + $output->writeln('User already exists.'); + return Command::SUCCESS; + } + + // Create role if it doesn't exist + $roleId = $this->getOrCreateRoleId('VRPayment viewer', $context); + + // Create user if it doesn't exist + $this->userRepository->create([ + [ + 'id' => Uuid::randomHex(), + 'username' => $email, + 'email' => $email, + 'firstName' => $firstName, + 'lastName' => $lastName, + 'password' => $password, + 'admin' => false, + 'localeId' => $this->getLocaleId($context), + 'aclRoles' => [ + [ + 'id' => $roleId + ] + ], + ] + ], $context); + + $output->writeln('Merchant user created successfully.'); + + return Command::SUCCESS; + } + + /** + * Fetches the default locale ID. + * + * @param Context $context + * @return string Locale ID + * @throws \RuntimeException If the default locale is not found + */ + private function getLocaleId(Context $context): string + { + // Fetch the default locale id + $criteria = new Criteria(); + $criteria->addFilter(new EqualsFilter('code', 'en-GB')); + $localeId = $this->localeRepository->searchIds($criteria, $context)->firstId(); + + if (!$localeId) { + throw new \RuntimeException('Default locale not found'); + } + + return $localeId; + } + + /** + * Fetches the role ID for a given role name or creates the role if it does not exist. + * + * @param string $roleName + * @param Context $context + * @return string Role ID + * @throws \RuntimeException If the role cannot be created or found + */ + private function getOrCreateRoleId(string $roleName, Context $context): string + { + $criteria = new Criteria(); + $criteria->addFilter(new EqualsFilter('name', $roleName)); + $roleId = $this->userRoleRepository->searchIds($criteria, $context)->firstId(); + + if (!$roleId) { + $roleId = Uuid::randomHex(); + $this->userRoleRepository->create([ + [ + 'id' => $roleId, + 'name' => $roleName, + 'privileges' => [ + 'vrpayment.viewer', + 'vrpayment_sales_channel:read', + 'vrpayment_sales_channel_run:read', + 'vrpayment_sales_channel_run_log:read', + 'language:read', + 'locale:read', + 'system_config:read' + ] + ] + ], $context); + } + + return $roleId; + } + + /** + * Configures the current command. + */ + protected function configure(): void + { + $this + ->setDescription('Creates a new merchant user with specific roles.') + ->addOption('firstName', null, InputOption::VALUE_OPTIONAL, 'First name of the merchant user', 'Merchant') + ->addOption('lastName', null, InputOption::VALUE_OPTIONAL, 'Last name of the merchant user', 'Merchant') + ->addOption('email', null, InputOption::VALUE_OPTIONAL, 'Email of the merchant user') + ->addOption('password', null, InputOption::VALUE_OPTIONAL, 'Password of the merchant user'); + } +} diff --git a/src/Core/Settings/Command/SettingsCommand.php b/src/Core/Settings/Command/SettingsCommand.php new file mode 100644 index 0000000..a273f6f --- /dev/null +++ b/src/Core/Settings/Command/SettingsCommand.php @@ -0,0 +1,135 @@ +settingsService = $settingsService; + } + + /** + * @param \Symfony\Component\Console\Input\InputInterface $input + * @param \Symfony\Component\Console\Output\OutputInterface $output + * @return int + */ + protected function execute(InputInterface $input, OutputInterface $output): int + { + $output->writeln('Set VRPaymentPayment settings...'); + $this->settingsService->updateSettings([ + SettingsService::CONFIG_APPLICATION_KEY => $input->getOption(SettingsService::CONFIG_APPLICATION_KEY), + SettingsService::CONFIG_EMAIL_ENABLED => $input->getOption(SettingsService::CONFIG_EMAIL_ENABLED), + SettingsService::CONFIG_INTEGRATION => $input->getOption(SettingsService::CONFIG_INTEGRATION), + SettingsService::CONFIG_LINE_ITEM_CONSISTENCY_ENABLED => $input->getOption(SettingsService::CONFIG_LINE_ITEM_CONSISTENCY_ENABLED), + SettingsService::CONFIG_SPACE_ID => $input->getOption(SettingsService::CONFIG_SPACE_ID), + SettingsService::CONFIG_SPACE_VIEW_ID => $input->getOption(SettingsService::CONFIG_SPACE_VIEW_ID), + SettingsService::CONFIG_STOREFRONT_INVOICE_DOWNLOAD_ENABLED => $input->getOption(SettingsService::CONFIG_STOREFRONT_INVOICE_DOWNLOAD_ENABLED), + SettingsService::CONFIG_USER_ID => $input->getOption(SettingsService::CONFIG_USER_ID), + SettingsService::CONFIG_STOREFRONT_WEBHOOKS_UPDATE_ENABLED => $input->getOption(SettingsService::CONFIG_STOREFRONT_WEBHOOKS_UPDATE_ENABLED), + SettingsService::CONFIG_STOREFRONT_PAYMENTS_UPDATE_ENABLED => $input->getOption(SettingsService::CONFIG_STOREFRONT_PAYMENTS_UPDATE_ENABLED), + ]); + return Command::SUCCESS; + } + + /** + * Configures the current command. + */ + protected function configure() + { + $this->setDescription('Sets VRPaymentPayment settings.') + ->setHelp('This command updates VRPaymentPayment settings for all SalesChannels.') + ->addOption( + SettingsService::CONFIG_APPLICATION_KEY, + SettingsService::CONFIG_APPLICATION_KEY, + InputOption::VALUE_REQUIRED, + SettingsService::CONFIG_APPLICATION_KEY + ) + ->addOption( + SettingsService::CONFIG_SPACE_ID, + SettingsService::CONFIG_SPACE_ID, + InputOption::VALUE_REQUIRED, + SettingsService::CONFIG_SPACE_ID + ) + ->addOption( + SettingsService::CONFIG_USER_ID, + SettingsService::CONFIG_USER_ID, + InputOption::VALUE_REQUIRED, + SettingsService::CONFIG_USER_ID + ) + ->addOption( + SettingsService::CONFIG_EMAIL_ENABLED, + SettingsService::CONFIG_EMAIL_ENABLED, + InputOption::VALUE_OPTIONAL, + SettingsService::CONFIG_EMAIL_ENABLED, + true + ) + ->addOption( + SettingsService::CONFIG_INTEGRATION, + SettingsService::CONFIG_INTEGRATION, + InputOption::VALUE_OPTIONAL, + SettingsService::CONFIG_INTEGRATION, + Integration::PAYMENT_PAGE + ) + ->addOption( + SettingsService::CONFIG_LINE_ITEM_CONSISTENCY_ENABLED, + SettingsService::CONFIG_LINE_ITEM_CONSISTENCY_ENABLED, + InputOption::VALUE_OPTIONAL, + SettingsService::CONFIG_LINE_ITEM_CONSISTENCY_ENABLED, + true + ) + ->addOption( + SettingsService::CONFIG_STOREFRONT_INVOICE_DOWNLOAD_ENABLED, + SettingsService::CONFIG_STOREFRONT_INVOICE_DOWNLOAD_ENABLED, + InputOption::VALUE_OPTIONAL, + SettingsService::CONFIG_STOREFRONT_INVOICE_DOWNLOAD_ENABLED, + true + ) + ->addOption( + SettingsService::CONFIG_SPACE_VIEW_ID, + SettingsService::CONFIG_SPACE_VIEW_ID, + InputOption::VALUE_OPTIONAL, + SettingsService::CONFIG_SPACE_VIEW_ID, + '' + ) + ->addOption( + SettingsService::CONFIG_STOREFRONT_WEBHOOKS_UPDATE_ENABLED, + SettingsService::CONFIG_STOREFRONT_WEBHOOKS_UPDATE_ENABLED, + InputOption::VALUE_OPTIONAL, + SettingsService::CONFIG_STOREFRONT_WEBHOOKS_UPDATE_ENABLED, + true + ) ->addOption( + SettingsService::CONFIG_STOREFRONT_PAYMENTS_UPDATE_ENABLED, + SettingsService::CONFIG_STOREFRONT_PAYMENTS_UPDATE_ENABLED, + InputOption::VALUE_OPTIONAL, + SettingsService::CONFIG_STOREFRONT_PAYMENTS_UPDATE_ENABLED, + true + ); + } +} diff --git a/src/Core/Settings/Options/Integration.php b/src/Core/Settings/Options/Integration.php new file mode 100644 index 0000000..8b53721 --- /dev/null +++ b/src/Core/Settings/Options/Integration.php @@ -0,0 +1,42 @@ +systemConfigService = $systemConfigService; + } + + /** + * @param \Psr\Log\LoggerInterface $logger + * @internal + * @required + * + */ + public function setLogger(LoggerInterface $logger): void + { + $this->logger = $logger; + } + + /** + * Update setting + * + * @param array $settings + * @param string|null $salesChannelId + */ + public function updateSettings(array $settings, ?string $salesChannelId = null): void + { + foreach ($settings as $key => $value) { + $this->systemConfigService->set( + self::SYSTEM_CONFIG_DOMAIN . $key, + $value, + $salesChannelId + ); + } + } + + /** + * Get valid settings + * + * @param string|null $salesChannelId + * @return \VRPaymentPayment\Core\Settings\Struct\Settings|null + */ + public function getValidSettings(?string $salesChannelId = null): ?Settings + { + $settings = $this->getSettings($salesChannelId); + + if (empty($settings->getSpaceId())) { + $this->logger->critical('Empty spaceId setting'); + return null; + } + + if (empty($settings->getUserId())) { + $this->logger->critical('Empty userId setting'); + return null; + } + + if (empty($settings->getIntegration())) { + $this->logger->critical('Empty integration setting'); + return null; + } + + if (empty($settings->getApplicationKey())) { + $this->logger->critical('Empty applicationKey setting'); + return null; + } + + return $settings; + } + + /** + * Get settings + * + * @param string|null $salesChannelId + * @return \VRPaymentPayment\Core\Settings\Struct\Settings + */ + public function getSettings(?string $salesChannelId = null): Settings + { + $values = $this->systemConfigService->getDomain( + self::SYSTEM_CONFIG_DOMAIN, + $salesChannelId, + true + ); + + $propertyValuePairs = []; + + /** @var string $key */ + foreach ($values as $key => $value) { + $property = (string) \mb_substr($key, \mb_strlen(self::SYSTEM_CONFIG_DOMAIN)); + if ($property === '') { + continue; + } + // Space view id is only numeric setting which can be 0. If it is, rest of the loop is skipped. + if ($property === self::CONFIG_SPACE_VIEW_ID && $value === 0) { + $propertyValuePairs[$property] = $value; + continue; + } + // Check if $value is empty and is not in the list of configs which are allowed to be empty + if (empty($value) && !in_array($property, self::ALLOWED_EMPTY_CONFIGS, true)) { + $this->logger->warning(strtr('Empty value :value for settings :property.', [':property' => $property, ':value' => $value])); + } + $propertyValuePairs[$property] = $value; + } + + return (new Settings())->assign($propertyValuePairs); + } +} \ No newline at end of file diff --git a/src/Core/Settings/Struct/Settings.php b/src/Core/Settings/Struct/Settings.php new file mode 100644 index 0000000..a9bc584 --- /dev/null +++ b/src/Core/Settings/Struct/Settings.php @@ -0,0 +1,271 @@ +emailEnabled); + } + + /** + * @param bool $emailEnabled + */ + public function setEmailEnabled(bool $emailEnabled): void + { + $this->emailEnabled = $emailEnabled; + } + + + /** + * @return string + */ + public function getIntegration(): string + { + return strval($this->integration); + } + + /** + * @param string $integration + */ + public function setIntegration(string $integration): void + { + $this->integration = $integration; + } + + /** + * @return bool + */ + public function isLineItemConsistencyEnabled(): bool + { + return boolval($this->lineItemConsistencyEnabled); + } + + /** + * @param bool $lineItemConsistencyEnabled + */ + public function setLineItemConsistencyEnabled(bool $lineItemConsistencyEnabled): void + { + $this->lineItemConsistencyEnabled = $lineItemConsistencyEnabled; + } + + /** + * @return bool + */ + public function isStorefrontInvoiceDownloadEnabled(): bool + { + return boolval($this->storefrontInvoiceDownloadEnabled); + } + + /** + * @param bool $storefrontInvoiceDownloadEnabled + */ + public function setStorefrontInvoiceDownloadEnabled(bool $storefrontInvoiceDownloadEnabled): void + { + $this->storefrontInvoiceDownloadEnabled = $storefrontInvoiceDownloadEnabled; + } + + /** + * @return int + */ + public function getSpaceId(): int + { + return intval($this->spaceId); + } + + /** + * @param int $spaceId + */ + public function setSpaceId(int $spaceId): void + { + $this->spaceId = $spaceId; + } + + /** + * @return int|null + */ + public function getSpaceViewId(): ?int + { + if (!empty($this->spaceViewId) && is_numeric($this->spaceViewId)) { + return intval($this->spaceViewId); + } + + return null; + } + + /** + * @param int $spaceViewId + */ + public function setSpaceViewId(int $spaceViewId): void + { + $this->spaceViewId = $spaceViewId; + } + + /** + * @return bool + */ + public function isWebhooksUpdateEnabled(): bool + { + return boolval($this->webhooksUpdate); + } + + /** + * @param bool $webhooksUpdate + */ + public function setWebhooksEnabled(bool $webhooksUpdate): void + { + $this->webhooksUpdate = $webhooksUpdate; + } + + /** + * @return bool + */ + public function isPaymentsUpdateEnabled(): bool + { + return boolval($this->paymentsUpdate); + } + + /** + * @param bool $paymentsUpdate + */ + public function setPaymentsEnabled(bool $paymentsUpdate): void + { + $this->paymentsUpdate = $paymentsUpdate; + } + + /** + * Get SDK ApiClient + * + * @return \VRPayment\Sdk\ApiClient + */ + public function getApiClient(): ApiClient + { + if (is_null($this->apiClient)) { + $this->apiClient = new ApiClient($this->getUserId(), $this->getApplicationKey()); + $apiClientBasePath = getenv('VRPAYMENT_API_BASE_PATH') ? getenv('VRPAYMENT_API_BASE_PATH') : $this->apiClient->getBasePath(); + $this->apiClient->setBasePath($apiClientBasePath); + Analytics::addHeaders($this->apiClient); + } + return $this->apiClient; + } + + /** + * @return int + */ + public function getUserId(): int + { + return intval($this->userId); + } + + /** + * @param int $userId + */ + public function setUserId(int $userId): void + { + $this->userId = $userId; + } + + /** + * @return string + */ + public function getApplicationKey(): string + { + return strval($this->applicationKey); + } + + /** + * @param string $applicationKey + */ + public function setApplicationKey(string $applicationKey): void + { + $this->applicationKey = $applicationKey; + } +} diff --git a/src/Core/Storefront/Account/Controller/AccountOrderController.php b/src/Core/Storefront/Account/Controller/AccountOrderController.php new file mode 100644 index 0000000..0978148 --- /dev/null +++ b/src/Core/Storefront/Account/Controller/AccountOrderController.php @@ -0,0 +1,130 @@ + ['storefront']])] +class AccountOrderController extends StorefrontController +{ + /** + * @var \VRPaymentPayment\Core\Settings\Service\SettingsService + */ + protected $settingsService; + + /** + * @var \Psr\Log\LoggerInterface + */ + protected $logger; + + /** + * @var \VRPaymentPayment\Core\Api\Transaction\Service\TransactionService + */ + protected $transactionService; + + /** + * @var RequestStack + */ + protected $requestStack; + + /** + * AccountOrderController constructor. + * @param \VRPaymentPayment\Core\Settings\Service\SettingsService $settingsService + * @param \VRPaymentPayment\Core\Api\Transaction\Service\TransactionService $transactionService + * @param RequestStack $requestStack + */ + public function __construct(SettingsService $settingsService, TransactionService $transactionService, RequestStack $requestStack) + { + $this->settingsService = $settingsService; + $this->transactionService = $transactionService; + $this->requestStack = $requestStack; + } + + /** + * @param \Psr\Log\LoggerInterface $logger + * @internal + * @required + * + */ + public function setLogger(LoggerInterface $logger): void + { + $this->logger = $logger; + } + + /** + * Download invoice document + * + * @param string $orderId + * @param \Shopware\Core\System\SalesChannel\SalesChannelContext $salesChannelContext + * @return \Symfony\Component\HttpFoundation\Response + * + * @throws \VRPayment\Sdk\ApiException + * @throws \VRPayment\Sdk\Http\ConnectionException + * @throws \VRPayment\Sdk\VersioningException + */ + #[Route("/vrpayment/account/order/download/invoice/document/{orderId}", + name: "frontend.vrpayment.account.order.download.invoice.document", + methods: ['GET'])] + public function downloadInvoiceDocument(string $orderId, SalesChannelContext $salesChannelContext): Response + { + $customer = $this->getLoggedInCustomer(); + $settings = $this->settingsService->getSettings($salesChannelContext->getSalesChannel()->getId()); + $transactionEntity = $this->transactionService->getByOrderId($orderId, $salesChannelContext->getContext()); + if (strcasecmp($customer->getCustomerNumber(), $transactionEntity->getData()['customerId']) != 0) { + throw new AccessDeniedException(); + } + $invoiceDocument = $settings->getApiClient()->getTransactionService()->getInvoiceDocument($settings->getSpaceId(), $transactionEntity->getTransactionId()); + $forceDownload = true; + $filename = preg_replace('/[\x00-\x1F\x7F-\xFF]/', '_', $invoiceDocument->getTitle()) . '.pdf'; + $disposition = HeaderUtils::makeDisposition( + $forceDownload ? HeaderUtils::DISPOSITION_ATTACHMENT : HeaderUtils::DISPOSITION_INLINE, + $filename, + $filename + ); + $response = new Response(base64_decode($invoiceDocument->getData())); + $response->headers->set('Content-Type', $invoiceDocument->getMimeType()); + $response->headers->set('Content-Disposition', $disposition); + + return $response; + } + + /** + * @return CustomerEntity + */ + protected function getLoggedInCustomer(): CustomerEntity + { + $request = $this->requestStack->getCurrentRequest(); + + if (!$request) { + throw new CustomerNotLoggedInException(); + } + + $context = $request->attributes->get(PlatformRequest::ATTRIBUTE_SALES_CHANNEL_CONTEXT_OBJECT); + + if ($context && $context->getCustomer() && $context->getCustomer()->getGuest() === false) { + return $context->getCustomer(); + } + + throw new CustomerNotLoggedInException(); + } +} diff --git a/src/Core/Storefront/Account/Subscriber/AccountOrderSubscriber.php b/src/Core/Storefront/Account/Subscriber/AccountOrderSubscriber.php new file mode 100644 index 0000000..aa73301 --- /dev/null +++ b/src/Core/Storefront/Account/Subscriber/AccountOrderSubscriber.php @@ -0,0 +1,83 @@ +settingsService = $settingsService; + } + + /** + * @param \Psr\Log\LoggerInterface $logger + * @internal + * @required + * + */ + public function setLogger(LoggerInterface $logger): void + { + $this->logger = $logger; + } + + /** + * @return array + */ + public static function getSubscribedEvents(): array + { + return [ + AccountOrderPageLoadedEvent::class => ['onAccountOrderPageLoaded', 1], + ]; + } + + + /** + * Pass settings to template + * + * @param \Shopware\Storefront\Page\Account\Order\AccountOrderPageLoadedEvent $event + */ + public function onAccountOrderPageLoaded(AccountOrderPageLoadedEvent $event): void + { + $vrpaymentSettings = new ArrayStruct(); + $vrpaymentSettings->set(SettingsService::CONFIG_STOREFRONT_INVOICE_DOWNLOAD_ENABLED, false); + try { + $settings = $this->settingsService->getValidSettings($event->getSalesChannelContext()->getSalesChannel()->getId()); + if (is_null($settings)) { + $this->logger->notice('Disabling invoice downloads'); + } else { + $vrpaymentSettings->set(SettingsService::CONFIG_STOREFRONT_INVOICE_DOWNLOAD_ENABLED, $settings->isStorefrontInvoiceDownloadEnabled()); + } + + } catch (\Exception $e) { + $this->logger->error($e->getMessage()); + } + + $event->getPage()->addExtension('vrpaymentSettings', $vrpaymentSettings); + } +} \ No newline at end of file diff --git a/src/Core/Storefront/Checkout/Controller/CheckoutController.php b/src/Core/Storefront/Checkout/Controller/CheckoutController.php new file mode 100644 index 0000000..e356c04 --- /dev/null +++ b/src/Core/Storefront/Checkout/Controller/CheckoutController.php @@ -0,0 +1,568 @@ + ['storefront']])] +class CheckoutController extends StorefrontController { + + /** + * @var \Shopware\Storefront\Page\GenericPageLoader + */ + protected $genericLoader; + + /** + * @var \Shopware\Core\Checkout\Cart\SalesChannel\CartService + */ + protected $cartService; + + /** + * @var \VRPaymentPayment\Core\Settings\Service\SettingsService + */ + protected $settingsService; + + /** + * @var \VRPaymentPayment\Core\Settings\Struct\Settings + */ + protected $settings; + + /** + * @var \VRPaymentPayment\Core\Api\Transaction\Service\TransactionService + */ + protected $transactionService; + + /** + * @var \Psr\Log\LoggerInterface + */ + private $logger; + + /** + * @var \Shopware\Core\Checkout\Cart\LineItemFactoryRegistry + */ + private $lineItemFactoryRegistry; + + /** + * @var \Shopware\Core\Checkout\Order\SalesChannel\AbstractOrderRoute + */ + private $orderRoute; + + /** + * PaymentController constructor. + * + * @param \Shopware\Core\Checkout\Cart\LineItemFactoryRegistry $lineItemFactoryRegistry + * @param \Shopware\Core\Checkout\Cart\SalesChannel\CartService $cartService + * @param \VRPaymentPayment\Core\Settings\Service\SettingsService $settingsService + * @param \VRPaymentPayment\Core\Api\Transaction\Service\TransactionService $transactionService + * @param \Shopware\Storefront\Page\GenericPageLoaderInterface $genericLoader + * @param \Shopware\Core\Checkout\Order\SalesChannel\AbstractOrderRoute $orderRoute + */ + public function __construct( + LineItemFactoryRegistry $lineItemFactoryRegistry, + CartService $cartService, + SettingsService $settingsService, + TransactionService $transactionService, + GenericPageLoaderInterface $genericLoader, + AbstractOrderRoute $orderRoute + ) + { + $this->cartService = $cartService; + $this->genericLoader = $genericLoader; + $this->settingsService = $settingsService; + $this->transactionService = $transactionService; + $this->lineItemFactoryRegistry = $lineItemFactoryRegistry; + $this->orderRoute = $orderRoute; + } + + /** + * @param \Psr\Log\LoggerInterface $logger + * + * @internal + * @required + * + */ + public function setLogger(LoggerInterface $logger): void + { + $this->logger = $logger; + } + + /** + * @param \Shopware\Core\System\SalesChannel\SalesChannelContext $salesChannelContext + * @param \Symfony\Component\HttpFoundation\Request $request + * + * @return \Symfony\Component\HttpFoundation\Response + * @throws \VRPayment\Sdk\ApiException + * @throws \VRPayment\Sdk\Http\ConnectionException + * @throws \VRPayment\Sdk\VersioningException + * + */ + #[Route( + path: "/vrpayment/checkout/pay", + name: "frontend.vrpayment.checkout.pay", + options: ["seo" => false], + methods: ["GET"], + )] + public function pay(SalesChannelContext $salesChannelContext, Request $request): Response + { + $orderId = $request->query->get('orderId'); + + if (empty($orderId)) { + throw new MissingRequestParameterException('orderId'); + } + + // Configuration + $this->settings = $this->settingsService->getSettings($salesChannelContext->getSalesChannel()->getId()); + + $transaction = $this->getTransaction($orderId, $salesChannelContext->getContext()); + $recreateCartUrl = $this->generateUrl( + 'frontend.vrpayment.checkout.recreate-cart', + ['orderId' => $orderId,], + UrlGeneratorInterface::ABSOLUTE_URL + ); + + if (in_array( + $transaction->getState(), + [ + TransactionState::AUTHORIZED, + TransactionState::COMPLETED, + TransactionState::FULFILL, + ] + )) { + return $this->redirect($transaction->getSuccessUrl(), Response::HTTP_MOVED_PERMANENTLY); + } else { + if (in_array( + $transaction->getState(), + [ + TransactionState::DECLINE, + TransactionState::FAILED, + TransactionState::VOIDED, + ] + )) { + return $this->redirect($transaction->getFailedUrl(), Response::HTTP_MOVED_PERMANENTLY); + } + } + + $possiblePaymentMethods = $this->settings->getApiClient() + ->getTransactionService() + ->fetchPaymentMethods( + $this->settings->getSpaceId(), + $transaction->getId(), + $this->settings->getIntegration() + ); + + if (empty($possiblePaymentMethods)) { + $this->addFlash('danger', $this->trans('vrpayment.paymentMethod.notAvailable')); + return $this->redirect($recreateCartUrl, Response::HTTP_MOVED_PERMANENTLY); + } + + $javascriptUrl = $this->getTransactionJavaScriptUrl($transaction->getId()); + + // Set Checkout Page Data + $checkoutPageData = (new CheckoutPageData()) + ->setIntegration($this->settings->getIntegration()) + ->setJavascriptUrl($javascriptUrl) + ->setDeviceJavascriptUrl($this->settings->getSpaceId(), Uuid::randomHex()) + ->setTransactionPossiblePaymentMethods($possiblePaymentMethods) + ->setCheckoutUrl($this->generateUrl( + 'frontend.vrpayment.checkout.pay', + ['orderId' => $orderId,], + UrlGeneratorInterface::ABSOLUTE_URL + )) + ->setCartRecreateUrl($recreateCartUrl); + $page = $this->load($request, $salesChannelContext); + $page->addExtension('vRPaymentData', $checkoutPageData); + + return $this->renderStorefront( + '@VRPaymentPayment/storefront/page/checkout/order/vrpayment.html.twig', + ['page' => $page] + ); + } + + /** + * Get transaction Javascript URL + * + * @param int $transactionId + * + * @return string + * @throws \VRPayment\Sdk\ApiException + * @throws \VRPayment\Sdk\Http\ConnectionException + * @throws \VRPayment\Sdk\VersioningException + */ + private function getTransactionJavaScriptUrl(int $transactionId): string + { + $javascriptUrl = ''; + switch ($this->settings->getIntegration()) { + case Integration::IFRAME: + $javascriptUrl = $this->settings->getApiClient()->getTransactionIframeService() + ->javascriptUrl($this->settings->getSpaceId(), $transactionId); + break; + case Integration::LIGHTBOX: + $javascriptUrl = $this->settings->getApiClient()->getTransactionLightboxService() + ->javascriptUrl($this->settings->getSpaceId(), $transactionId); + break; + default: + $this->logger->critical(strtr('invalid integration : :integration', [':integration' => $this->settings->getIntegration()])); + + } + return $javascriptUrl; + } + + /** + * @param $orderId + * @param \Shopware\Core\Framework\Context $context + * + * @return \VRPayment\Sdk\Model\Transaction + * @throws \VRPayment\Sdk\ApiException + * @throws \VRPayment\Sdk\Http\ConnectionException + * @throws \VRPayment\Sdk\VersioningException + */ + private function getTransaction($orderId, Context $context): Transaction + { + $transactionEntity = $this->transactionService->getByOrderId($orderId, $context); + return $this->settings->getApiClient()->getTransactionService()->read($this->settings->getSpaceId(), $transactionEntity->getTransactionId()); + } + + /** + * @param \Symfony\Component\HttpFoundation\Request $request + * @param \Shopware\Core\System\SalesChannel\SalesChannelContext $salesChannelContext + * + * @return \Shopware\Storefront\Page\Checkout\Finish\CheckoutFinishPage + */ + protected function load(Request $request, SalesChannelContext $salesChannelContext): CheckoutFinishPage + { + $page = CheckoutFinishPage::createFrom($this->genericLoader->load($request, $salesChannelContext)); + $page->setOrder($this->getOrder($request, $salesChannelContext)); + + return $page; + } + + + /** + * @param \Symfony\Component\HttpFoundation\Request $request + * @param \Shopware\Core\System\SalesChannel\SalesChannelContext $salesChannelContext + * + * @return \Shopware\Core\Checkout\Order\OrderEntity + */ + private function getOrder(Request $request, SalesChannelContext $salesChannelContext): OrderEntity + { + + $orderId = $request->get('orderId'); + if (!$orderId) { + throw new MissingRequestParameterException('orderId', '/orderId'); + } + + $criteria = (new Criteria([$orderId])) + ->addAssociation('lineItems.cover') + ->addAssociation('transactions.paymentMethod') + ->addAssociation('deliveries.shippingMethod'); + + $customer = $salesChannelContext->getCustomer(); + if ($customer !== null) { + $criteria = $criteria->addFilter(new EqualsFilter('order.orderCustomer.customerId', $customer->getId())); + } + + $criteria->getAssociation('transactions')->addSorting(new FieldSorting('createdAt')); + + try { + $searchResult = $this->orderRoute + ->load(new Request(), $salesChannelContext, $criteria) + ->getOrders(); + } catch (InvalidUuidException $e) { + throw CartException::orderNotFound($orderId); + } + + /** @var OrderEntity|null $order */ + $order = $searchResult->get($orderId); + + if (!$order) { + throw CartException::orderNotFound($orderId); + } + + return $order; + } + + /** + * Recreate Cart + * + * @param \Symfony\Component\HttpFoundation\Request $request + * @param \Shopware\Core\System\SalesChannel\SalesChannelContext $salesChannelContext + * + * @return \Symfony\Component\HttpFoundation\Response + * + */ + #[Route( + path: "/vrpayment/checkout/recreate-cart", + name: "frontend.vrpayment.checkout.recreate-cart", + options: ["seo" => false], + methods: ["GET"], + )] + public function recreateCart(Request $request, SalesChannelContext $salesChannelContext) + { + $orderId = $request->query->get('orderId'); + + if (empty($orderId)) { + throw new MissingRequestParameterException('orderId'); + } + + // Adoption for Headless Storefronts + $orderRepo = $this->container->get('order.repository'); + $criteria = new Criteria([$orderId]); + + $orderEntity = $orderRepo->search($criteria, $salesChannelContext->getContext())->first(); + + if($orderEntity->getSalesChannelId() !== $salesChannelContext->getSalesChannelId()) { + $this->settings = $this->settingsService->getSettings($orderEntity->getSalesChannelId()); + $trans = $this->getTransaction($orderId, $salesChannelContext->getContext()); + return $this->redirect($trans->getSuccessUrl()); + } + // End Adoption for Headless Storefronts + + try { + $this->cartService->deleteCart($salesChannelContext); + $cart = $this->cartService->createNew($salesChannelContext->getToken()); + + // Configuration + $this->settings = $this->settingsService->getSettings($salesChannelContext->getSalesChannel()->getId()); + $orderEntity = $this->getOrder($request, $salesChannelContext); + $lastTransaction = $orderEntity->getTransactions()->last(); + if ($lastTransaction && !$lastTransaction->getPaymentMethod()->getAfterOrderEnabled()) { + return $this->redirectToRoute('frontend.home.page'); + } + + $transaction = $this->getTransaction($orderId, $salesChannelContext->getContext()); + if (!empty($transaction->getUserFailureMessage())) { + $this->addFlash('danger', $transaction->getUserFailureMessage()); + } + + $orderItems = $orderEntity->getLineItems(); + $hasCustomProducts = $this->hasCustomProducts($orderItems); + + if ($hasCustomProducts === true) { + $cart = $this->addCustomProducts($orderItems, $request, $salesChannelContext); + } + + foreach ($orderItems as $orderLineItemEntity) { + $type = $orderLineItemEntity->getType(); + + if ($type !== CustomProductsLineItemTypes::LINE_ITEM_TYPE_PRODUCT || $orderLineItemEntity->getParentid() !== null) { + continue; + } + + $lineItem = $this->lineItemFactoryRegistry->create([ + 'id' => $orderLineItemEntity->getId(), + 'quantity' => $orderLineItemEntity->getQuantity(), + 'referencedId' => $orderLineItemEntity->getReferencedId(), + 'type' => $type, + ], $salesChannelContext); + + $lineItemPayload = $orderLineItemEntity->getPayload(); + if (!empty($lineItemPayload)) { + $lineItem->setPayload($lineItemPayload); + } + + $cart = $this->cartService->add($cart, $lineItem, $salesChannelContext); + + } + + } catch (\Exception $exception) { + $this->addFlash('danger', $this->trans('error.addToCartError')); + $this->logger->critical($exception->getMessage()); + return $this->redirectToRoute('frontend.home.page'); + } + + return $this->redirectToRoute('frontend.checkout.confirm.page'); + } + + /** + * @param OrderLineItemCollection $orderItems + * + * @return bool + */ + private function hasCustomProducts(OrderLineItemCollection $orderItems): bool + { + foreach ($orderItems as $orderItem) { + if ($orderItem->getType() === CustomProductsLineItemTypes::LINE_ITEM_TYPE_CUSTOMIZED_PRODUCTS) { + return true; + } + } + + return false; + } + + /** + * @param OrderLineItemCollection $orderItems + * @param string $parentId + * + * @return OrderLineItemEntity|null + */ + private function getCustomProduct(OrderLineItemCollection $orderItems, string $parentId): ?OrderLineItemEntity + { + foreach ($orderItems as $orderItem) { + if ($orderItem->getType() === CustomProductsLineItemTypes::LINE_ITEM_TYPE_PRODUCT && $orderItem->getParentId() === $parentId) { + return $orderItem; + } + } + return null; + } + + /** + * @param OrderLineItemCollection $orderItems + * @param string $parentId + * + * @return array + */ + private function getCustomProductOptions(OrderLineItemCollection $orderItems, string $parentId): array + { + $options = []; + foreach ($orderItems as $orderItem) { + if ($orderItem->getType() === CustomProductsLineItemTypes::LINE_ITEM_TYPE_CUSTOMIZED_PRODUCTS_OPTION && $orderItem->getParentId() === $parentId) { + $options[] = $orderItem; + } + } + return $options; + } + + /** + * @param $orderItems + * @param $request + * @param $salesChannelContext + * + * @return Cart + */ + private function addCustomProducts(OrderLineItemCollection $orderItems, Request $request, SalesChannelContext $salesChannelContext): Cart + { + + $cart = $this->cartService->getCart($salesChannelContext->getToken(), $salesChannelContext); + if (!\class_exists('Swag\\CustomizedProducts\\Core\\Checkout\\Cart\\Route\\AddCustomizedProductsToCartRoute')) { + return $cart; + } + + $customProductsService = $this->get('Swag\CustomizedProducts\Core\Checkout\Cart\Route\AddCustomizedProductsToCartRoute'); + + foreach ($orderItems as $orderItem) { + if ($orderItem->getType() !== CustomProductsLineItemTypes::LINE_ITEM_TYPE_CUSTOMIZED_PRODUCTS) { + continue; + } + + $product = $this->getCustomProduct($orderItems, $orderItem->getId()); + $productOptions = $this->getCustomProductOptions($orderItems, $orderItem->getId()); + $optionValues = $this->getOptionValues($productOptions); + + $params = new RequestDataBag([ + 'customized-products-template' => new RequestDataBag([ + 'id' => $orderItem->getReferencedId(), + 'options' => new RequestDataBag($optionValues), + ]), + ]); + + $request->request->add( + [ + 'lineItems' => + [ + $product->getProductId() => + [ + 'quantity' => $orderItem->getQuantity(), + 'id' => $product->getProductId(), + 'type' => CustomProductsLineItemTypes::LINE_ITEM_TYPE_PRODUCT, + 'referencedId' => $product->getReferencedId(), + 'stackable' => $orderItem->getStackable(), + 'removable' => $orderItem->getRemovable(), + ] + ] + ] + ); + + $customProductsService->add($params, $request, $salesChannelContext, $cart); + $cart = $this->cartService->getCart($salesChannelContext->getToken(), $salesChannelContext); + } + + return $cart; + } + + /** + * @param array $productOptions + * + * @return array + */ + private function getOptionValues(array $productOptions): array + { + $optionValues = []; + foreach ($productOptions as $productOption) { + $optionType = $productOption->getPayload()['type'] ?: ''; + + switch ($optionType) { + case CustomProductsLineItemTypes::PRODUCT_OPTION_TYPE_IMAGE_UPLOAD: + case CustomProductsLineItemTypes::PRODUCT_OPTION_TYPE_FILE_UPLOAD: + $media = $productOption->getPayload()['media'] ?: []; + foreach ($media as $mediaItem) { + $optionValues[$productOption->getReferencedId()] = new RequestDataBag([ + 'media' => new RequestDataBag([ + $mediaItem['filename'] => new RequestDataBag([ + 'id' => $mediaItem['mediaId'], + 'filename' => $mediaItem['filename'], + ]), + ]), + ]); + } + break; + + default: + $optionValues[$productOption->getReferencedId()] = new RequestDataBag([ + 'value' => $productOption->getPayload()['value'] ?: '', + ]); + } + } + + return $optionValues; + } +} diff --git a/src/Core/Storefront/Checkout/Struct/CheckoutPageData.php b/src/Core/Storefront/Checkout/Struct/CheckoutPageData.php new file mode 100644 index 0000000..d926d54 --- /dev/null +++ b/src/Core/Storefront/Checkout/Struct/CheckoutPageData.php @@ -0,0 +1,200 @@ +cartRecreateUrl; + } + + /** + * @param string $cartRecreateUrl + * @return CheckoutPageData + */ + public function setCartRecreateUrl(string $cartRecreateUrl): CheckoutPageData + { + $this->cartRecreateUrl = $cartRecreateUrl; + return $this; + } + + /** + * @return string + */ + public function getCheckoutUrl(): string + { + return $this->checkoutUrl; + } + + /** + * @param string $checkoutUrl + * @return CheckoutPageData + */ + public function setCheckoutUrl(string $checkoutUrl): CheckoutPageData + { + $this->checkoutUrl = $checkoutUrl; + return $this; + } + + /** + * @return string + */ + public function getDeviceJavascriptUrl(): string + { + return $this->deviceJavascriptUrl; + } + + + /** + * @param int $spaceId + * @param string $sessionId + * @return \VRPaymentPayment\Core\Storefront\Checkout\Struct\CheckoutPageData + */ + public function setDeviceJavascriptUrl(int $spaceId, string $sessionId): CheckoutPageData + { + $this->deviceJavascriptUrl = strtr('https://gateway.vr-payment.de/s/{spaceId}/payment/device.js?sessionIdentifier={sessionId}', [ + '{spaceId}' => $spaceId, + '{sessionId}' => $sessionId, + ]); + return $this; + } + + /** + * @return string + */ + public function getJavascriptUrl(): string + { + return $this->javascriptUrl; + } + + /** + * JavaScript URL + * + * @param string $javascriptUrl + * @return \VRPaymentPayment\Core\Storefront\Checkout\Struct\CheckoutPageData + */ + public function setJavascriptUrl(string $javascriptUrl): CheckoutPageData + { + $this->javascriptUrl = $javascriptUrl; + return $this; + } + + /** + * @return array + */ + public function getPossiblePaymentMethodsArray(): array + { + return $this->possiblePaymentMethodsArray; + } + + /** + * @param array $possiblePaymentMethodsArray + * @return \VRPaymentPayment\Core\Storefront\Checkout\Struct\CheckoutPageData + */ + public function setPossiblePaymentMethodsArray(array $possiblePaymentMethodsArray): CheckoutPageData + { + $this->possiblePaymentMethodsArray = $possiblePaymentMethodsArray; + return $this; + } + + /** + * @return array + */ + public function getTransactionPossiblePaymentMethods(): array + { + return $this->transactionPossiblePaymentMethods; + } + + /** + * @param array $transactionPossiblePaymentMethods + * @return \VRPaymentPayment\Core\Storefront\Checkout\Struct\CheckoutPageData + */ + public function setTransactionPossiblePaymentMethods(array $transactionPossiblePaymentMethods): CheckoutPageData + { + $this->transactionPossiblePaymentMethods = $transactionPossiblePaymentMethods; + return $this; + } + + /** + * @return string + */ + public function getIntegration(): string + { + return $this->integration; + } + + /** + * @param string $integration + * @return \VRPaymentPayment\Core\Storefront\Checkout\Struct\CheckoutPageData + */ + public function setIntegration(string $integration): CheckoutPageData + { + $this->integration = $integration; + return $this; + } + + /** + * @return string + */ + public function getPaymentMethodId(): string + { + return $this->paymentMethodId; + } + + /** + * Payment method id from Shopware database + * + * @param string $paymentMethodId + * @return \VRPaymentPayment\Core\Storefront\Checkout\Struct\CheckoutPageData + */ + public function setPaymentMethodId(string $paymentMethodId): CheckoutPageData + { + $this->paymentMethodId = $paymentMethodId; + return $this; + } +} \ No newline at end of file diff --git a/src/Core/Storefront/Checkout/Subscriber/CheckoutSubscriber.php b/src/Core/Storefront/Checkout/Subscriber/CheckoutSubscriber.php new file mode 100644 index 0000000..9baf214 --- /dev/null +++ b/src/Core/Storefront/Checkout/Subscriber/CheckoutSubscriber.php @@ -0,0 +1,260 @@ +paymentMethodConfigurationService = $paymentMethodConfigurationService; + $this->transactionService = $transactionService; + $this->settingsService = $settingsService; + $this->paymentMethodUtil = $paymentMethodUtil; + } + + /** + * @param \Psr\Log\LoggerInterface $logger + * + * @internal + * @required + * + */ + public function setLogger(LoggerInterface $logger): void + { + $this->logger = $logger; + } + + /** + * @return array + */ + public static function getSubscribedEvents(): array + { + return [ + CheckoutConfirmPageLoadedEvent::class => ['onConfirmPageLoaded', 1], + MailBeforeValidateEvent::class => ['onMailBeforeValidate', 1], + ]; + } + + /** + * Stop order emails being sent out + * + * @param \Shopware\Core\Content\MailTemplate\Service\Event\MailBeforeValidateEvent $event + */ + public function onMailBeforeValidate(MailBeforeValidateEvent $event): void + { + $templateData = $event->getTemplateData(); + + /** + * @var $order \Shopware\Core\Checkout\Order\OrderEntity + */ + $order = !empty($templateData['order']) && $templateData['order'] instanceof OrderEntity ? $templateData['order'] : null; + + if (!empty($order) && $order->getAmountTotal() > 0) { + + $isVRPaymentEmailSettingEnabled = $this->settingsService->getSettings($order->getSalesChannelId())->isEmailEnabled(); + + if (!$isVRPaymentEmailSettingEnabled) { //setting is disabled + return; + } + + $orderTransactions = $order->getTransactions(); + if (!($orderTransactions instanceof OrderTransactionCollection)) { + return; + } + $orderTransactionLast = $orderTransactions->last(); + if (empty($orderTransactionLast) || empty($orderTransactionLast->getPaymentMethod())) { // no payment method available + return; + } + + $isVRPaymentPM = VRPaymentPaymentHandler::class == $orderTransactionLast->getPaymentMethod()->getHandlerIdentifier(); + if (!$isVRPaymentPM) { // not our payment method + return; + } + + $isOrderTransactionStateOpen = in_array( + $orderTransactionLast->getStateMachineState()->getTechnicalName(), [ + OrderTransactionStates::STATE_OPEN, + OrderTransactionStates::STATE_IN_PROGRESS, + ]); + + if (!$isOrderTransactionStateOpen) { // order payment status is open or in progress + return; + } + } + } + + /** + * @param \Shopware\Storefront\Page\Checkout\Confirm\CheckoutConfirmPageLoadedEvent $event + */ + public function onConfirmPageLoaded(CheckoutConfirmPageLoadedEvent $event): void + { + try { + $salesChannelContext = $event->getSalesChannelContext(); + $settings = $this->settingsService->getValidSettings($salesChannelContext->getSalesChannel()->getId()); + if (is_null($settings)) { + $this->logger->notice('Removing payment methods because settings are invalid'); + $this->removeVRPaymentPaymentMethodFromConfirmPage($event); + } + + $createdTransactionId = $this->transactionService->createPendingTransaction($salesChannelContext, $event); + $this->updateTempTransactionIfNeeded($salesChannelContext, $createdTransactionId); + + $this->getAvailablePaymentMethods($settings, $createdTransactionId); + $this->setPossiblePaymentMethods($settings->getSpaceId(), $event); + } catch (\Exception $e) { + $this->logger->error($e->getMessage()); + $this->removeVRPaymentPaymentMethodFromConfirmPage($event); + } + } + + /** + * @param \Shopware\Storefront\Page\Checkout\Confirm\CheckoutConfirmPageLoadedEvent $event + */ + private function removeVRPaymentPaymentMethodFromConfirmPage(CheckoutConfirmPageLoadedEvent $event): void + { + $paymentMethodCollection = $event->getPage()->getPaymentMethods(); + $paymentMethodIds = $this->paymentMethodUtil->getVRPaymentPaymentMethodIds($event->getContext()); + foreach ($paymentMethodIds as $paymentMethodId) { + $paymentMethodCollection->remove($paymentMethodId); + } + } + + /** + * @param Settings $settings + * @param int $createdTransactionId + * @return void + */ + private function getAvailablePaymentMethods(Settings $settings, int $createdTransactionId): void + { + $transactionService = $settings->getApiClient()->getTransactionService(); + $possiblePaymentMethods = $transactionService->fetchPaymentMethods( + $settings->getSpaceId(), + $createdTransactionId, + $settings->getIntegration() + ); + $arrayOfPossibleMethods = []; + foreach ($possiblePaymentMethods as $possiblePaymentMethod) { + $arrayOfPossibleMethods[] = $possiblePaymentMethod->getid(); + } + $_SESSION['arrayOfPossibleMethods'] = $arrayOfPossibleMethods; + } + + /** + * @param int $spaceId + * @param CheckoutConfirmPageLoadedEvent $event + * @return void + */ + private function setPossiblePaymentMethods(int $spaceId, CheckoutConfirmPageLoadedEvent $event): void + { + $localPaymentMethods = []; + $paymentMethodConfigurations = $this->paymentMethodConfigurationService->getAllPaymentMethodConfigurations($spaceId, $event->getSalesChannelContext()->getContext()); + foreach ($paymentMethodConfigurations as $paymentMethodConfiguration) { + $localPaymentMethods[$paymentMethodConfiguration->getId()] = $paymentMethodConfiguration->getPaymentMethodConfigurationId(); + } + + $paymentMethodCollection = $event->getPage()->getPaymentMethods(); + foreach ($paymentMethodCollection as $paymentMethodCollectionItem) { + $isVRPaymentPM = VRPaymentPaymentHandler::class == $paymentMethodCollectionItem->getHandlerIdentifier(); + if (!$isVRPaymentPM) { + continue; + } + + $paymentMethodConfigurationId = $localPaymentMethods[$paymentMethodCollectionItem->getId()]; + if (!\in_array($paymentMethodConfigurationId, $_SESSION['arrayOfPossibleMethods'])) { + $paymentMethodCollection->remove($paymentMethodCollectionItem->getId()); + } + } + } + + /** + * @param SalesChannelContext $salesChannelContext + * @param int $createdTransactionId + * @return void + */ + private function updateTempTransactionIfNeeded(SalesChannelContext $salesChannelContext, int $createdTransactionId): void + { + $addressCheck = $_SESSION['addressCheck'] ?? null; + $currencyCheck = $_SESSION['currencyCheck'] ?? null; + + $customer = $salesChannelContext->getCustomer(); + $addressHash = md5(json_encode((array)$customer)); + $currency = $salesChannelContext->getCurrency()->getIsoCode(); + if (($addressCheck && $currencyCheck) && $addressCheck !== $addressHash || $currencyCheck !== $currency) { + if ($createdTransactionId) { + $this->transactionService->updateTempTransaction($salesChannelContext, $createdTransactionId); + } + $_SESSION['arrayOfPossibleMethods'] = null; + $_SESSION['addressCheck'] = $addressHash; + $_SESSION['currencyCheck'] = $currency; + } + } +} diff --git a/src/Core/Storefront/Framework/Cookie/VRPaymentCookieProvider.php b/src/Core/Storefront/Framework/Cookie/VRPaymentCookieProvider.php new file mode 100644 index 0000000..5c3b714 --- /dev/null +++ b/src/Core/Storefront/Framework/Cookie/VRPaymentCookieProvider.php @@ -0,0 +1,54 @@ +original = $cookieProvider; + } + + public function getCookieGroups(): array + { + $cookies = $this->original->getCookieGroups(); + + foreach ($cookies as &$cookie) { + if (!\is_array($cookie)) { + continue; + } + + if (!$this->isRequiredCookieGroup($cookie)) { + continue; + } + + if (!\array_key_exists('entries', $cookie)) { + continue; + } + + $cookie['entries'][] = [ + 'snippet_name' => 'vrpayment.cookie.name', + 'cookie' => 'vrpayment-cookie-key', + ]; + } + + return $cookies; + } + + private function isRequiredCookieGroup(array $cookie): bool + { + return (\array_key_exists('isRequired', $cookie) && $cookie['isRequired'] === true) + && (\array_key_exists('snippet_name', $cookie) && $cookie['snippet_name'] === 'cookie.groupRequired'); + } +} \ No newline at end of file diff --git a/src/Core/Util/Analytics/Analytics.php b/src/Core/Util/Analytics/Analytics.php new file mode 100644 index 0000000..474d11c --- /dev/null +++ b/src/Core/Util/Analytics/Analytics.php @@ -0,0 +1,44 @@ + 'shopware', + self::SHOP_SYSTEM_VERSION => '6', + self::SHOP_SYSTEM_AND_VERSION => 'shopware-6', + self::PLUGIN_SYSTEM_VERSION => '7.0.1', + ]; + } + + /** + * @param \VRPayment\Sdk\ApiClient $apiClient + */ + public static function addHeaders(ApiClient &$apiClient) + { + $data = self::getDefaultData(); + foreach ($data as $key => $value) { + $apiClient->addDefaultHeader($key, $value); + } + } +} + + diff --git a/src/Core/Util/Exception/InvalidPayloadException.php b/src/Core/Util/Exception/InvalidPayloadException.php new file mode 100644 index 0000000..5ffe7e3 --- /dev/null +++ b/src/Core/Util/Exception/InvalidPayloadException.php @@ -0,0 +1,9 @@ +container = $container; + $this->translator = $translator; + $this->languageRepository = $this->container->get('language.repository'); + } + + /** + * @param \Psr\Log\LoggerInterface $logger + * + * @internal + * @required + * + */ + public function setLogger(LoggerInterface $logger): void + { + $this->logger = $logger; + } + + /** + * @param \Shopware\Core\Framework\Context $context + * + * @return string + */ + public function getLocaleCodeFromContext(Context $context): string + { + $defaultLocale = self::LOCALE_GREAT_BRITAIN_ENGLISH; + $languageId = $context->getLanguageId(); + /** @var \Shopware\Core\System\Language\LanguageCollection $languageCollection */ + $languageCollection = $this->languageRepository->search( + (new Criteria([$languageId]))->addAssociation('locale'), + $context + )->getEntities(); + + $language = $languageCollection->get($languageId); + if (is_null($language)) { + return $defaultLocale; + } + + return $language->getLocale() ? $language->getLocale()->getCode() : $defaultLocale; + } + + + /** + * @param \Shopware\Core\Framework\Context $context + * + * @return string + */ + public function getDefaultLocaleCode(Context $context): string + { + $defaultLocale = self::LOCALE_GREAT_BRITAIN_ENGLISH; + $languageId = Defaults::LANGUAGE_SYSTEM; + /** @var \Shopware\Core\System\Language\LanguageCollection $languageCollection */ + $languageCollection = $this->languageRepository->search( + (new Criteria([$languageId]))->addAssociation('locale'), + $context + )->getEntities(); + + $language = $languageCollection->get($languageId); + if (is_null($language)) { + return $defaultLocale; + } + + return $language->getLocale() ? $language->getLocale()->getCode() : $defaultLocale; + } + + /** + * Get available translations + * + * @param string $snippet + * @param string $fallback + * @param \Shopware\Core\Framework\Context $context + * + * @return array + */ + public function getAvailableTranslations(string $snippet, string $fallback, Context $context): array + { + $locales = $this->getAvailableLocales($context); + $translations = []; + + foreach ($locales as $locale) { + $translation = $this->translator->trans($snippet, [], null, $locale); + $pattern = '/^vrpayment\./'; + + // there is a bug/lack of documentation on Shopware translations, sometimes the translation does not work + + if (preg_match($pattern, $translation)) { // string not translated + $translation = $this->translator->trans($snippet, [], 'storefront', $locale); + } + + if (preg_match($pattern, $translation)) { // string not translated + $translation = $fallback; + } + + $translations[$locale]['name'] = $translation; + } + + return $translations; + } + + /** + * Get all locales available + * + * @param \Shopware\Core\Framework\Context $context + * + * @return array + */ + public function getAvailableLocales(Context $context): array + { + $availableLanguages = $this->getAvailableLanguages($context); + $locales = array_map(function (LanguageEntity $language) { + return $language->getLocale()->getCode(); + }, + $availableLanguages->jsonSerialize() + ); + $locales[] = $this->getDefaultLocaleCode($context); + $locales[] = self::LOCALE_GERMANY_GERMAN; + $locales[] = self::LOCALE_GREAT_BRITAIN_ENGLISH; + $locales[] = self::LOCALE_FRANCE_FRENCH; + $locales[] = self::LOCALE_ITALY_ITALIAN; + $locales = array_unique($locales); + return $locales; + } + + /** + * Get available languages + * + * @param \Shopware\Core\Framework\Context $context + * + * @return \Shopware\Core\System\Language\LanguageCollection + */ + public function getAvailableLanguages(Context $context): LanguageCollection + { + return $this->languageRepository->search((new Criteria())->addAssociations([ + 'locale', + ]), $context)->getEntities(); + } +} diff --git a/src/Core/Util/Payload/AbstractPayload.php b/src/Core/Util/Payload/AbstractPayload.php new file mode 100644 index 0000000..5b0057b --- /dev/null +++ b/src/Core/Util/Payload/AbstractPayload.php @@ -0,0 +1,52 @@ +logger = $logger; + } + + /** + * Fix string length string to specific length. + * + * @param string $string + * @param int $maxLength + * @return string + */ + protected function fixLength(string $string, int $maxLength): string + { + return mb_substr($string, 0, $maxLength, 'UTF-8'); + } + + /** + * @param $amount + * @param int $precision + * + * @return float + */ + public static function round(float $amount, int $precision = 2): float { + return \round($amount, $precision); + } + +} \ No newline at end of file diff --git a/src/Core/Util/Payload/CustomProducts/CustomProductsLineItemTypes.php b/src/Core/Util/Payload/CustomProducts/CustomProductsLineItemTypes.php new file mode 100644 index 0000000..4c791fc --- /dev/null +++ b/src/Core/Util/Payload/CustomProducts/CustomProductsLineItemTypes.php @@ -0,0 +1,16 @@ +transaction->getOrder() + ->getLineItems() + ->filterByType(CustomProductsLineItemTypes::LINE_ITEM_TYPE_CUSTOMIZED_PRODUCTS_OPTION) + ->filterByProperty('parentId', $shopLineItem->getId()); + $productAttributes = []; + foreach ($customProductsOptions as $option) { + $label = $option->getLabel(); + $value = $this->extractValueFromPayload($option); + + if ($value === null) { + continue; + } + + $lineItemAttributeCreate = (new LineItemAttributeCreate()) + ->setLabel($this->fixLength($label, 512)) + ->setValue($this->fixLength($value, 512)); + + if ($lineItemAttributeCreate->valid()) { + $key = $this->fixLength('option_' . md5($label), 40); + $productAttributes[$key] = $lineItemAttributeCreate; + } else { + $this->logger->critical('LineItemAttributeCreate payload invalid:', $lineItemAttributeCreate->listInvalidProperties()); + throw new InvalidPayloadException('LineItemAttributeCreate payload invalid:' . json_encode($lineItemAttributeCreate->listInvalidProperties())); + } + } + return $productAttributes; + } + + /** + * @param \Shopware\Core\Checkout\Cart\Tax\Struct\CalculatedTaxCollection $calculatedTaxes + * @param string $title + * @param $amount + * + * @return array + */ + public function getCustomProductTaxes(CalculatedTaxCollection $calculatedTaxes, string $title, $amount) + { + $taxes = []; + $sumOfTaxes = $this->getSumOfTaxes($calculatedTaxes); + + foreach ($calculatedTaxes as $calculatedTax) { + $taxRate = ($calculatedTax->getTax() * 100) / ($amount - $sumOfTaxes); + $taxRate = (float) number_format($taxRate, 8, '.', ''); + $tax = (new TaxCreate()) + ->setRate($taxRate) + ->setTitle($this->fixLength($title . ' : ' . $calculatedTax->getTaxRate(), 40)); + + if (!$tax->valid()) { + $this->logger->critical('Tax payload invalid:', $tax->listInvalidProperties()); + throw new InvalidPayloadException('Tax payload invalid:' . json_encode($tax->listInvalidProperties())); + } + + $taxes [] = $tax; + } + + return $taxes; + } + + /** + * Extract Custom Product Attribute value + * + * @param \Shopware\Core\Checkout\Order\Aggregate\OrderLineItem\OrderLineItemEntity $option + * + * @return string|null + */ + protected function extractValueFromPayload(OrderLineItemEntity $option): ?string + { + $payload = $option->getPayload() ?? []; + + $type = $payload['type'] ?? null; + + $value = $payload['value'] ?? 'on'; + + if (!$type) { + return null; + } + + if ($type === CustomProductsLineItemTypes::PRODUCT_OPTION_TYPE_DATETIME) { + return $value ? \date('d.m.Y', \strtotime($value)) : null; + } + + if ($type === CustomProductsLineItemTypes::PRODUCT_OPTION_TYPE_TIMESTAMP) { + return $value ? \date('H:i', \strtotime($value)) : null; + } + + if ($type === CustomProductsLineItemTypes::PRODUCT_OPTION_TYPE_IMAGE_UPLOAD || $type === CustomProductsLineItemTypes::PRODUCT_OPTION_TYPE_FILE_UPLOAD) { + return \implode(', ', \array_column($option->getPayload()['media'] ?? [], 'filename')); + } + + return $value; + } + + /** + * @param CalculatedTaxCollection $calculatedTaxes + * @return float + */ + private function getSumOfTaxes(CalculatedTaxCollection $calculatedTaxes): float + { + $sumOfTaxes = 0; + foreach ($calculatedTaxes as $calculatedTax) { + $sumOfTaxes += $calculatedTax->getTax(); + } + + return $sumOfTaxes; + } +} \ No newline at end of file diff --git a/src/Core/Util/Payload/RefundPayload.php b/src/Core/Util/Payload/RefundPayload.php new file mode 100644 index 0000000..20a762f --- /dev/null +++ b/src/Core/Util/Payload/RefundPayload.php @@ -0,0 +1,171 @@ +findLineItemByUniqueId($transaction['line_items'], $lineItemId); + + if ($lineItem === null) { + $errorMessage = sprintf('Line item doesn\'t exist: %s', $lineItemId); + $this->logger->critical($errorMessage); + throw new InvalidPayloadException($errorMessage); + } + + $price = 0; + + // If refund the whole line item + if ($quantity === 0) { + $quantity = $lineItem['quantity']; + $price = $lineItem['unit_price_including_tax']; + } + + $amount = floatval($quantity * $lineItem['unit_price_including_tax']); + + if ( + ($transaction->getState() == TransactionState::FULFILL) && + ($amount <= floatval($transaction->getAuthorizationAmount())) + ) { + $reduction = new \VRPayment\Sdk\Model\LineItemReductionCreate(); + $reduction->setLineItemUniqueId($lineItem['unique_id']); + $reduction->setQuantityReduction($quantity); + $reduction->setUnitPriceReduction($price); + + $refund = (new RefundCreate()) + ->setReductions([$reduction]) + ->setTransaction($transaction->getId()) + ->setMerchantReference($this->fixLength($transaction->getMerchantReference(), 100)) + ->setExternalId($this->fixLength(uniqid('refund_', true), 100)) + /** @noinspection PhpParamsInspection */ + ->setType(RefundType::MERCHANT_INITIATED_ONLINE); + + if (!$refund->valid()) { + $this->logger->critical('Refund payload invalid:', $refund->listInvalidProperties()); + throw new InvalidPayloadException('Refund payload invalid:' . json_encode($refund->listInvalidProperties())); + } + + return $refund; + } + + return null; + } + + /** + * @param \VRPayment\Sdk\Model\Transaction $transaction + * @param float $amount + * @return \VRPayment\Sdk\Model\RefundCreate|null + * @throws \Exception + */ + public function getByAmount(Transaction $transaction, float $amount): ?RefundCreate + { + if ( + ($transaction->getState() == TransactionState::FULFILL) && + ($amount <= floatval($transaction->getAuthorizationAmount())) + ) { + $refund = (new RefundCreate()) + ->setAmount(self::round($amount)) + ->setTransaction($transaction->getId()) + ->setMerchantReference($this->fixLength($transaction->getMerchantReference(), 100)) + ->setExternalId($this->fixLength(uniqid('refund_', true), 100)) + ->setType(RefundType::MERCHANT_INITIATED_ONLINE); + + if (!$refund->valid()) { + $this->logger->critical('Refund payload invalid:', $refund->listInvalidProperties()); + throw new InvalidPayloadException('Refund payload invalid:' . json_encode($refund->listInvalidProperties())); + } + + return $refund; + } + + return null; + } + + /** + * @param \VRPayment\Sdk\Model\Transaction $transaction + * @param string $lineItemId + * @param int $quantity + * @return \VRPayment\Sdk\Model\RefundCreate|null + * @throws \Exception + */ + public function getForPartial(Transaction $transaction, string $lineItemId, float $amount): ?RefundCreate + { + $lineItem = $this->findLineItemByUniqueId($transaction['line_items'], $lineItemId); + + if ($lineItem === null) { + $errorMessage = sprintf('Line item doesn\'t exist: %s', $lineItemId); + $this->logger->critical($errorMessage); + throw new InvalidPayloadException($errorMessage); + } + + $unitPrice = floatval($lineItem['unit_price_including_tax']); + $quantityAvailable = intval($lineItem['quantity']); + $totalItemAmount = $unitPrice * $quantityAvailable; + + if ( + ($transaction->getState() == TransactionState::FULFILL) && + ($amount <= $totalItemAmount) + ) { + $reduction = new \VRPayment\Sdk\Model\LineItemReductionCreate(); + $reduction->setLineItemUniqueId($lineItemId); + $reduction->setQuantityReduction(0); + $reduction->setUnitPriceReduction($amount / $quantityAvailable); + + $refund = (new RefundCreate()) + ->setReductions([$reduction]) + ->setTransaction($transaction->getId()) + ->setMerchantReference($this->fixLength($transaction->getMerchantReference(), 100)) + ->setExternalId($this->fixLength(uniqid('refund_', true), 100)) + /** @noinspection PhpParamsInspection */ + ->setType(RefundType::MERCHANT_INITIATED_ONLINE); + + if (!$refund->valid()) { + $this->logger->critical('Refund payload invalid:', $refund->listInvalidProperties()); + throw new InvalidPayloadException('Refund payload invalid:' . json_encode($refund->listInvalidProperties())); + } + + return $refund; + } + + return null; + } + + /** + * @param array $lineItems + * @param string $uniqueId + * @return LineItem|null + */ + private function findLineItemByUniqueId(array $lineItems, string $uniqueId): ?LineItem + { + $lineItems = \array_values( + \array_filter($lineItems, function ($item) use ($uniqueId) { + return $item['unique_id'] === $uniqueId; + }) + ); + + return $lineItems[0] ?? null; + } +} diff --git a/src/Core/Util/Payload/TransactionPayload.php b/src/Core/Util/Payload/TransactionPayload.php new file mode 100644 index 0000000..ebc1e14 --- /dev/null +++ b/src/Core/Util/Payload/TransactionPayload.php @@ -0,0 +1,921 @@ +localeCodeProvider = $localeCodeProvider; + $this->salesChannelContext = $salesChannelContext; + $this->settings = $settings; + $this->transaction = $transaction; + $this->container = $container; + $this->translator = $this->container->get('translator'); + $this->orderTransactionRepository = $this->container->get('order_transaction.repository'); + + $criteria = (new Criteria()); + $criteria->addFilter(new EqualsFilter('id', $this->transaction->getOrderTransactionId())); + + $orders = $this->orderTransactionRepository->search($criteria, $this->salesChannelContext->getContext())->getEntities(); + $orderId = $orders->first()->getOrderId(); + + $criteria = new Criteria([$orderId]); + $criteria + ->addAssociation('lineItems') + ->addAssociation('orderCustomer') + ->addAssociation('transactions') + ->addAssociation('currency') + ; + + $this->order = $this->container->get('order.repository')->search($criteria, $this->salesChannelContext->getContext())->getEntities()->first(); + } + + /** + * Get Transaction Payload + * + * @return \VRPayment\Sdk\Model\TransactionPending + * @throws \Exception + */ + public function get(int $version): TransactionPending + { + $customerId = $this->order->getOrderCustomer()->getCustomerId(); + $criteria = new Criteria([$customerId]); + $criteria->addAssociation('activeBillingAddress') + ->addAssociation('activeShippingAddress') + ->addAssociation('activeShippingAddress') + ->addAssociation('defaultBillingAddress') + ->addAssociation('defaultShippingAddress') + ->addAssociation('salutation'); + $customer = $this->container->get('customer.repository')->search($criteria, $this->salesChannelContext->getContext())->getEntities()->first(); + + $lineItems = $this->getLineItems(); + + $billingAddress = $this->getAddressPayload($customer, $customer->getActiveBillingAddress()); + $shippingAddress = $this->getAddressPayload($customer, $customer->getActiveShippingAddress(), false); + + $customerId = null; + $customerName = null; + if ($customer->getGuest() === false) { + $customerId = $customer->getCustomerNumber(); + $customerName = ''; + if ($customer->getGuest() === false) { + $customerId = $customer->getCustomerNumber(); + $customerName = $customer->getSalutation()->getDisplayName() . ' ' . $customer->getFirstName() . ' ' . $customer->getLastName(); + } + } + + $transactionData = [ + 'currency' => $this->order->getCurrency()->getIsoCode(), + 'customer_email_address' => $customer->getEmail(), + 'customer_id' => $customerId, + 'language' => $this->localeCodeProvider->getLocaleCodeFromContext($this->salesChannelContext->getContext()) ?? null, + 'merchant_reference' => $this->fixLength($this->order->getOrderNumber(), 100), + 'meta_data' => [ + self::VRPAYMENT_METADATA_ORDER_ID => $this->order->getId(), + self::VRPAYMENT_METADATA_ORDER_TRANSACTION_ID => $this->order->getTransactions()->first()->getId(), + self::VRPAYMENT_METADATA_SALES_CHANNEL_ID => $this->salesChannelContext->getSalesChannel()->getId(), + self::VRPAYMENT_METADATA_CUSTOMER_NAME => $customerName, + ], + 'shipping_method' => $this->salesChannelContext->getShippingMethod()->getName() ? $this->fixLength($this->salesChannelContext->getShippingMethod()->getName(), 200) : null, + 'space_view_id' => $this->settings->getSpaceViewId() ?? null, + ]; + + // we have to manually check for these additional fields as they might not be active + if (!empty($additionalAddress1 = $customer->getDefaultBillingAddress()->getAdditionalAddressLine1())) { + $transactionData['meta_data']['additionalAddress1'] = $additionalAddress1; + } + + if (!empty($additionalAddress2 = $customer->getDefaultBillingAddress()->getAdditionalAddressLine2())) { + $transactionData['meta_data']['additionalAddress2'] = $additionalAddress2; + } + + if (!empty($this->order->getCustomerComment())) { + $transactionData['meta_data']['customer_comment'] = $this->order->getCustomerComment(); + } + + $vatIds = $customer->getVatIds(); + if (!empty($vatIds)) { + $taxNumber = $vatIds[0]; + $transactionData['meta_data']['taxNumber'] = $taxNumber; + } + + if (!empty($companyDepartment = $customer->getDefaultBillingAddress()->getDepartment())) { + $transactionData['meta_data']['billingCompanyDepartment'] = $companyDepartment; + } + + if (!empty($companyDepartment = $customer->getDefaultShippingAddress()->getDepartment())) { + $transactionData['meta_data']['shippingCompanyDepartment'] = $companyDepartment; + } + + $transactionPayload = (new TransactionPending()) + ->setId($_SESSION['transactionId']) + ->setVersion($version) + ->setBillingAddress($billingAddress) + ->setCurrency($transactionData['currency']) + ->setCustomerEmailAddress($transactionData['customer_email_address']) + ->setCustomerId($transactionData['customer_id']) + ->setLanguage($transactionData['language']) + ->setLineItems($lineItems) + ->setMerchantReference($transactionData['merchant_reference']) + ->setMetaData($transactionData['meta_data']) + ->setShippingAddress($shippingAddress) + ->setShippingMethod($transactionData['shipping_method']); + + $paymentConfiguration = $this->getPaymentConfiguration($this->salesChannelContext->getPaymentMethod()->getId()); + + $transactionPayload->setAllowedPaymentMethodConfigurations([$paymentConfiguration->getPaymentMethodConfigurationId()]); + + $successUrl = $this->transaction->getReturnUrl() . '&status=paid'; + $failedUrl = $this->getFailUrl($this->order->getId()) . '&status=fail'; + $transactionPayload->setSuccessUrl($successUrl) + ->setFailedUrl($failedUrl); + + if (!$transactionPayload->valid()) { + $this->logger->critical('Transaction payload invalid:', $transactionPayload->listInvalidProperties()); + throw new InvalidPayloadException('Transaction payload invalid:' . json_encode($transactionPayload->listInvalidProperties())); + } + + return $transactionPayload; + } + + /** + * Get transaction line items + * + * @return \VRPayment\Sdk\Model\LineItemCreate[] + * @throws \Exception + */ + protected function getLineItems(): array + { + $lineItems = []; + $items = $this->order->getLineItems() ?? []; + + foreach ($items as $shopLineItem) { + if ($this->shouldSkipLineItem($shopLineItem)) { + continue; + } + + if ($this->isCustomProductOption($shopLineItem)) { + $shopLineItem = $this->updateCustomProductOptionLabel($shopLineItem); + } + + $lineItem = $this->createLineItem($shopLineItem); + $this->validateLineItem($lineItem); + + $lineItems[] = $lineItem; + } + + $this->processDiscounts($items, $lineItems); + $this->sortLineItemsByName($lineItems); + + $this->addOptionalLineItems($lineItems); + + return $lineItems; + } + + /** + * Determine if a line item should be skipped. + */ + protected function shouldSkipLineItem($shopLineItem): bool + { + return in_array($shopLineItem->getType(), [ + CustomProductsLineItemTypes::LINE_ITEM_TYPE_CUSTOMIZED_PRODUCTS, + 'promotion' + ]); + } + + /** + * Check if the line item is a custom product option. + */ + protected function isCustomProductOption($shopLineItem): bool + { + return $shopLineItem->getType() === CustomProductsLineItemTypes::LINE_ITEM_TYPE_CUSTOMIZED_PRODUCTS_OPTION; + } + + /** + * Update the label of a custom product option. + */ + protected function updateCustomProductOptionLabel($shopLineItem) + { + $customProductOptionParentLabel = $this->getCustomProductOptionLabel($shopLineItem->getParentId()); + $shopLineItem->setLabel($customProductOptionParentLabel . ': ' . $shopLineItem->getLabel()); + return $shopLineItem; + } + + /** + * Validate the created line item. + */ + protected function validateLineItem($lineItem): void + { + if (!$lineItem->valid()) { + $this->logger->critical('LineItem payload invalid:', $lineItem->listInvalidProperties()); + throw new InvalidPayloadException('LineItem payload invalid: ' . json_encode($lineItem->listInvalidProperties())); + } + } + + /** + * Process discounts from the order items and add them to the line items array. + */ + protected function processDiscounts($items, array &$lineItems): void + { + $itemsArray = is_array($items) ? $items : iterator_to_array($items); + $discounts = array_filter($itemsArray, function ($orderItem) { + return $orderItem->getType() === 'promotion'; + }); + + if ($discounts) { + foreach ($discounts as $discount) { + $this->addDiscountLineItem($discount, $lineItems); + } + } + } + + /** + * Add discount line item. + */ + protected function addDiscountLineItem($discount, array &$lineItems): void + { + $calculatedPrice = $discount->getPrice(); + $discountName = $discount->getLabel() ?? 'Unnamed'; + $definition = $discount->getPriceDefinition(); + + if ($this->order->getTaxStatus() === 'net' || $definition instanceof \Shopware\Core\Checkout\Cart\Price\Struct\AbsolutePriceDefinition) { + $calculatedTaxesCollection = $calculatedPrice->getCalculatedTaxes(); + foreach ($calculatedTaxesCollection as $calculatedTax) { + $rate = $calculatedTax->getTaxRate(); + $amount = $this->calculateDiscountAmount($calculatedTax); + + $lineItems[] = $this->createDiscountLineItem($discountName, $amount, $rate); + } + } else { + $taxRules = $calculatedPrice->getTaxRules(); + + if ($taxRules && $taxRules->count() > 0) { + foreach ($taxRules as $taxRule) { + $rate = $taxRule->getTaxRate(); + $amount = $calculatedPrice->getTotalPrice(); + $lineItems[] = $this->createDiscountLineItem($discountName, $amount, $rate); + } + } else { + $rate = $this->getDefaultTaxRate(); + $amount = $calculatedPrice->getTotalPrice(); + $lineItems[] = $this->createDiscountLineItem($discountName, $amount, $rate); + } + } + } + + /** + * @param string $discountName + * @param float $amount + * @param float $rate + * @return LineItemCreate + */ + private function createDiscountLineItem(string $discountName, float $amount, float $rate): LineItemCreate + { + $lineItem = new LineItemCreate(); + + $discountSkuName = 'sku-discount-' . $rate . '-' . $discountName; + $discountTitle = sprintf('DISCOUNT: %s (%s%% tax)', $discountName, $rate); + if ($this->order->getTaxStatus() === 'tax-free') { + $discountSkuName = 'sku-discount-' . $discountName; + $discountTitle = sprintf('DISCOUNT: %s', $discountName); + } + + $lineItem->setAmountIncludingTax($amount) + ->setName($discountTitle) + ->setQuantity(1) + ->setShippingRequired(false) + ->setSku($discountSkuName, 200) + ->setType(LineItemType::DISCOUNT) + ->setUniqueId('coupon-' . $discountSkuName); + + $taxRate = new TaxCreate([ + 'title' => 'Discount Tax: ' . $rate, + 'rate' => $rate, + ]); + + if ($this->order->getTaxStatus() !== 'tax-free') { + $lineItem->setTaxes([$taxRate]); + } + + return $lineItem; + } + + /** + * @return float + */ + private function getDefaultTaxRate(): float + { + /** @var SystemConfigService $systemConfigService */ + $systemConfigService = $this->container->get(SystemConfigService::class); + $taxId = $systemConfigService->get('core.tax.defaultTaxRate'); + + if (!$taxId || !is_string($taxId)) { + return 21.0; + } + + $criteria = new Criteria([$taxId]); + /** @var TaxRepository $taxRepository */ + $taxRepository = $this->container->get('tax.repository'); + $tax = $taxRepository->search($criteria, Context::createDefaultContext())->get($taxId); + + return $tax instanceof TaxEntity ? $tax->getTaxRate() : 21.0; + } + + /** + * Calculate discount amount including tax if necessary. + */ + protected function calculateDiscountAmount($calculatedTax): float + { + $amount = self::round($calculatedTax->getPrice()); + if ($this->order->getTaxStatus() === 'net') { + $amount = self::round($amount + $calculatedTax->getTax()); + } + return $amount; + } + + /** + * Sort line items by name. + */ + protected function sortLineItemsByName(array &$lineItems): void + { + usort($lineItems, function ($lineItem1, $lineItem2) { + return strcmp($lineItem1->getName(), $lineItem2->getName()); + }); + } + + /** + * Add optional shipping and adjustment line items. + */ + protected function addOptionalLineItems(array &$lineItems): void + { + if (count($this->order->getShippingCosts()->getCalculatedTaxes()) === 1) { + if ($shippingLineItem = $this->getShippingLineItem()) { + $lineItems[] = $shippingLineItem; + } + } else { + if ($multipleShippingLineItems = $this->getMultipleShippingLineItems()) { + $lineItems = array_merge($lineItems, $multipleShippingLineItems); + } + } + + if ($adjustmentLineItem = $this->getAdjustmentLineItem($lineItems)) { + $lineItems[] = $adjustmentLineItem; + } + } + + /** + * @param string $lineItemParentId + * @return string + */ + protected function getCustomProductOptionLabel(string $lineItemParentId): string + { + $label = ''; + foreach ($this->order->getLineItems() as $shopLineItem) { + if ($shopLineItem->getParentId() === $lineItemParentId && $shopLineItem->getType() === CustomProductsLineItemTypes::LINE_ITEM_TYPE_PRODUCT) { + $label = $shopLineItem->getLabel(); + break; + } + } + + return $label; + } + + /** + * + * @return \VRPayment\Sdk\Model\LineItemCreate|null + * @throws \Exception + * @var \Shopware\Core\Checkout\Order\Aggregate\OrderLineItem\OrderLineItemEntity $shopLineItem + */ + protected function createLineItem(OrderLineItemEntity $shopLineItem): ?LineItemCreate + { + $uniqueId = $shopLineItem->getId(); + $sku = $shopLineItem->getProductId() ? $shopLineItem->getProductId() : $uniqueId; + $payLoad = $shopLineItem->getPayload(); + if (!empty($payLoad) && !empty($payLoad['productNumber'])) { + $sku = $payLoad['productNumber']; + } + $sku = $this->fixLength($sku, 200); + + $amount = $shopLineItem->getTotalPrice() ? self::round($shopLineItem->getTotalPrice()) : 0; + + //include Tax Excluded for Net Tax display customer group + if ($this->order->getTaxStatus() === 'net') { + $amount = self::round($amount + $shopLineItem->getPrice()->getCalculatedTaxes()->getAmount()); + } + + $lineItem = (new LineItemCreate()) + ->setName($this->fixLength($shopLineItem->getLabel(), 150)) + ->setUniqueId($uniqueId) + ->setSku($sku) + ->setQuantity($shopLineItem->getQuantity() ?? 1) + ->setAmountIncludingTax($amount); + + + if (!empty($shopLineItem->getType()) && $shopLineItem->getType() == CustomProductsLineItemTypes::LINE_ITEM_TYPE_CUSTOMIZED_PRODUCTS) { + + $productAttributes = $this->getCustomProductLineItemAttribute($shopLineItem); + $taxes = $this->getCustomProductTaxes( + $shopLineItem->getPrice()->getCalculatedTaxes(), + $this->translator->trans('vrpayment.payload.taxes'), + $amount + ); + + } else { + $productAttributes = $this->getProductAttributes($shopLineItem); + + $taxes = $this->getTaxes( + $shopLineItem->getPrice()->getCalculatedTaxes(), + $this->translator->trans('vrpayment.payload.taxes') + ); + } + + + if (!empty($productAttributes)) { + $lineItem->setAttributes($productAttributes); + } + + if (!empty($taxes)) { + if ($this->order->getTaxStatus() !== 'tax-free') { + $lineItem->setTaxes($taxes); + } + } + + if ($shopLineItem->getTotalPrice() >= 0) { + $lineItem->setType(LineItemType::PRODUCT); + } else { + $lineItem->setType(LineItemType::DISCOUNT); + } + + return $lineItem; + } + + /** + * @param \Shopware\Core\Checkout\Cart\Tax\Struct\CalculatedTaxCollection $calculatedTaxes + * @param string $title + * + * @return array + */ + protected function getTaxes(CalculatedTaxCollection $calculatedTaxes, string $title): array + { + $taxes = []; + foreach ($calculatedTaxes as $calculatedTax) { + + $tax = (new TaxCreate()) + ->setRate($calculatedTax->getTaxRate()) + ->setTitle($this->fixLength($title . ' : ' . $calculatedTax->getTaxRate(), 40)); + + if (!$tax->valid()) { + $this->logger->critical('Tax payload invalid:', $tax->listInvalidProperties()); + throw new InvalidPayloadException('Tax payload invalid:' . json_encode($tax->listInvalidProperties())); + } + + $taxes [] = $tax; + } + + return $taxes; + } + + /** + * @param \Shopware\Core\Checkout\Order\Aggregate\OrderLineItem\OrderLineItemEntity $shopLineItem + * + * @return array|null + */ + protected function getProductAttributes(OrderLineItemEntity $shopLineItem): ?array + { + $productAttributes = []; + $lineItemPayload = $shopLineItem->getPayload(); + + if (is_array($lineItemPayload) && !empty($lineItemPayload['options'])) { + foreach ($lineItemPayload['options'] as $option) { + + $label = $option['group']; + $lineItemAttributeCreate = (new LineItemAttributeCreate()) + ->setLabel($this->fixLength($label, 512)) + ->setValue($this->fixLength((string)$option['option'], 512)); + + if ($lineItemAttributeCreate->valid()) { + $key = $this->fixLength('option_' . md5($label), 40); + $productAttributes[$key] = $lineItemAttributeCreate; + } else { + $this->logger->critical('LineItemAttributeCreate payload invalid:', $lineItemAttributeCreate->listInvalidProperties()); + throw new InvalidPayloadException('LineItemAttributeCreate payload invalid:' . json_encode($lineItemAttributeCreate->listInvalidProperties())); + } + } + } + + return empty($productAttributes) ? null : $productAttributes; + } + + /** + * @return \VRPayment\Sdk\Model\LineItemCreate|null + */ + protected function getShippingLineItem(): ?LineItemCreate + { + try { + + $amount = $this->order->getShippingTotal(); + $amount = self::round($amount); + + if ($amount > 0) { + + $shippingName = $this->salesChannelContext->getShippingMethod()->getName() ?? $this->translator->trans('vrpayment.payload.shipping.name'); + $taxes = $this->getTaxes( + $this->order->getShippingCosts()->getCalculatedTaxes(), + $shippingName + ); + if ($this->order->getTaxStatus() === 'net') { + $amount = self::round($amount + $this->order->getShippingCosts()->getCalculatedTaxes()->getAmount()); + } + + + $lineItem = (new LineItemCreate()) + ->setAmountIncludingTax($amount) + ->setName($this->fixLength($shippingName . ' ' . $this->translator->trans('vrpayment.payload.shipping.lineItem'), 150)) + ->setQuantity($this->order->getShippingCosts()->getQuantity() ?? 1) + ->setSku($this->fixLength($shippingName . '-Shipping', 200)) + /** @noinspection PhpParamsInspection */ + ->setType(LineItemType::SHIPPING) + ->setUniqueId($this->fixLength($shippingName . '-Shipping', 200)); + + if ($this->order->getTaxStatus() !== 'tax-free') { + $lineItem->setTaxes($taxes); + } + + if (!$lineItem->valid()) { + $this->logger->critical('Shipping LineItem payload invalid:', $lineItem->listInvalidProperties()); + throw new InvalidPayloadException('Shipping LineItem payload invalid:' . json_encode($lineItem->listInvalidProperties())); + } + + return $lineItem; + } + + } catch (\Exception $exception) { + $this->logger->critical(__CLASS__ . ' : ' . __FUNCTION__ . ' : ' . $exception->getMessage()); + } + return null; + } + + /** + * @return array + */ + protected function getMultipleShippingLineItems(): array + { + try { + if ($this->order->getShippingTotal() > 0) { + $lineItems = []; + $shippingName = $this->salesChannelContext->getShippingMethod()->getName() ?? $this->translator->trans('vrpayment.payload.shipping.name'); + + $isFirst = true; + + foreach ($this->order->getShippingCosts()->getCalculatedTaxes() as $taxItem) { + $amount = self::round($taxItem->getPrice()); + if ($this->order->getTaxStatus() === 'net') { + $amount = self::round($amount + $taxItem->getTax()); + } + $taxRate = $taxItem->getTaxRate(); + $tax = (new TaxCreate()) + ->setRate($taxRate) + ->setTitle('Tax rate: '.$taxRate); + + $name = $taxRate . '%-' . $shippingName; + $lineItem = (new LineItemCreate()) + ->setAmountIncludingTax($amount) + ->setName($this->fixLength($name . ' ' . $this->translator->trans('vrpayment.payload.shipping.lineItem'), 150)) + ->setQuantity($this->order->getShippingCosts()->getQuantity() ?? 1) + ->setSku($this->fixLength($name . '-Shipping', 200)) + ->setType($isFirst ? LineItemType::SHIPPING : LineItemType::FEE) // First item as SHIPPING, rest as FEE + ->setUniqueId($this->fixLength($name . '-Shipping', 200)); + + if ($this->order->getTaxStatus() !== 'tax-free') { + $lineItem->setTaxes([$tax]); + } + + if (!$lineItem->valid()) { + $this->logger->critical('Shipping LineItem payload invalid:', $lineItem->listInvalidProperties()); + throw new InvalidPayloadException('Shipping LineItem payload invalid:' . json_encode($lineItem->listInvalidProperties())); + } + + $lineItems[] = $lineItem; + $isFirst = false; + } + return $lineItems; + } + + } catch (\Exception $exception) { + $this->logger->critical(__CLASS__ . ' : ' . __FUNCTION__ . ' : ' . $exception->getMessage()); + } + return []; + } + + /** + * Get Adjustment Line Item + * + * @param \VRPayment\Sdk\Model\LineItemCreate[] $lineItems + * + * @return \VRPayment\Sdk\Model\LineItemCreate|null + * @throws \Exception + */ + protected function getAdjustmentLineItem(array &$lineItems): ?LineItemCreate + { + $lineItem = null; + + $lineItemPriceTotal = array_sum(array_map(static function (LineItemCreate $lineItem) { + return $lineItem->getAmountIncludingTax(); + }, $lineItems)); + + $adjustmentPrice = $this->order->getAmountTotal() - $lineItemPriceTotal; + $adjustmentPrice = self::round($adjustmentPrice); + + if (abs($adjustmentPrice) != 0) { + if ($this->settings->isLineItemConsistencyEnabled()) { + $error = strtr('LineItems total :lineItemTotal does not add up to order total :orderTotal', [ + ':lineItemTotal' => $lineItemPriceTotal, + ':orderTotal' => $this->order->getAmountTotal(), + ]); + $this->logger->critical($error); + throw new \Exception($error); + + } else { + $lineItem = (new LineItemCreate()) + ->setName($this->translator->trans('vrpayment.payload.adjustmentLineItem')) + ->setUniqueId('Adjustment-Line-Item') + ->setSku('Adjustment-Line-Item') + ->setQuantity(1); + /** @noinspection PhpParamsInspection */ + $lineItem->setAmountIncludingTax($adjustmentPrice) + ->setType(($adjustmentPrice > 0) ? LineItemType::FEE : LineItemType::DISCOUNT); + + if (!$lineItem->valid()) { + $this->logger->critical('Adjustment LineItem payload invalid:', $lineItem->listInvalidProperties()); + throw new InvalidPayloadException('Adjustment LineItem payload invalid:' . json_encode($lineItem->listInvalidProperties())); + } + } + } + + return $lineItem; + } + + /** + * Get address payload + * + * @param \Shopware\Core\Checkout\Customer\CustomerEntity $customer + * @param \Shopware\Core\Checkout\Customer\Aggregate\CustomerAddress\CustomerAddressEntity $customerAddressEntity + * + * @return \VRPayment\Sdk\Model\AddressCreate + * @throws \Exception + */ + protected function getAddressPayload(CustomerEntity $customer, CustomerAddressEntity $customerAddressEntity, bool $returnSalesTaxNumber = true): AddressCreate + { + // Family name + $family_name = null; + if (!empty($customerAddressEntity->getLastName())) { + $family_name = $customerAddressEntity->getLastName(); + } else { + if (!empty($customer->getLastName())) { + $family_name = $customer->getLastName(); + } + } + $family_name = !empty($family_name) ? $this->fixLength($family_name, 100) : null; + + // Given name + $given_name = null; + if (!empty($customerAddressEntity->getFirstName())) { + $given_name = $customerAddressEntity->getFirstName(); + } else { + if (!empty($customer->getFirstName())) { + $given_name = $customer->getFirstName(); + } + } + $given_name = !empty($given_name) ? $this->fixLength($given_name, 100) : null; + + // Organization name + $organization_name = null; + if (!empty($customerAddressEntity->getCompany())) { + $organization_name = $customerAddressEntity->getCompany(); + } + + $organization_name = !empty($organization_name) ? $this->fixLength($organization_name, 100) : null; + + $salesTaxNumber = null; + if ($returnSalesTaxNumber) { + // salesTaxNumber + $vatIds = $customer->getVatIds(); + if (!empty($vatIds)) { + $salesTaxNumber = $vatIds[0]; + } + } + + // Salutation + $salutation = null; + if (!( + empty($customerAddressEntity->getSalutation()) || + empty($customerAddressEntity->getSalutation()->getDisplayName()) + )) { + $salutation = $customerAddressEntity->getSalutation()->getDisplayName(); + } else { + if (!empty($customer->getSalutation())) { + $salutation = $customer->getSalutation()->getDisplayName(); + + } + } + $salutation = !empty($salutation) ? $this->fixLength($salutation, 20) : null; + + $birthday = null; + if (!empty($customer->getBirthday())) { + $birthday = new \DateTime(); + $birthday->setTimestamp($customer->getBirthday()->getTimestamp()); + $birthday = $birthday->format('Y-m-d'); + } + + $postalState = $customerAddressEntity?->getCountryState()?->getName() ?? ''; + if (empty($postalState)) { + $postalState = $customerAddressEntity?->getCountryState()?->getShortCode() ?? ''; + } + + $addressData = [ + 'city' => $customerAddressEntity->getCity() ? $this->fixLength($customerAddressEntity->getCity(), 100) : null, + 'country' => $customerAddressEntity->getCountry() ? $customerAddressEntity->getCountry()->getIso() : null, + 'email_address' => $customer->getEmail() ? $this->fixLength($customer->getEmail(), 254) : null, + 'family_name' => $family_name, + 'given_name' => $given_name, + 'organization_name' => $organization_name, + 'phone_number' => $customerAddressEntity->getPhoneNumber() ? $this->fixLength($customerAddressEntity->getPhoneNumber(), 100) : null, + 'postcode' => $customerAddressEntity->getZipcode() ? $this->fixLength($customerAddressEntity->getZipcode(), 40) : null, + 'postal_state' => $postalState, + 'salutation' => $salutation, + 'street' => $customerAddressEntity->getStreet() ? $this->fixLength($customerAddressEntity->getStreet(), 300) : null, + 'birthday' => $birthday + ]; + + if ($returnSalesTaxNumber) { + $addressData['sales_tax_number'] = $salesTaxNumber; + } + + $addressPayload = (new AddressCreate()) + ->setCity($addressData['city']) + ->setCountry($addressData['country']) + ->setEmailAddress($addressData['email_address']) + ->setFamilyName($addressData['family_name']) + ->setGivenName($addressData['given_name']) + ->setOrganizationName($addressData['organization_name']) + ->setPhoneNumber($addressData['phone_number']) + ->setPostCode($addressData['postcode']) + ->setPostalState($addressData['postal_state']) + ->setSalutation($addressData['salutation']) + ->setStreet($addressData['street']); + + if ($returnSalesTaxNumber) { + $addressPayload->setSalesTaxNumber($addressData['sales_tax_number']); + } + + if (!empty($addressData['birthday'])) { + $addressPayload->setDateOfBirth($addressData['birthday']); + } + + if (!$addressPayload->valid()) { + $this->logger->critical('Address payload invalid:', $addressPayload->listInvalidProperties()); + throw new InvalidPayloadException('Address payload invalid:' . json_encode($addressPayload->listInvalidProperties())); + } + + return $addressPayload; + } + + /** + * @param string $id + * + * @return \VRPaymentPayment\Core\Api\PaymentMethodConfiguration\Entity\PaymentMethodConfigurationEntity + */ + protected function getPaymentConfiguration(string $id): PaymentMethodConfigurationEntity + { + $criteria = (new Criteria([$id])); + + return $this->container->get('vrpayment_payment_method_configuration.repository') + ->search($criteria, $this->salesChannelContext->getContext()) + ->getEntities()->first(); + } + + /** + * Get failure URL + * + * @param string $orderId + * + * @return string + */ + protected function getFailUrl(string $orderId): string + { + return $this->container->get('router')->generate( + 'frontend.vrpayment.checkout.recreate-cart', + ['orderId' => $orderId,], + UrlGeneratorInterface::ABSOLUTE_URL + ); + } +} diff --git a/src/Core/Util/PaymentMethodUtil.php b/src/Core/Util/PaymentMethodUtil.php new file mode 100644 index 0000000..d64e6df --- /dev/null +++ b/src/Core/Util/PaymentMethodUtil.php @@ -0,0 +1,205 @@ +container = $container; + $this->paymentRepository = $this->container->get('payment_method.repository'); + $this->salesChannelRepository = $this->container->get('sales_channel.repository'); + $this->salesChannelPaymentMethodRepository = $this->container->get('sales_channel_payment_method.repository'); + } + + /** + * @param \Psr\Log\LoggerInterface $logger + * @internal + * @required + * + */ + public function setLogger(LoggerInterface $logger): void + { + $this->logger = $logger; + } + + /** + * @param \Shopware\Core\Framework\Context $context + * @param string|null $salesChannelId + */ + public function setVRPaymentAsDefaultPaymentMethod(Context $context, ?string $salesChannelId = null): void + { + $paymentMethodIds = $this->getVRPaymentPaymentMethodIds($context); + if (empty($paymentMethodIds)) { + return; + } + + $salesChannelsToChange = $this->getSalesChannelsToChange($context, $salesChannelId); + $updateData = []; + + foreach ($salesChannelsToChange as $salesChannel) { + foreach ($paymentMethodIds as $paymentMethodId) { + $salesChannelUpdateData = [ + 'id' => $salesChannel->getId(), + 'paymentMethodId' => $paymentMethodId, + ]; + + $paymentMethodCollection = $salesChannel->getPaymentMethods(); + if (is_null($paymentMethodCollection) || is_null($paymentMethodCollection->get($paymentMethodId))) { + $salesChannelUpdateData['paymentMethods'][] = [ + 'id' => $paymentMethodId, + ]; + } + + $updateData[] = $salesChannelUpdateData; + } + } + + $this->salesChannelRepository->update($updateData, $context); + } + + /** + * @param \Shopware\Core\Framework\Context $context + * @return array + */ + public function getVRPaymentPaymentMethodIds(Context $context): array + { + $criteria = (new Criteria()) + ->addFilter(new EqualsFilter('handlerIdentifier', VRPaymentPaymentHandler::class)) + ->addSorting(new FieldSorting('position')); + + return $this->paymentRepository->searchIds($criteria, $context)->getIds(); + } + + /** + * @param \Shopware\Core\Framework\Context $context + * @param string|null $salesChannelId + * @return \Shopware\Core\System\SalesChannel\SalesChannelCollection + */ + private function getSalesChannelsToChange(Context $context, ?string $salesChannelId = null): SalesChannelCollection + { + $criteria = is_null($salesChannelId) ? new Criteria() : new Criteria([$salesChannelId]); + $criteria->addAssociation('paymentMethods'); + + return $this->salesChannelRepository->search($criteria, $context)->getEntities(); + } + + /** + * Disable System Payment Methods + * + * @param \Shopware\Core\Framework\Context $context + * + */ + public function disableSystemPaymentMethods(Context $context): void + { + $paymentMethodIds = $this->getSystemPaymentMethodIds($context); + $this->setPaymentMethodIsActive($paymentMethodIds, false, $context); + $this->disableSalesChannelPaymentMethods($paymentMethodIds, $context); + } + + /** + * @param \Shopware\Core\Framework\Context $context + * @return string[] + */ + protected function getSystemPaymentMethodIds(Context $context): array + { + $criteria = (new Criteria()) + ->addFilter(new NotFilter( + NotFilter::CONNECTION_AND, + [ + new EqualsFilter('handlerIdentifier', VRPaymentPaymentHandler::class), + ] + )); + + return $this->paymentRepository->searchIds($criteria, $context)->getIds(); + } + + /** + * @param array $paymentMethodIds + * @param bool $active + * @param \Shopware\Core\Framework\Context $context + * + */ + protected function setPaymentMethodIsActive(array $paymentMethodIds, bool $active, Context $context): void + { + $data = []; + + foreach ($paymentMethodIds as $paymentMethodId) { + $data[] = [ + 'id' => $paymentMethodId, + 'active' => $active, + ]; + } + + $this->paymentRepository->update($data, $context); + } + + /** + * @param array $paymentMethodIds + * @param \Shopware\Core\Framework\Context $context + * + */ + protected function disableSalesChannelPaymentMethods(array $paymentMethodIds, Context $context) + { + $data = []; + + $salesChannels = $this->getSalesChannelsToChange($context); + + foreach ($salesChannels as $salesChannel) { + foreach ($paymentMethodIds as $paymentMethodId) { + $data[] = [ + 'paymentMethodId' => $paymentMethodId, + 'salesChannelId' => $salesChannel->getId(), + ]; + } + } + $this->salesChannelPaymentMethodRepository->delete($data, $context); + } + +} \ No newline at end of file diff --git a/src/Core/Util/Traits/VRPaymentPaymentPluginTrait.php b/src/Core/Util/Traits/VRPaymentPaymentPluginTrait.php new file mode 100644 index 0000000..4de4eeb --- /dev/null +++ b/src/Core/Util/Traits/VRPaymentPaymentPluginTrait.php @@ -0,0 +1,115 @@ +getPaymentMethodIds($context); + foreach ($paymentMethodIds as $paymentMethodId) { + $this->setPaymentMethodIsActive($paymentMethodId, true, $context); + } + } + + /** + * @param \Shopware\Core\Framework\Context $context + * @return string[] + */ + protected function getPaymentMethodIds(Context $context): array + { + /** @var EntityRepositoryInterface $paymentRepository */ + $paymentRepository = $this->container->get('payment_method.repository'); + $criteria = (new Criteria()) + ->addFilter(new EqualsFilter('handlerIdentifier', VRPaymentPaymentHandler::class)); + + return $paymentRepository->searchIds($criteria, $context)->getIds(); + } + + /** + * @param string $paymentMethodId + * @param bool $active + * @param \Shopware\Core\Framework\Context $context + * @return void + */ + protected function setPaymentMethodIsActive(string $paymentMethodId, bool $active, Context $context): void + { + $paymentMethod = [ + 'id' => $paymentMethodId, + 'active' => $active, + ]; + + /** @var EntityRepositoryInterface $paymentRepository */ + $paymentRepository = $this->container->get('payment_method.repository'); + $paymentRepository->update([$paymentMethod], $context); + } + + /** + * @param \Shopware\Core\Framework\Context $context + * @return void + */ + protected function disablePaymentMethods(Context $context): void + { + $paymentMethodIds = $this->getPaymentMethodIds($context); + foreach ($paymentMethodIds as $paymentMethodId) { + $this->setPaymentMethodIsActive($paymentMethodId, false, $context); + } + } + + /** + * @param \Shopware\Core\Framework\Context $context + * @return void + */ + private function removeConfiguration(Context $context): void + { + $criteria = (new Criteria()) + ->addFilter(new ContainsFilter('configurationKey', SettingsService::SYSTEM_CONFIG_DOMAIN)); + + $systemConfigRepository = $this->container->get('system_config.repository'); + $idSearchResult = $systemConfigRepository->searchIds($criteria, $context); + + foreach ($idSearchResult->getIds() as $id) { + $systemConfigRepository->delete([['id' => $id]], $context); + } + } + + /** + * Delete user data when plugin is uninstalled + * + * @internal + * @param \Shopware\Core\Framework\Plugin\Context\UninstallContext $uninstallContext + * @return void + */ + protected function deleteUserData(UninstallContext $uninstallContext): void + { + $connection = $this->container->get(Connection::class); + // Check if the column exists before attempting to drop it + $columns = $connection->fetchAllAssociative("SHOW COLUMNS FROM `order` LIKE 'vrpayment_lock'"); + if (!empty($columns)) { + $query = 'ALTER TABLE `order` DROP COLUMN `vrpayment_lock`;'; + $connection->executeStatement($query); + } + } +} diff --git a/src/Migration/Migration1590156974PaymentMethodConfigurationEntity.php b/src/Migration/Migration1590156974PaymentMethodConfigurationEntity.php new file mode 100644 index 0000000..2eb442e --- /dev/null +++ b/src/Migration/Migration1590156974PaymentMethodConfigurationEntity.php @@ -0,0 +1,60 @@ +executeStatement(' + CREATE TABLE IF NOT EXISTS `vrpayment_payment_method_configuration` ( + `id` BINARY(16) NOT NULL, + `data` JSON NOT NULL, + `payment_method_configuration_id` INT UNSIGNED NOT NULL, + `payment_method_id` BINARY(16) NOT NULL, + `sort_order` TINYINT UNSIGNED NOT NULL, + `space_id` INT UNSIGNED NOT NULL, + `state` VARCHAR(255) NOT NULL, + `created_at` DATETIME(3) NOT NULL, + `updated_at` DATETIME(3) DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `payment_method_configuration_id_space_id_UNIQUE` (`payment_method_configuration_id`,`space_id`), + KEY `fk.vrp_payment_method_configuration.payment_method_id` (`payment_method_id`), + CONSTRAINT `fk.vrp_payment_method_configuration.payment_method_id` FOREIGN KEY (`payment_method_id`) REFERENCES `payment_method` (`id`) ON DELETE CASCADE + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + '); + } + + /** + * update destructive changes + * + * @param \Doctrine\DBAL\Connection $connection + */ + public function updateDestructive(Connection $connection): void + { + // implement update destructive + } +} diff --git a/src/Migration/Migration1590156974TransactionEntity.php b/src/Migration/Migration1590156974TransactionEntity.php new file mode 100644 index 0000000..16b93c5 --- /dev/null +++ b/src/Migration/Migration1590156974TransactionEntity.php @@ -0,0 +1,69 @@ +executeStatement(' + CREATE TABLE IF NOT EXISTS `vrpayment_transaction` ( + `id` BINARY(16) NOT NULL, + `data` JSON NOT NULL, + `payment_method_id` BINARY(16) NOT NULL, + `order_id` BINARY(16) NOT NULL, + `order_transaction_id` BINARY(16) NOT NULL, + `space_id` INT UNSIGNED NOT NULL, + `state` VARCHAR(255) NOT NULL, + `sales_channel_id` BINARY(16) NOT NULL, + `transaction_id` INT UNSIGNED NOT NULL, + `created_at` DATETIME(3) NOT NULL, + `updated_at` DATETIME(3) DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `transaction_id_UNIQUE` (`transaction_id`), + KEY `fk.vrp_transaction.order_id` (`order_id`), + KEY `fk.vrp_transaction.order_transaction_id` (`order_transaction_id`), + KEY `fk.vrp_transaction.payment_method_id` (`payment_method_id`), + KEY `fk.vrp_transaction.sales_channel_id` (`sales_channel_id`), + CONSTRAINT `fk.vrp_transaction.order_id` FOREIGN KEY (`order_id`) REFERENCES `order` (`id`) ON DELETE CASCADE, + CONSTRAINT `fk.vrp_transaction.order_transaction_id` FOREIGN KEY (`order_transaction_id`) REFERENCES `order_transaction` (`id`) ON DELETE CASCADE, + CONSTRAINT `fk.vrp_transaction.payment_method_id` FOREIGN KEY (`payment_method_id`) REFERENCES `payment_method` (`id`) ON DELETE CASCADE, + CONSTRAINT `fk.vrp_transaction.sales_channel_id` FOREIGN KEY (`sales_channel_id`) REFERENCES `sales_channel` (`id`) ON DELETE CASCADE + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + '); + } + + /** + * update destructive changes + * + * @param \Doctrine\DBAL\Connection $connection + */ + public function updateDestructive(Connection $connection): void + { + // implement update destructive + } +} diff --git a/src/Migration/Migration1590646356OrderEntity.php b/src/Migration/Migration1590646356OrderEntity.php new file mode 100644 index 0000000..e381e1a --- /dev/null +++ b/src/Migration/Migration1590646356OrderEntity.php @@ -0,0 +1,47 @@ +executeStatement('ALTER TABLE `order` ADD COLUMN `vrpayment_lock` DATETIME DEFAULT NULL;'); + }catch (\Exception $exception){ + echo $exception->getMessage(); + } + } + + /** + * update destructive changes + * + * @param \Doctrine\DBAL\Connection $connection + */ + public function updateDestructive(Connection $connection): void + { + // implement update destructive + } +} diff --git a/src/Migration/Migration1590646356RefundEntity.php b/src/Migration/Migration1590646356RefundEntity.php new file mode 100644 index 0000000..272d2ec --- /dev/null +++ b/src/Migration/Migration1590646356RefundEntity.php @@ -0,0 +1,59 @@ +executeStatement(' + CREATE TABLE IF NOT EXISTS `vrpayment_refund` ( + `id` BINARY(16) NOT NULL, + `data` JSON NOT NULL, + `refund_id` INT UNSIGNED NOT NULL, + `space_id` INT UNSIGNED NOT NULL, + `state` VARCHAR(255) NOT NULL, + `transaction_id` INT UNSIGNED NOT NULL, + `created_at` DATETIME(3) NOT NULL, + `updated_at` DATETIME(3) DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `refund_id_UNIQUE` (`refund_id`), + KEY `fk.vrp_refund.transaction_id` (`transaction_id`), + CONSTRAINT `fk.vrp_refund.transaction_id` FOREIGN KEY (`transaction_id`) REFERENCES `vrpayment_transaction` (`transaction_id`) ON DELETE CASCADE + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + '); + } + + /** + * update destructive changes + * + * @param \Doctrine\DBAL\Connection $connection + */ + public function updateDestructive(Connection $connection): void + { + // implement update destructive + } +} diff --git a/src/Migration/Migration1590646356TransactionEntity.php b/src/Migration/Migration1590646356TransactionEntity.php new file mode 100644 index 0000000..1e36e65 --- /dev/null +++ b/src/Migration/Migration1590646356TransactionEntity.php @@ -0,0 +1,48 @@ +executeStatement('ALTER TABLE `vrpayment_transaction` ADD COLUMN `confirmation_email_sent` TINYINT(1) NOT NULL DEFAULT 0 AFTER `id`;'); + }catch (\Exception $exception){ + // column probably exists + } + } + + /** + * update destructive changes + * + * @param \Doctrine\DBAL\Connection $connection + */ + public function updateDestructive(Connection $connection): void + { + // implement update destructive + } +} diff --git a/src/Migration/Migration1605701047PaymentMethodConfigurationEntity.php b/src/Migration/Migration1605701047PaymentMethodConfigurationEntity.php new file mode 100644 index 0000000..11c9271 --- /dev/null +++ b/src/Migration/Migration1605701047PaymentMethodConfigurationEntity.php @@ -0,0 +1,44 @@ +executeStatement('ALTER TABLE `vrpayment_payment_method_configuration` CHANGE `payment_method_configuration_id` `payment_method_configuration_id` bigint unsigned NOT NULL AFTER `data`;'); + } + + /** + * update destructive changes + * + * @param \Doctrine\DBAL\Connection $connection + */ + public function updateDestructive(Connection $connection): void + { + // implement update destructive + } +} diff --git a/src/Migration/Migration1605701048TransactionEntity.php b/src/Migration/Migration1605701048TransactionEntity.php new file mode 100644 index 0000000..5368d14 --- /dev/null +++ b/src/Migration/Migration1605701048TransactionEntity.php @@ -0,0 +1,78 @@ +executeStatement(' + ALTER TABLE `vrpayment_transaction` + ADD `order_version_id` binary(16) NOT NULL AFTER `transaction_id`; + '); + + $connection->executeStatement(' + UPDATE `vrpayment_transaction` t1 + INNER JOIN `order` t2 + ON t1.order_id = t2.id + SET t1.order_version_id = t2.version_id; + '); + + $connection->executeStatement(' + ALTER TABLE `vrpayment_transaction` + DROP FOREIGN KEY `fk.vrp_transaction.order_id`, + DROP FOREIGN KEY `fk.vrp_transaction.order_transaction_id`, + DROP FOREIGN KEY `fk.vrp_transaction.payment_method_id`, + DROP FOREIGN KEY `fk.vrp_transaction.sales_channel_id`; + '); + + $connection->executeStatement(' + ALTER TABLE `vrpayment_transaction` + ADD CONSTRAINT `fk.vrp_transaction_order_id` FOREIGN KEY (`order_id`, `order_version_id`) + REFERENCES `order` (`id`, `version_id`) ON DELETE CASCADE ON UPDATE CASCADE, + ADD CONSTRAINT `fk.vrp_transaction_payment_method_id` FOREIGN KEY (`payment_method_id`) + REFERENCES `payment_method` (`id`) ON UPDATE CASCADE ON DELETE RESTRICT, + ADD CONSTRAINT `fk.vrp_transaction_sales_channel_id` FOREIGN KEY (`sales_channel_id`) + REFERENCES `sales_channel` (`id`) ON UPDATE CASCADE ON DELETE RESTRICT; + '); + } catch (\Exception $exception) { + // column probably exists + } + } + + /** + * update destructive changes + * + * @param \Doctrine\DBAL\Connection $connection + */ + public function updateDestructive(Connection $connection): void + { + // implement update destructive + } +} diff --git a/src/Migration/Migration1605701049StateMachineEntity.php b/src/Migration/Migration1605701049StateMachineEntity.php new file mode 100644 index 0000000..5f830dd --- /dev/null +++ b/src/Migration/Migration1605701049StateMachineEntity.php @@ -0,0 +1,89 @@ +fetchOne( + "SELECT id FROM `$table` WHERE `technical_name` = :technical_name", + [ + 'technical_name' => 'order_transaction.state', + ] + ); + + $table = StateMachineStateDefinition::ENTITY_NAME; + $remindedStateId = $connection->fetchOne( + "SELECT id FROM `$table` WHERE `technical_name` = :technical_name AND `state_machine_id` = :state_machine_id", + [ + 'technical_name' => 'reminded', + 'state_machine_id' => $stateMachineId, + ] + ); + + $paidStateId = $connection->fetchOne( + "SELECT id FROM `$table` WHERE `technical_name` = :technical_name AND `state_machine_id` = :state_machine_id", + [ + 'technical_name' => 'paid', + 'state_machine_id' => $stateMachineId, + ] + ); + + $id = Uuid::randomBytes(); + $connection->insert(StateMachineTransitionDefinition::ENTITY_NAME, + [ + 'id' => $id, + 'action_name' => 'paid', + 'state_machine_id' => $stateMachineId, + 'from_state_id' => $remindedStateId, + 'to_state_id' => $paidStateId, + 'created_at' => date('Y-m-d H:i:s') + ] + ); + } catch (\Exception $exception) { + // column probably exists + } + } + + /** + * update destructive changes + * + * @param \Doctrine\DBAL\Connection $connection + */ + public function updateDestructive(Connection $connection): void + { + // implement update destructive + } +} diff --git a/src/Migration/Migration1684240994TransactionEntity.php b/src/Migration/Migration1684240994TransactionEntity.php new file mode 100644 index 0000000..2fc4bf9 --- /dev/null +++ b/src/Migration/Migration1684240994TransactionEntity.php @@ -0,0 +1,48 @@ +executeStatement('ALTER TABLE `vrpayment_transaction` ADD COLUMN `erp_merchant_id` VARCHAR(255) DEFAULT NULL AFTER `confirmation_email_sent`;'); + }catch (\Exception $exception){ + // column probably exists + } + } + + /** + * update destructive changes + * + * @param \Doctrine\DBAL\Connection $connection + */ + public function updateDestructive(Connection $connection): void + { + // implement update destructive + } +} diff --git a/src/Resources/app/administration/src/core/service/api/vrpayment-configuration.service.js b/src/Resources/app/administration/src/core/service/api/vrpayment-configuration.service.js new file mode 100644 index 0000000..8e0d302 --- /dev/null +++ b/src/Resources/app/administration/src/core/service/api/vrpayment-configuration.service.js @@ -0,0 +1,141 @@ +/* global Shopware */ + +const ApiService = Shopware.Classes.ApiService; + +/** + * @class VRPaymentPayment\Core\Api\Config\Controller\ConfigurationController + */ +class VRPaymentConfigurationService extends ApiService { + + /** + * VRPaymentConfigurationService constructor + * + * @param httpClient + * @param loginService + * @param apiEndpoint + */ + constructor(httpClient, loginService, apiEndpoint = 'vrpayment') { + super(httpClient, loginService, apiEndpoint); + } + + /** + * Register web hooks + * + * @param {String|null} salesChannelId + * @return {*} + */ + registerWebHooks(salesChannelId = null) { + + const headers = this.getBasicHeaders(); + const apiRoute = `${Shopware.Context.api.apiPath}/_action/${this.getApiBasePath()}/configuration/register-web-hooks`; + + return this.httpClient.post( + apiRoute, + { + salesChannelId: salesChannelId + }, + { + headers: headers + } + ).then((response) => { + return ApiService.handleResponse(response); + }); + } + + /** + * Test API connection + * + * @param {int|null} spaceId + * @param {int|null} userId + * @param {String|null} applicationId + * @return {*} + */ + checkApiConnection(spaceId = null, userId = null, applicationId = null) { + + const headers = this.getBasicHeaders(); + const apiRoute = `${Shopware.Context.api.apiPath}/_action/${this.getApiBasePath()}/configuration/check-api-connection`; + + return this.httpClient.post( + apiRoute, + { + spaceId: spaceId, + userId: userId, + applicationId: applicationId + }, + { + headers: headers + } + ).then((response) => { + return ApiService.handleResponse(response); + }); + } + + /** + * Set's the default payment method to VRPayment for the given salesChannel id. + * + * @param {String|null} salesChannelId + * + * @returns {Promise} + */ + setVRPaymentAsSalesChannelPaymentDefault(salesChannelId = null) { + + const headers = this.getBasicHeaders(); + const apiRoute = `${Shopware.Context.api.apiPath}/_action/${this.getApiBasePath()}/configuration/set-vrpayment-as-sales-channel-payment-default`; + + return this.httpClient.post( + apiRoute, + { + salesChannelId: salesChannelId + }, + { + headers: headers + } + ).then((response) => { + return ApiService.handleResponse(response); + }); + } + + /** + * + * @param salesChannelId + * @return {Promise} + */ + synchronizePaymentMethodConfiguration(salesChannelId = null) { + const headers = this.getBasicHeaders(); + const apiRoute = `${Shopware.Context.api.apiPath}/_action/${this.getApiBasePath()}/configuration/synchronize-payment-method-configuration`; + + return this.httpClient.post( + apiRoute, + { + salesChannelId: salesChannelId + }, + { + headers: headers + } + ).then((response) => { + return ApiService.handleResponse(response); + }); + } + + /** + * + * @return {*} + */ + installOrderDeliveryStates() { + const headers = this.getBasicHeaders(); + const apiRoute = `${Shopware.Context.api.apiPath}/_action/${this.getApiBasePath()}/configuration/install-order-delivery-states`; + + return this.httpClient.post( + apiRoute, + { + }, + { + headers: headers + } + ).then((response) => { + return ApiService.handleResponse(response); + }); + } +} + +export default VRPaymentConfigurationService; diff --git a/src/Resources/app/administration/src/core/service/api/vrpayment-refund.service.js b/src/Resources/app/administration/src/core/service/api/vrpayment-refund.service.js new file mode 100644 index 0000000..dd71437 --- /dev/null +++ b/src/Resources/app/administration/src/core/service/api/vrpayment-refund.service.js @@ -0,0 +1,110 @@ +/* global Shopware */ + +const ApiService = Shopware.Classes.ApiService; + +/** + * @class VRPaymentPayment\Core\Api\Transaction\Controller\RefundController + */ +class VRPaymentRefundService extends ApiService { + + /** + * VRPaymentRefundService constructor + * + * @param httpClient + * @param loginService + * @param apiEndpoint + */ + constructor(httpClient, loginService, apiEndpoint = 'vrpayment') { + super(httpClient, loginService, apiEndpoint); + } + + /** + * Refund a transaction + * + * @param {String} salesChannelId + * @param {int} transactionId + * @param {int} quantity + * @param {int} lineItemId + * @return {*} + */ + createRefund(salesChannelId, transactionId, quantity, lineItemId) { + + const headers = this.getBasicHeaders(); + const apiRoute = `${Shopware.Context.api.apiPath}/_action/${this.getApiBasePath()}/refund/create-refund/`; + + return this.httpClient.post( + apiRoute, + { + salesChannelId: salesChannelId, + transactionId: transactionId, + quantity: quantity, + lineItemId: lineItemId + }, + { + headers: headers + } + ).then((response) => { + return ApiService.handleResponse(response); + }); + } + + /** + * Refund a transaction + * + * @param {String} salesChannelId + * @param {int} transactionId + * @param {float} refundableAmount + * @return {*} + */ + createRefundByAmount(salesChannelId, transactionId, refundableAmount) { + + const headers = this.getBasicHeaders(); + const apiRoute = `${Shopware.Context.api.apiPath}/_action/${this.getApiBasePath()}/refund/create-refund-by-amount/`; + + return this.httpClient.post( + apiRoute, + { + salesChannelId: salesChannelId, + transactionId: transactionId, + refundableAmount: refundableAmount + }, + { + headers: headers + } + ).then((response) => { + return ApiService.handleResponse(response); + }); + } + + /** + * Refund a transaction + * + * @param {String} salesChannelId + * @param {int} transactionId + * @param {float} refundableAmount + * @param {String} lineItemId + * @return {*} + */ + createPartialRefund(salesChannelId, transactionId, refundableAmount, lineItemId) { + + const headers = this.getBasicHeaders(); + const apiRoute = `${Shopware.Context.api.apiPath}/_action/${this.getApiBasePath()}/refund/create-partial-refund/`; + + return this.httpClient.post( + apiRoute, + { + salesChannelId: salesChannelId, + transactionId: transactionId, + refundableAmount: refundableAmount, + lineItemId: lineItemId + }, + { + headers: headers + } + ).then((response) => { + return ApiService.handleResponse(response); + }); + } +} + +export default VRPaymentRefundService; diff --git a/src/Resources/app/administration/src/core/service/api/vrpayment-transaction-completion.service.js b/src/Resources/app/administration/src/core/service/api/vrpayment-transaction-completion.service.js new file mode 100644 index 0000000..233ca71 --- /dev/null +++ b/src/Resources/app/administration/src/core/service/api/vrpayment-transaction-completion.service.js @@ -0,0 +1,48 @@ +/* global Shopware */ + +const ApiService = Shopware.Classes.ApiService; + +/** + * @class VRPaymentPayment\Core\Api\Transaction\Controller\TransactionCompletionController + */ +class VRPaymentTransactionCompletionService extends ApiService { + + /** + * VRPaymentTransactionCompletionService constructor + * + * @param httpClient + * @param loginService + * @param apiEndpoint + */ + constructor(httpClient, loginService, apiEndpoint = 'vrpayment') { + super(httpClient, loginService, apiEndpoint); + } + + /** + * Complete a transaction + * + * @param {String} salesChannelId + * @param {int} transactionId + * @return {*} + */ + createTransactionCompletion(salesChannelId, transactionId) { + + const headers = this.getBasicHeaders(); + const apiRoute = `${Shopware.Context.api.apiPath}/_action/${this.getApiBasePath()}/transaction-completion/create-transaction-completion/`; + + return this.httpClient.post( + apiRoute, + { + salesChannelId: salesChannelId, + transactionId: transactionId + }, + { + headers: headers + } + ).then((response) => { + return ApiService.handleResponse(response); + }); + } +} + +export default VRPaymentTransactionCompletionService; \ No newline at end of file diff --git a/src/Resources/app/administration/src/core/service/api/vrpayment-transaction-void.service.js b/src/Resources/app/administration/src/core/service/api/vrpayment-transaction-void.service.js new file mode 100644 index 0000000..a70cdcd --- /dev/null +++ b/src/Resources/app/administration/src/core/service/api/vrpayment-transaction-void.service.js @@ -0,0 +1,48 @@ +/* global Shopware */ + +const ApiService = Shopware.Classes.ApiService; + +/** + * @class VRPaymentPayment\Core\Api\Transaction\Controller\TransactionVoidController + */ +class VRPaymentTransactionVoidService extends ApiService { + + /** + * VRPaymentTransactionVoidService constructor + * + * @param httpClient + * @param loginService + * @param apiEndpoint + */ + constructor(httpClient, loginService, apiEndpoint = 'vrpayment') { + super(httpClient, loginService, apiEndpoint); + } + + /** + * Void a transaction + * + * @param {String} salesChannelId + * @param {int} transactionId + * @return {*} + */ + createTransactionVoid(salesChannelId, transactionId) { + + const headers = this.getBasicHeaders(); + const apiRoute = `${Shopware.Context.api.apiPath}/_action/${this.getApiBasePath()}/transaction-void/create-transaction-void/`; + + return this.httpClient.post( + apiRoute, + { + salesChannelId: salesChannelId, + transactionId: transactionId + }, + { + headers: headers + } + ).then((response) => { + return ApiService.handleResponse(response); + }); + } +} + +export default VRPaymentTransactionVoidService; \ No newline at end of file diff --git a/src/Resources/app/administration/src/core/service/api/vrpayment-transaction.service.js b/src/Resources/app/administration/src/core/service/api/vrpayment-transaction.service.js new file mode 100644 index 0000000..6683b6d --- /dev/null +++ b/src/Resources/app/administration/src/core/service/api/vrpayment-transaction.service.js @@ -0,0 +1,71 @@ +/* global Shopware */ + +const ApiService = Shopware.Classes.ApiService; + +/** + * @class VRPaymentPayment\Core\Api\Transaction\Controller\TransactionController + */ +class VRPaymentTransactionService extends ApiService { + + /** + * VRPaymentTransactionService constructor + * + * @param httpClient + * @param loginService + * @param apiEndpoint + */ + constructor(httpClient, loginService, apiEndpoint = 'vrpayment') { + super(httpClient, loginService, apiEndpoint); + } + + /** + * Get transaction data + * + * @param {String} salesChannelId + * @param {int} transactionId + * @return {*} + */ + getTransactionData(salesChannelId, transactionId) { + + const headers = this.getBasicHeaders(); + const apiRoute = `${Shopware.Context.api.apiPath}/_action/${this.getApiBasePath()}/transaction/get-transaction-data/`; + + return this.httpClient.post( + apiRoute, + { + salesChannelId: salesChannelId, + transactionId: transactionId + }, + { + headers: headers + } + ).then((response) => { + return ApiService.handleResponse(response); + }); + } + + /** + * Download Invoice Document + * + * @param context + * @param salesChannelId + * @param transactionId + * @return {string} + */ + getInvoiceDocument(salesChannelId, transactionId) { + return `${Shopware.Context.api.apiPath}/_action/${this.getApiBasePath()}/transaction/get-invoice-document/${salesChannelId}/${transactionId}`; + } + + /** + * Download Packing slip + * + * @param salesChannelId + * @param transactionId + * @return {string} + */ + getPackingSlip(salesChannelId, transactionId) { + return `${Shopware.Context.api.apiPath}/_action/${this.getApiBasePath()}/transaction/get-packing-slip/${salesChannelId}/${transactionId}`; + } +} + +export default VRPaymentTransactionService; \ No newline at end of file diff --git a/src/Resources/app/administration/src/core/service/api/vrpayment-webhook-register.service.js b/src/Resources/app/administration/src/core/service/api/vrpayment-webhook-register.service.js new file mode 100644 index 0000000..c612c12 --- /dev/null +++ b/src/Resources/app/administration/src/core/service/api/vrpayment-webhook-register.service.js @@ -0,0 +1,44 @@ +/* global Shopware */ + +const ApiService = Shopware.Classes.ApiService; + +/** + * @class VRPaymentPayment\Core\Api\WebHooks\Controller\WebHookController + */ +class VRPaymentWebHookRegisterService extends ApiService { + + /** + * VRPaymentWebHookRegisterService + * + * @param httpClient + * @param loginService + * @param apiEndpoint + */ + constructor(httpClient, loginService, apiEndpoint = 'vrpayment') { + super(httpClient, loginService, apiEndpoint); + } + + /** + * Register a webhook + * + * @param {String|null} salesChannelId + * @return {*} + */ + registerWebHook(salesChannelId) { + + const headers = this.getBasicHeaders(); + const apiRoute = `${Shopware.Context.api.apiPath}/_action/${this.getApiBasePath()}/webHook/register/${salesChannelId}`; + + return this.httpClient.post( + apiRoute, + {}, + { + headers: headers + } + ).then((response) => { + return ApiService.handleResponse(response); + }); + } +} + +export default VRPaymentWebHookRegisterService; 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..0f93bc3 --- /dev/null +++ b/src/Resources/app/administration/src/init/api-service.init.js @@ -0,0 +1,42 @@ +/* global Shopware */ + +import VRPaymentConfigurationService from '../core/service/api/vrpayment-configuration.service'; +import VRPaymentRefundService from '../core/service/api/vrpayment-refund.service'; +import VRPaymentTransactionService from '../core/service/api/vrpayment-transaction.service'; +import VRPaymentTransactionCompletionService + from '../core/service/api/vrpayment-transaction-completion.service'; +import VRPaymentTransactionVoidService + from '../core/service/api/vrpayment-transaction-void.service'; + + +const {Application} = Shopware; + +// noinspection JSUnresolvedFunction +Application.addServiceProvider('VRPaymentConfigurationService', (container) => { + const initContainer = Application.getContainer('init'); + return new VRPaymentConfigurationService(initContainer.httpClient, container.loginService); +}); + +// noinspection JSUnresolvedFunction +Application.addServiceProvider('VRPaymentRefundService', (container) => { + const initContainer = Application.getContainer('init'); + return new VRPaymentRefundService(initContainer.httpClient, container.loginService); +}); + +// noinspection JSUnresolvedFunction +Application.addServiceProvider('VRPaymentTransactionService', (container) => { + const initContainer = Application.getContainer('init'); + return new VRPaymentTransactionService(initContainer.httpClient, container.loginService); +}); + +// noinspection JSUnresolvedFunction +Application.addServiceProvider('VRPaymentTransactionCompletionService', (container) => { + const initContainer = Application.getContainer('init'); + return new VRPaymentTransactionCompletionService(initContainer.httpClient, container.loginService); +}); + +// noinspection JSUnresolvedFunction +Application.addServiceProvider('VRPaymentTransactionVoidService', (container) => { + const initContainer = Application.getContainer('init'); + return new VRPaymentTransactionVoidService(initContainer.httpClient, container.loginService); +}); \ No newline at end of file diff --git a/src/Resources/app/administration/src/main.js b/src/Resources/app/administration/src/main.js new file mode 100644 index 0000000..082fc10 --- /dev/null +++ b/src/Resources/app/administration/src/main.js @@ -0,0 +1,3 @@ +import './module/vrpayment-order'; +import './module/vrpayment-settings'; +import './init/api-service.init'; \ No newline at end of file diff --git a/src/Resources/app/administration/src/module/vrpayment-order/component/vrpayment-order-action-completion/index.html.twig b/src/Resources/app/administration/src/module/vrpayment-order/component/vrpayment-order-action-completion/index.html.twig new file mode 100644 index 0000000..ec0cc3b --- /dev/null +++ b/src/Resources/app/administration/src/module/vrpayment-order/component/vrpayment-order-action-completion/index.html.twig @@ -0,0 +1,24 @@ +{% block vrpayment_order_action_completion %} + + + {% block vrpayment_order_action_completion_amount %} + + + {% endblock %} + + {% block vrpayment_order_action_completion_confirm_button %} + + {% endblock %} + + + +{% endblock %} diff --git a/src/Resources/app/administration/src/module/vrpayment-order/component/vrpayment-order-action-completion/index.js b/src/Resources/app/administration/src/module/vrpayment-order/component/vrpayment-order-action-completion/index.js new file mode 100644 index 0000000..553ca4c --- /dev/null +++ b/src/Resources/app/administration/src/module/vrpayment-order/component/vrpayment-order-action-completion/index.js @@ -0,0 +1,86 @@ +/* global Shopware */ + +import template from './index.html.twig'; + +const {Component, Mixin, Filter, Utils} = Shopware; + +Component.register('vrpayment-order-action-completion', { + + template: template, + + inject: ['VRPaymentTransactionCompletionService'], + + mixins: [ + Mixin.getByName('notification') + ], + + props: { + transactionData: { + type: Object, + required: true + } + }, + + data() { + return { + isLoading: true, + isCompletion: false + }; + }, + + computed: { + dateFilter() { + return Filter.getByName('date'); + } + }, + + created() { + this.createdComponent(); + }, + + methods: { + createdComponent() { + this.isLoading = false; + }, + + completion() { + if (this.isCompletion) { + this.isLoading = true; + this.VRPaymentTransactionCompletionService.createTransactionCompletion( + this.transactionData.transactions[0].metaData.salesChannelId, + this.transactionData.transactions[0].id + ).then(() => { + this.createNotificationSuccess({ + title: this.$tc('vrpayment-order.captureAction.successTitle'), + message: this.$tc('vrpayment-order.captureAction.successMessage') + }); + this.isLoading = false; + this.$emit('modal-close'); + this.$nextTick(() => { + this.$router.replace(`${this.$route.path}?hash=${Utils.createId()}`); + }); + }).catch((errorResponse) => { + try { + this.createNotificationError({ + title: errorResponse.response.data.errors[0].title, + message: errorResponse.response.data.errors[0].detail, + autoClose: false + }); + } catch (e) { + this.createNotificationError({ + title: errorResponse.title, + message: errorResponse.message, + autoClose: false + }); + } finally { + this.isLoading = false; + this.$emit('modal-close'); + this.$nextTick(() => { + this.$router.replace(`${this.$route.path}?hash=${Utils.createId()}`); + }); + } + }); + } + } + } +}); diff --git a/src/Resources/app/administration/src/module/vrpayment-order/component/vrpayment-order-action-refund-by-amount/index.html.twig b/src/Resources/app/administration/src/module/vrpayment-order/component/vrpayment-order-action-refund-by-amount/index.html.twig new file mode 100644 index 0000000..6f86754 --- /dev/null +++ b/src/Resources/app/administration/src/module/vrpayment-order/component/vrpayment-order-action-refund-by-amount/index.html.twig @@ -0,0 +1,26 @@ +{% block vrpayment_order_action_refund_by_amount %} + + + {% block vrpayment_order_action_refund_amount_by_amount %} + + + {% endblock %} + + {% block vrpayment_order_action_refund_confirm_button_by_amount %} + + {% endblock %} + + + +{% endblock %} diff --git a/src/Resources/app/administration/src/module/vrpayment-order/component/vrpayment-order-action-refund-by-amount/index.js b/src/Resources/app/administration/src/module/vrpayment-order/component/vrpayment-order-action-refund-by-amount/index.js new file mode 100644 index 0000000..f11d712 --- /dev/null +++ b/src/Resources/app/administration/src/module/vrpayment-order/component/vrpayment-order-action-refund-by-amount/index.js @@ -0,0 +1,103 @@ +/* global Shopware */ + +import template from './index.html.twig'; + +const {Component, Mixin, Filter, Utils} = Shopware; + +Component.register('vrpayment-order-action-refund-by-amount', { + template, + + inject: ['VRPaymentRefundService'], + + mixins: [ + Mixin.getByName('notification') + ], + + props: { + transactionData: { + type: Object, + required: true + }, + + orderId: { + type: String, + required: true + } + }, + + data() { + return { + isLoading: true, + currency: this.transactionData.transactions[0].currency, + refundAmount: 0, + refundableAmount: 0, + }; + }, + + computed: { + dateFilter() { + return Filter.getByName('date'); + } + }, + + created() { + this.createdComponent(); + }, + + methods: { + createdComponent() { + this.isLoading = false; + this.currency = this.transactionData.transactions[0].currency; + this.refundAmount = Number(this.transactionData.transactions[0].amountIncludingTax); + this.refundableAmount = Number(this.transactionData.transactions[0].amountIncludingTax); + }, + + refundByAmount() { + this.isLoading = true; + this.VRPaymentRefundService.createRefundByAmount( + this.transactionData.transactions[0].metaData.salesChannelId, + this.transactionData.transactions[0].id, + this.refundAmount + ).then(() => { + this.createNotificationSuccess({ + title: this.$tc('vrpayment-order.refundAction.successTitle'), + message: this.$tc('vrpayment-order.refundAction.successMessage') + }); + this.isLoading = false; + this.$emit('modal-close'); + this.$nextTick(() => { + this.$router.replace(`${this.$route.path}?hash=${Utils.createId()}`); + }); + }).catch((errorResponse) => { + try { + var errorTitle; + var errorMessage; + if (errorResponse.response.data == 'refundExceedsAmount') { + errorTitle = this.$tc('vrpayment-order.refundAction.refundExceedsTotalError.title'); + errorMessage = this.$tc('vrpayment-order.refundAction.refundExceedsTotalError.messageRefundAmountExceedsAvailableBalance'); + } else { + errorTitle = errorResponse.response.data.errors[0].title; + errorMessage = errorResponse.response.data.errors[0].detail; + } + this.createNotificationError({ + title: errorTitle, + message: errorMessage, + autoClose: false + }); + } catch (e) { + this.createNotificationError({ + title: errorResponse.title, + message: errorResponse.message, + autoClose: false + }); + } finally { + this.isLoading = false; + this.$emit('modal-close'); + this.$nextTick(() => { + this.$router.replace(`${this.$route.path}?hash=${Utils.createId()}`); + }); + } + }); + } + } +}); diff --git a/src/Resources/app/administration/src/module/vrpayment-order/component/vrpayment-order-action-refund-partial/index.html.twig b/src/Resources/app/administration/src/module/vrpayment-order/component/vrpayment-order-action-refund-partial/index.html.twig new file mode 100644 index 0000000..37a7e2b --- /dev/null +++ b/src/Resources/app/administration/src/module/vrpayment-order/component/vrpayment-order-action-refund-partial/index.html.twig @@ -0,0 +1,31 @@ +{% block vrpayment_order_action_refund_partial %} + + + {% block vrpayment_order_action_refund_amount_partial %} + + + +
+ {{ $tc('vrpayment-order.refundAction.maxAvailableAmountToRefund') }}: + {{ this.$parent.$parent.itemRefundableAmount }} +
+ {% endblock %} + + {% block vrpayment_order_action_refund_confirm_button_partial %} + + {% endblock %} + + +
+{% endblock %} diff --git a/src/Resources/app/administration/src/module/vrpayment-order/component/vrpayment-order-action-refund-partial/index.js b/src/Resources/app/administration/src/module/vrpayment-order/component/vrpayment-order-action-refund-partial/index.js new file mode 100644 index 0000000..d581002 --- /dev/null +++ b/src/Resources/app/administration/src/module/vrpayment-order/component/vrpayment-order-action-refund-partial/index.js @@ -0,0 +1,103 @@ +/* global Shopware */ + +import template from './index.html.twig'; + +const {Component, Mixin, Filter, Utils} = Shopware; + +Component.register('vrpayment-order-action-refund-partial', { + template, + + inject: ['VRPaymentRefundService'], + + mixins: [ + Mixin.getByName('notification') + ], + + props: { + transactionData: { + type: Object, + required: true + }, + + orderId: { + type: String, + required: true + } + }, + + data() { + return { + isLoading: true, + currency: this.transactionData.transactions[0].currency, + refundAmount: 0.00, + }; + }, + + computed: { + dateFilter() { + return Filter.getByName('date'); + } + }, + + created() { + this.createdComponent(); + }, + + methods: { + createdComponent() { + this.isLoading = false; + this.currency = this.transactionData.transactions[0].currency; + if (!this.refundAmount) { + this.refundAmount = this.$parent.$parent.itemRefundableAmount; + } + }, + + createPartialRefund(itemUniqueId) { + this.isLoading = true; + this.VRPaymentRefundService.createPartialRefund( + this.transactionData.transactions[0].metaData.salesChannelId, + this.transactionData.transactions[0].id, + this.refundAmount, + itemUniqueId + ).then(() => { + this.createNotificationSuccess({ + title: this.$tc('vrpayment-order.refundAction.successTitle'), + message: this.$tc('vrpayment-order.refundAction.successMessage') + }); + this.isLoading = false; + this.$emit('modal-close'); + this.$nextTick(() => { + this.$router.replace(`${this.$route.path}?hash=${Utils.createId()}`); + }); + }).catch((errorResponse) => { + try { + this.createNotificationError({ + title: errorResponse.response.data.errors[0].title, + message: errorResponse.response.data.errors[0].detail, + autoClose: false + }); + } catch (e) { + this.createNotificationError({ + title: errorResponse.title, + message: errorResponse.message, + autoClose: false + }); + } finally { + this.isLoading = false; + this.$emit('modal-close'); + this.$nextTick(() => { + this.$router.replace(`${this.$route.path}?hash=${Utils.createId()}`); + }); + } + }); + } + }, + + watch: { + refundAmount(newValue) { + if (newValue !== null) { + this.refundAmount = Math.round(newValue * 100) / 100; + } + } + } +}); diff --git a/src/Resources/app/administration/src/module/vrpayment-order/component/vrpayment-order-action-refund-selected/index.html.twig b/src/Resources/app/administration/src/module/vrpayment-order/component/vrpayment-order-action-refund-selected/index.html.twig new file mode 100644 index 0000000..a2bfbb1 --- /dev/null +++ b/src/Resources/app/administration/src/module/vrpayment-order/component/vrpayment-order-action-refund-selected/index.html.twig @@ -0,0 +1,16 @@ +{% block vrpayment_order_action_refund_selected %} + + + {% block vrpayment_order_action_refund_confirm_button_selected %} + + {% endblock %} + + + +{% endblock %} diff --git a/src/Resources/app/administration/src/module/vrpayment-order/component/vrpayment-order-action-refund-selected/index.js b/src/Resources/app/administration/src/module/vrpayment-order/component/vrpayment-order-action-refund-selected/index.js new file mode 100644 index 0000000..e2862b7 --- /dev/null +++ b/src/Resources/app/administration/src/module/vrpayment-order/component/vrpayment-order-action-refund-selected/index.js @@ -0,0 +1,94 @@ +/* global Shopware */ + +import template from './index.html.twig'; + +const {Component, Mixin, Filter, Utils} = Shopware; + +Component.register('vrpayment-order-action-refund-selected', { + template, + + inject: ['VRPaymentRefundService'], + + mixins: [ + Mixin.getByName('notification') + ], + + props: { + transactionData: { + type: Object, + required: true + }, + + orderId: { + type: String, + required: true + } + }, + + data() { + return { + isLoading: true, + currency: this.transactionData.transactions[0].currency, + refundAmount: 0, + refundableAmount: 0, + }; + }, + + computed: { + dateFilter() { + return Filter.getByName('date'); + } + }, + + created() { + this.createdComponent(); + }, + + methods: { + createdComponent() { + this.isLoading = false; + this.currency = this.transactionData.transactions[0].currency; + this.refundAmount = Number(this.transactionData.transactions[0].amountIncludingTax); + this.refundableAmount = Number(this.transactionData.transactions[0].amountIncludingTax); + }, + + refundSelected() { + this.isLoading = true; + this.VRPaymentRefundService.createRefundByAmount( + this.transactionData.transactions[0].metaData.salesChannelId, + this.transactionData.transactions[0].id, + this.refundAmount + ).then(() => { + this.createNotificationSuccess({ + title: this.$tc('vrpayment-order.refundAction.successTitle'), + message: this.$tc('vrpayment-order.refundAction.successMessage') + }); + this.isLoading = false; + this.$emit('modal-close'); + this.$nextTick(() => { + this.$router.replace(`${this.$route.path}?hash=${Utils.createId()}`); + }); + }).catch((errorResponse) => { + try { + this.createNotificationError({ + title: errorResponse.response.data.errors[0].title, + message: errorResponse.response.data.errors[0].detail, + autoClose: false + }); + } catch (e) { + this.createNotificationError({ + title: errorResponse.title, + message: errorResponse.message, + autoClose: false + }); + } finally { + this.isLoading = false; + this.$emit('modal-close'); + this.$nextTick(() => { + this.$router.replace(`${this.$route.path}?hash=${Utils.createId()}`); + }); + } + }); + } + } +}); diff --git a/src/Resources/app/administration/src/module/vrpayment-order/component/vrpayment-order-action-refund/index.html.twig b/src/Resources/app/administration/src/module/vrpayment-order/component/vrpayment-order-action-refund/index.html.twig new file mode 100644 index 0000000..2e3e421 --- /dev/null +++ b/src/Resources/app/administration/src/module/vrpayment-order/component/vrpayment-order-action-refund/index.html.twig @@ -0,0 +1,31 @@ +{% block vrpayment_order_action_refund %} + + + {% block vrpayment_order_action_refund_amount %} + + + + +
+ {{ $tc('vrpayment-order.refundAction.maxAvailableItemsToRefund') }}: + {{ this.$parent.$parent.itemRefundableQuantity }} +
+ {% endblock %} + + {% block vrpayment_order_action_refund_confirm_button %} + + {% endblock %} + + +
+{% endblock %} diff --git a/src/Resources/app/administration/src/module/vrpayment-order/component/vrpayment-order-action-refund/index.js b/src/Resources/app/administration/src/module/vrpayment-order/component/vrpayment-order-action-refund/index.js new file mode 100644 index 0000000..243dc97 --- /dev/null +++ b/src/Resources/app/administration/src/module/vrpayment-order/component/vrpayment-order-action-refund/index.js @@ -0,0 +1,92 @@ +/* global Shopware */ + +import template from './index.html.twig'; + +const {Component, Mixin, Filter, Utils} = Shopware; + +Component.register('vrpayment-order-action-refund', { + template, + + inject: ['VRPaymentRefundService'], + + mixins: [ + Mixin.getByName('notification') + ], + + props: { + transactionData: { + type: Object, + required: true + }, + + orderId: { + type: String, + required: true + } + }, + + data() { + return { + refundQuantity: 0, + isLoading: true, + currentLineItem: '', + }; + }, + + computed: { + dateFilter() { + return Filter.getByName('date'); + } + }, + + created() { + this.createdComponent(); + }, + + methods: { + createdComponent() { + this.isLoading = false; + this.refundQuantity = 1; + }, + + refund() { + this.isLoading = true; + this.VRPaymentRefundService.createRefund( + this.transactionData.transactions[0].metaData.salesChannelId, + this.transactionData.transactions[0].id, + this.refundQuantity, + this.$parent.$parent.currentLineItem + ).then(() => { + this.createNotificationSuccess({ + title: this.$tc('vrpayment-order.refundAction.successTitle'), + message: this.$tc('vrpayment-order.refundAction.successMessage') + }); + this.isLoading = false; + this.$emit('modal-close'); + this.$nextTick(() => { + this.$router.replace(`${this.$route.path}?hash=${Utils.createId()}`); + }); + }).catch((errorResponse) => { + try { + this.createNotificationError({ + title: errorResponse.response.data.errors[0].title, + message: errorResponse.response.data.errors[0].detail, + autoClose: false + }); + } catch (e) { + this.createNotificationError({ + title: errorResponse.title, + message: errorResponse.message, + autoClose: false + }); + } finally { + this.isLoading = false; + this.$emit('modal-close'); + this.$nextTick(() => { + this.$router.replace(`${this.$route.path}?hash=${Utils.createId()}`); + }); + } + }); + } + } +}); diff --git a/src/Resources/app/administration/src/module/vrpayment-order/component/vrpayment-order-action-void/index.html.twig b/src/Resources/app/administration/src/module/vrpayment-order/component/vrpayment-order-action-void/index.html.twig new file mode 100644 index 0000000..62abf11 --- /dev/null +++ b/src/Resources/app/administration/src/module/vrpayment-order/component/vrpayment-order-action-void/index.html.twig @@ -0,0 +1,25 @@ +{% block vrpayment_order_action_void %} + + + {% block vrpayment_order_action_void_amount %} + {# Review if this v-model:checked="isVoid" needs to change to checked #} + + + {% endblock %} + + {% block vrpayment_order_action_void_confirm_button %} + + {% endblock %} + + + +{% endblock %} diff --git a/src/Resources/app/administration/src/module/vrpayment-order/component/vrpayment-order-action-void/index.js b/src/Resources/app/administration/src/module/vrpayment-order/component/vrpayment-order-action-void/index.js new file mode 100644 index 0000000..92bbe58 --- /dev/null +++ b/src/Resources/app/administration/src/module/vrpayment-order/component/vrpayment-order-action-void/index.js @@ -0,0 +1,138 @@ +/* global Shopware */ + +import template from './index.html.twig'; + +const {Component, Mixin, Filter, Utils} = Shopware; + +Component.register('vrpayment-order-action-void', { + template, + + inject: ['VRPaymentTransactionVoidService'], + + mixins: [ + Mixin.getByName('notification') + ], + + props: { + transactionData: { + type: Object, + required: true + } + }, + + data() { + return { + isLoading: true, + isVoid: false + }; + }, + + computed: { + dateFilter() { + return Filter.getByName('date'); + }, + lineItemColumns() { + return [ + { + property: 'uniqueId', + label: this.$tc('vrpayment-order.refund.types.uniqueId'), + rawData: false, + allowResize: true, + primary: true, + width: 'auto' + }, + { + property: 'name', + label: this.$tc('vrpayment-order.refund.types.name'), + rawData: true, + allowResize: true, + sortable: true, + width: 'auto' + }, + { + property: 'quantity', + label: this.$tc('vrpayment-order.refund.types.quantity'), + rawData: true, + allowResize: true, + width: 'auto' + }, + { + property: 'amountIncludingTax', + label: this.$tc('vrpayment-order.refund.types.amountIncludingTax'), + rawData: true, + allowResize: true, + inlineEdit: 'string', + width: 'auto' + }, + { + property: 'type', + label: this.$tc('vrpayment-order.refund.types.type'), + rawData: true, + allowResize: true, + sortable: true, + width: 'auto' + }, + { + property: 'taxAmount', + label: this.$tc('vrpayment-order.refund.types.taxAmount'), + rawData: true, + allowResize: true, + width: 'auto' + } + ]; + } + }, + + created() { + this.createdComponent(); + }, + + methods: { + createdComponent() { + this.isLoading = false; + this.currency = this.transactionData.transactions[0].currency; + this.refundableAmount = this.transactionData.transactions[0].amountIncludingTax; + this.refundAmount = this.transactionData.transactions[0].amountIncludingTax; + }, + + voidPayment() { + if (this.isVoid) { + this.isLoading = true; + this.VRPaymentTransactionVoidService.createTransactionVoid( + this.transactionData.transactions[0].metaData.salesChannelId, + this.transactionData.transactions[0].id + ).then(() => { + this.createNotificationSuccess({ + title: this.$tc('vrpayment-order.voidAction.successTitle'), + message: this.$tc('vrpayment-order.voidAction.successMessage') + }); + this.isLoading = false; + this.$emit('modal-close'); + this.$nextTick(() => { + this.$router.replace(`${this.$route.path}?hash=${Utils.createId()}`); + }); + }).catch((errorResponse) => { + try { + this.createNotificationError({ + title: errorResponse.response.data.errors[0].title, + message: errorResponse.response.data.errors[0].detail, + autoClose: false + }); + } catch (e) { + this.createNotificationError({ + title: errorResponse.title, + message: errorResponse.message, + autoClose: false + }); + } finally { + this.isLoading = false; + this.$emit('modal-close'); + this.$nextTick(() => { + this.$router.replace(`${this.$route.path}?hash=${Utils.createId()}`); + }); + } + }); + } + } + } +}); diff --git a/src/Resources/app/administration/src/module/vrpayment-order/extension/sw-order/index.js b/src/Resources/app/administration/src/module/vrpayment-order/extension/sw-order/index.js new file mode 100644 index 0000000..bf3a925 --- /dev/null +++ b/src/Resources/app/administration/src/module/vrpayment-order/extension/sw-order/index.js @@ -0,0 +1,75 @@ +/* global Shopware */ + +import template from './sw-order.html.twig'; +import './sw-order.scss'; + +const {Component, Context} = Shopware; +const Criteria = Shopware.Data.Criteria; + +const vrpaymentFormattedHandlerIdentifier = 'handler_vrpaymentpayment_vrpaymentpaymenthandler'; + +Component.override('sw-order-detail', { + template, + + data() { + return { + isVRPaymentPayment: false + }; + }, + + computed: { + isEditable() { + return !this.isVRPaymentPayment || this.$route.name !== 'vrpayment.order.detail'; + }, + showTabs() { + return true; + } + }, + + watch: { + orderId: { + deep: true, + handler() { + if (!this.orderId) { + this.setIsVRPaymentPayment(null); + return; + } + + const orderRepository = this.repositoryFactory.create('order'); + const orderCriteria = new Criteria(1, 1); + orderCriteria.addAssociation('transactions'); + + orderRepository.get(this.orderId, Context.api, orderCriteria).then((order) => { + if ( + (order.amountTotal <= 0) || + (order.transactions.length <= 0) || + !order.transactions[0].paymentMethodId + ) { + this.setIsVRPaymentPayment(null); + return; + } + + const paymentMethodId = order.transactions[0].paymentMethodId; + if (paymentMethodId !== undefined && paymentMethodId !== null) { + this.setIsVRPaymentPayment(paymentMethodId); + } + }); + }, + immediate: true + } + }, + + methods: { + setIsVRPaymentPayment(paymentMethodId) { + if (!paymentMethodId) { + return; + } + const paymentMethodRepository = this.repositoryFactory.create('payment_method'); + paymentMethodRepository.get(paymentMethodId, Context.api).then( + (paymentMethod) => { + this.isVRPaymentPayment = (paymentMethod.formattedHandlerIdentifier === vrpaymentFormattedHandlerIdentifier); + } + ); + } + } +}); diff --git a/src/Resources/app/administration/src/module/vrpayment-order/extension/sw-order/sw-order.html.twig b/src/Resources/app/administration/src/module/vrpayment-order/extension/sw-order/sw-order.html.twig new file mode 100644 index 0000000..aeac916 --- /dev/null +++ b/src/Resources/app/administration/src/module/vrpayment-order/extension/sw-order/sw-order.html.twig @@ -0,0 +1,16 @@ +{% block sw_order_detail_content_tabs_general %} + {% parent %} + +{# sw-tabs-item will dissappear. See: https://github.com/shopware/shopware/blob/trunk/UPGRADE-6.7.md#sw-tabs-is-removed #} + + {{ $tc('vrpayment-order.header') }} + +{% endblock %} + +{% block sw_order_detail_actions_slot_smart_bar_actions %} + +{% endblock %} diff --git a/src/Resources/app/administration/src/module/vrpayment-order/extension/sw-order/sw-order.scss b/src/Resources/app/administration/src/module/vrpayment-order/extension/sw-order/sw-order.scss new file mode 100644 index 0000000..f7ab85e --- /dev/null +++ b/src/Resources/app/administration/src/module/vrpayment-order/extension/sw-order/sw-order.scss @@ -0,0 +1,11 @@ +.sw-order-detail { + .sw-tabs { + margin-top: 40px; + } + + .sw-order-detail-base .mt-card-view__content { + overflow-x: visible; + overflow-y: visible; + } + +} diff --git a/src/Resources/app/administration/src/module/vrpayment-order/index.js b/src/Resources/app/administration/src/module/vrpayment-order/index.js new file mode 100644 index 0000000..14f4ffe --- /dev/null +++ b/src/Resources/app/administration/src/module/vrpayment-order/index.js @@ -0,0 +1,40 @@ +/* global Shopware */ + +import './extension/sw-order'; +import './page/vrpayment-order-detail'; + +import deDE from './snippet/de-DE.json'; +import enGB from './snippet/en-GB.json'; +import frFR from './snippet/fr-FR.json'; +import itIT from './snippet/it-IT.json'; + +const {Module} = Shopware; + +Module.register('vrpayment-order', { + type: 'plugin', + name: 'VRPayment', + title: 'vrpayment-order.general.title', + description: 'vrpayment-order.general.descriptionTextModule', + version: '1.0.1', + targetVersion: '1.0.1', + color: '#2b52ff', + + snippets: { + 'de-DE': deDE, + 'en-GB': enGB, + 'fr-FR': frFR, + 'it-IT': itIT + }, + + routeMiddleware(next, currentRoute) { + if (currentRoute.name === 'sw.order.detail') { + currentRoute.children.push({ + component: 'vrpayment-order-detail', + name: 'vrpayment.order.detail', + isChildren: true, + path: '/sw/order/vrpayment/detail/:id' + }); + } + next(currentRoute); + } +}); diff --git a/src/Resources/app/administration/src/module/vrpayment-order/page/vrpayment-order-detail/index.html.twig b/src/Resources/app/administration/src/module/vrpayment-order/page/vrpayment-order-detail/index.html.twig new file mode 100644 index 0000000..30d37cc --- /dev/null +++ b/src/Resources/app/administration/src/module/vrpayment-order/page/vrpayment-order-detail/index.html.twig @@ -0,0 +1,200 @@ +{% block vrpayment_order_detail %} +
+
+ + + + {% block vrpayment_order_transaction_history_card %} + + + + + {% endblock %} + {% block vrpayment_order_transaction_line_items_card %} + + + + {% endblock %} + {% block vrpayment_order_transaction_refunds_card %} + + + + + {% endblock %} + {% block vrpayment_order_actions_modal_refund_partial %} + + + {% endblock %} + {% block vrpayment_order_actions_modal_refund %} + + + {% endblock %} + {% block vrpayment_order_actions_modal_refund_by_amount %} + + + {% endblock %} + {% block vrpayment_order_actions_modal_completion%} + + + {% endblock %} + {% block vrpayment_order_actions_modal_void %} + + + {% endblock %} +
+ +
+{% endblock %} diff --git a/src/Resources/app/administration/src/module/vrpayment-order/page/vrpayment-order-detail/index.js b/src/Resources/app/administration/src/module/vrpayment-order/page/vrpayment-order-detail/index.js new file mode 100644 index 0000000..a0ac885 --- /dev/null +++ b/src/Resources/app/administration/src/module/vrpayment-order/page/vrpayment-order-detail/index.js @@ -0,0 +1,448 @@ +/* global Shopware */ + +import '../../component/vrpayment-order-action-completion'; +import '../../component/vrpayment-order-action-refund'; +import '../../component/vrpayment-order-action-refund-partial'; +import '../../component/vrpayment-order-action-refund-by-amount'; +import '../../component/vrpayment-order-action-void'; +import template from './index.html.twig'; +import './index.scss'; + +const {Component, Mixin, Filter, Context, Utils} = Shopware; +const Criteria = Shopware.Data.Criteria; + +Component.register('vrpayment-order-detail', { + template, + + inject: [ + 'VRPaymentTransactionService', + 'VRPaymentRefundService', + 'repositoryFactory' + ], + + mixins: [ + Mixin.getByName('notification') + ], + + data() { + return { + transactionData: { + transactions: [], + refunds: [] + }, + transaction: {}, + lineItems: [], + refundableQuantity: 0, + itemRefundableQuantity: 0, + isLoading: true, + orderId: '', + currency: '', + modalType: '', + refundAmount: 0.00, + refundableAmount: 0.00, + itemRefundedAmount: 0.00, + itemRefundedQuantity: 0, + itemRefundableAmount: 0.00, + currentLineItem: '', + refundLineItemQuantity: [], + refundLineItemAmount: [], + selectedItems: [] + }; + }, + + metaInfo() { + return { + title: this.$tc('vrpayment-order.header') + }; + }, + + + computed: { + dateFilter() { + return Filter.getByName('date'); + }, + + relatedResourceColumns() { + return [ + { + property: 'paymentMethodName', + label: this.$tc('vrpayment-order.transactionHistory.types.payment_method'), + rawData: true + }, + { + property: 'state', + label: this.$tc('vrpayment-order.transactionHistory.types.state'), + rawData: true + }, + { + property: 'currency', + label: this.$tc('vrpayment-order.transactionHistory.types.currency'), + rawData: true + }, + { + property: 'authorized_amount', + label: this.$tc('vrpayment-order.transactionHistory.types.authorized_amount'), + rawData: true + }, + { + property: 'id', + label: this.$tc('vrpayment-order.transactionHistory.types.transaction'), + rawData: true + }, + { + property: 'customerId', + label: this.$tc('vrpayment-order.transactionHistory.types.customer'), + rawData: true + } + ]; + }, + + lineItemColumns() { + return [ + // It must be set in order to have correctly working checkbox mechanism + { + property: 'id', + rawData: true, + visible: false, + primary: true + }, + { + property: 'uniqueId', + label: this.$tc('vrpayment-order.lineItem.types.uniqueId'), + rawData: true, + visible: false, + primary: true + }, + { + property: 'name', + label: this.$tc('vrpayment-order.lineItem.types.name'), + rawData: true + }, + { + property: 'quantity', + label: this.$tc('vrpayment-order.lineItem.types.quantity'), + rawData: true + }, + { + property: 'amountIncludingTax', + label: this.$tc('vrpayment-order.lineItem.types.amountIncludingTax'), + rawData: true + }, + { + property: 'type', + label: this.$tc('vrpayment-order.lineItem.types.type'), + rawData: true + }, + { + property: 'taxAmount', + label: this.$tc('vrpayment-order.lineItem.types.taxAmount'), + rawData: true + }, + { + property: 'refundableQuantity', + rawData: true, + visible: false, + }, + ]; + }, + + refundColumns() { + return [ + { + property: 'id', + label: this.$tc('vrpayment-order.refund.types.id'), + rawData: true, + visible: true, + primary: true + }, + { + property: 'amount', + label: this.$tc('vrpayment-order.refund.types.amount'), + rawData: true + }, + { + property: 'state', + label: this.$tc('vrpayment-order.refund.types.state'), + rawData: true + }, + { + property: 'createdOn', + label: this.$tc('vrpayment-order.refund.types.createdOn'), + rawData: true + } + ]; + } + }, + + watch: { + '$route'() { + this.resetDataAttributes(); + this.createdComponent(); + } + }, + + created() { + this.createdComponent(); + }, + + methods: { + createdComponent() { + this.orderId = this.$route.params.id; + const orderRepository = this.repositoryFactory.create('order'); + const orderCriteria = new Criteria(1, 1); + orderCriteria.addAssociation('transactions'); + orderCriteria.getAssociation('transactions').addSorting(Criteria.sort('createdAt', 'DESC')); + + orderRepository.get(this.orderId, Context.api, orderCriteria).then((order) => { + this.order = order; + this.isLoading = false; + var totalAmountTemp = 0; + var refundsAmountTemp = 0; + const vrpaymentTransactionId = order.transactions[0].customFields.vrpayment_transaction_id; + this.VRPaymentTransactionService.getTransactionData(order.salesChannelId, vrpaymentTransactionId) + .then((VRPaymentTransaction) => { + this.currency = VRPaymentTransaction.transactions[0].currency; + + VRPaymentTransaction.transactions[0].authorized_amount = Utils.format.currency( + VRPaymentTransaction.transactions[0].authorizationAmount, + this.currency + ); + + VRPaymentTransaction.refunds.forEach((refund) => { + refundsAmountTemp = parseFloat(parseFloat(refundsAmountTemp) + parseFloat(refund.amount)); + refund.amount = Utils.format.currency( + refund.amount, + this.currency + ); + + refund.reductions.forEach((reduction) => { + if (reduction.quantityReduction > 0) { + if (this.refundLineItemQuantity[reduction.lineItemUniqueId] === undefined) { + this.refundLineItemQuantity[reduction.lineItemUniqueId] = reduction.quantityReduction; + } else { + this.refundLineItemQuantity[reduction.lineItemUniqueId] += reduction.quantityReduction; + } + } + if (reduction.unitPriceReduction > 0) { + if (this.refundLineItemAmount[reduction.lineItemUniqueId] === undefined) { + this.refundLineItemAmount[reduction.lineItemUniqueId] = reduction.unitPriceReduction; + } else { + this.refundLineItemAmount[reduction.lineItemUniqueId] += reduction.unitPriceReduction; + } + } + }); + + }); + + VRPaymentTransaction.transactions[0].lineItems.forEach((lineItem) => { + if (!lineItem.id) { + lineItem.id = lineItem.uniqueId; + } + + lineItem.itemRefundedAmount = parseFloat(this.refundLineItemAmount[lineItem.uniqueId] || 0) * parseInt(lineItem.quantity); + lineItem.amountIncludingTax = parseFloat(lineItem.amountIncludingTax) || 0; + + lineItem.itemRefundedQuantity = parseInt(this.refundLineItemQuantity[lineItem.uniqueId]) || 0; + lineItem.refundableAmount = parseFloat( + (lineItem.amountIncludingTax - lineItem.itemRefundedAmount).toFixed(2) + ); + + lineItem.amountIncludingTax = Utils.format.currency( + lineItem.amountIncludingTax, + this.currency + ); + + lineItem.taxAmount = Utils.format.currency( + lineItem.taxAmount, + this.currency + ); + + totalAmountTemp = parseFloat(parseFloat(totalAmountTemp) + parseFloat(lineItem.unitPriceIncludingTax * lineItem.quantity)); + + lineItem.refundableQuantity = parseInt( + parseInt(lineItem.quantity) - parseInt(this.refundLineItemQuantity[lineItem.uniqueId] || 0) + ); + + }); + + this.lineItems = VRPaymentTransaction.transactions[0].lineItems; + this.transactionData = VRPaymentTransaction; + this.transaction = this.transactionData.transactions[0]; + this.refundAmount = Number(this.transactionData.transactions[0].amountIncludingTax); + this.refundableAmount = parseFloat(parseFloat(totalAmountTemp) - parseFloat(refundsAmountTemp)); + + }).catch((errorResponse) => { + try { + this.createNotificationError({ + title: this.$tc('vrpayment-order.paymentDetails.error.title'), + message: errorResponse.message, + autoClose: false + }); + } catch (e) { + this.createNotificationError({ + title: this.$tc('vrpayment-order.paymentDetails.error.title'), + message: errorResponse.message, + autoClose: false + }); + } finally { + this.isLoading = false; + } + }); + }); + }, + downloadPackingSlip() { + window.open( + this.VRPaymentTransactionService.getPackingSlip( + this.transaction.metaData.salesChannelId, + this.transaction.id + ), + '_blank' + ); + }, + + downloadInvoice() { + window.open( + this.VRPaymentTransactionService.getInvoiceDocument( + this.transaction.metaData.salesChannelId, + this.transaction.id + ), + '_blank' + ); + }, + + resetDataAttributes() { + this.transactionData = { + transactions: [], + refunds: [] + }; + this.lineItems = []; + this.refundLineItemQuantity = []; + this.refundLineItemAmount = []; + this.isLoading = true; + }, + + spawnModal(modalType, lineItemId, refundableQuantity, itemRefundableAmount) { + this.modalType = modalType; + this.currentLineItem = lineItemId; + this.itemRefundableQuantity = refundableQuantity; + this.itemRefundableAmount = !isNaN(itemRefundableAmount) ? Math.round(itemRefundableAmount * 100) / 100 : 0; + }, + + closeModal() { + this.modalType = ''; + }, + + lineItemRefund(lineItemId) { + this.isLoading = true; + this.VRPaymentRefundService.createRefund( + this.transactionData.transactions[0].metaData.salesChannelId, + this.transactionData.transactions[0].id, + 0, + lineItemId + ).then(() => { + this.createNotificationSuccess({ + title: this.$tc('vrpayment-order.refundAction.successTitle'), + message: this.$tc('vrpayment-order.refundAction.successMessage') + }); + this.isLoading = false; + this.$emit('modal-close'); + this.$nextTick(() => { + this.$router.replace(`${this.$route.path}?hash=${Utils.createId()}`); + }); + }).catch((errorResponse) => { + try { + this.createNotificationError({ + title: errorResponse.response.data.errors[0].title, + message: errorResponse.response.data.errors[0].detail, + autoClose: false + }); + } catch (e) { + this.createNotificationError({ + title: errorResponse.title, + message: errorResponse.response.data, + autoClose: false + }); + } finally { + this.isLoading = false; + this.$emit('modal-close'); + this.$nextTick(() => { + this.$router.replace(`${this.$route.path}?hash=${Utils.createId()}`); + }); + } + }); + }, + isSelectable(item) { + return item.refundableQuantity > 0 && item.refundableAmount > 0 && item.itemRefundedAmount == 0 && item.itemRefundedQuantity == 0; + }, + onSelectionChanged(selection) { + this.selectedItems = Object.values(selection); + }, + onPerformBulkAction() { + if (this.selectedItems.length) { + // Set isLoading to true to show the loader + this.isLoading = true; + + // Force the DOM to update before proceeding with the asynchronous operations + this.$nextTick(() => { + const refundPromises = this.selectedItems.map((item) => { + return this.lineItemRefundBulk(item.uniqueId); // Simulated refund action with delay + }); + + // Wait for all refund promises to complete + Promise.all(refundPromises) + .then(() => { + // Once all promises are resolved, hide the loader and close the modal + this.isLoading = false; + this.$emit('modal-close'); + this.$nextTick(() => { + this.$router.replace(`${this.$route.path}?hash=${Utils.createId()}`); + }); + }) + .catch((error) => { + // Handle any errors during the refund process + this.createNotificationError({ + title: 'Error', + message: 'Something went wrong with the refunds', + autoClose: false + }); + this.isLoading = false; // Ensure the loader is hidden even on error + }); + }); + } + }, + lineItemRefundBulk(lineItemId) { + return new Promise((resolve, reject) => { + this.VRPaymentRefundService.createRefund( + this.transactionData.transactions[0].metaData.salesChannelId, + this.transactionData.transactions[0].id, + 0, + lineItemId + ) + .then(() => { + this.createNotificationSuccess({ + title: this.$tc('vrpayment-order.refundAction.successTitle'), + message: this.$tc('vrpayment-order.refundAction.successMessage') + }); + resolve(); + }) + .catch((errorResponse) => { + try { + this.createNotificationError({ + title: errorResponse.response.data.errors[0].title, + message: errorResponse.response.data.errors[0].detail, + autoClose: false + }); + } catch (e) { + this.createNotificationError({ + title: errorResponse.title, + message: errorResponse.response.data, + autoClose: false + }); + } finally { + reject(); + } + }); + }); + }, + } +}); diff --git a/src/Resources/app/administration/src/module/vrpayment-order/page/vrpayment-order-detail/index.scss b/src/Resources/app/administration/src/module/vrpayment-order/page/vrpayment-order-detail/index.scss new file mode 100644 index 0000000..b0ffc12 --- /dev/null +++ b/src/Resources/app/administration/src/module/vrpayment-order/page/vrpayment-order-detail/index.scss @@ -0,0 +1,7 @@ +.vrpayment-order-detail__data { + display: grid; +} + +.vrpayment-order-detail__heading { + padding-top: 15px; +} diff --git a/src/Resources/app/administration/src/module/vrpayment-order/snippet/de-DE.json b/src/Resources/app/administration/src/module/vrpayment-order/snippet/de-DE.json new file mode 100644 index 0000000..d712fb1 --- /dev/null +++ b/src/Resources/app/administration/src/module/vrpayment-order/snippet/de-DE.json @@ -0,0 +1,116 @@ +{ + "vrpayment-order": { + "buttons": { + "label": { + "completion": "Abschluss", + "download-invoice": "Rechnung herunterladen", + "download-packing-slip": "Packzettel herunterladen", + "refund": "Eine neue Rückerstattung erstellen", + "void": "Genehmigung annullieren", + "refund-whole-line-item": "Gesamte Werbebuchung erstatten", + "refund-line-item-by-quantity": "Rückerstattung nach Menge", + "refund-line-item-selected": "Rückerstattung auswählen", + "refund-line-item-parial": "Teilweise Rückerstattung" + } + }, + "captureAction": { + "button": { + "text": "Zahlung erfassen" + }, + "currentAmount": "Betrag", + "isFinal": "Dies ist die endgültige Verbuchung", + "maxAmount": "Maximaler Betrag", + "successMessage": "Ihre Verbuchung war erfolgreich", + "successTitle": "Erfolg" + }, + "general": { + "title": "Bestellungen" + }, + "header": "VRPayment Payment", + "lineItem": { + "cardTitle": "Einzelposten", + "types": { + "amountIncludingTax": "Betrag", + "name": "Name", + "quantity": "Anzahl", + "taxAmount": "Steuern", + "type": "Typ", + "uniqueId": "Eindeutige ID" + } + }, + "modal": { + "title": { + "capture": "Erfassen", + "refund": "Neue Gutschrift", + "void": "Autorisierung aufheben" + } + }, + "paymentDetails": { + "cardTitle": "Zahlung", + "error": { + "title": "Fehler beim Abrufen von Zahlungsdetails von VRPayment" + } + }, + "refund": { + "cardTitle": "Gutschriften", + "refundAmount": { + "label": "Gutschriftsbetrag" + }, + "refundQuantity": { + "label": "Refund Menge" + }, + "types": { + "amount": "Betrag", + "createdOn": "Erstellt am", + "id": "ID", + "state": "Staat" + } + }, + "refundAction": { + "confirmButton": { + "text": "Ausführen" + }, + "refundAmount": { + "label": "Betrag", + "placeholder": "Einen Betrag eingeben" + }, + "successMessage": "Ihre Rückerstattung war erfolgreich", + "successTitle": "Erfolg", + "maxAvailableItemsToRefund": "Maximal Verfügbare Artikel zum Erstatten", + "maxAvailableAmountToRefund": "Maximal verfügbarer Erstattungsbetrag", + "refundExceedsTotalError": { + "title": "Fehler beim Erstellen der Rückerstattung.", + "messageRefundAmountExceedsAvailableBalance": "Der Rückerstattungsbetrag übersteigt das verfügbare Guthaben." + } + }, + "transactionHistory": { + "cardTitle": "Einzelheiten", + "types": { + "authorized_amount": "Autorisierter Betrag", + "currency": "Währung", + "customer": "Kunde", + "payment_method": "Zahlungsweise", + "state": "Staat", + "transaction": "Transaktion" + }, + "customerId": "Customer ID", + "customerName": "Customer Name", + "creditCardHolder": "Kreditkarteninhaber", + "paymentMethod": "Zahlungsart", + "paymentMethodBrand": "Marke der Zahlungsmethode", + "PseudoCreditCardNumber": "Pseudo-Kreditkartennummer", + "CardExpire": "Karte verfällt" + }, + "voidAction": { + "confirm": { + "button": { + "cancel": "Nein", + "confirm": "Autorisierung aufheben" + }, + "message": "Wollen Sie diese Zahlung wirklich stornieren?" + }, + "successMessage": "Die Zahlung wurde erfolgreich annulliert", + "successTitle": "Erfolg" + } + } +} diff --git a/src/Resources/app/administration/src/module/vrpayment-order/snippet/en-GB.json b/src/Resources/app/administration/src/module/vrpayment-order/snippet/en-GB.json new file mode 100644 index 0000000..370354c --- /dev/null +++ b/src/Resources/app/administration/src/module/vrpayment-order/snippet/en-GB.json @@ -0,0 +1,117 @@ +{ + "vrpayment-order": { + "buttons": { + "label": { + "completion": "Complete", + "download-invoice": "Download Invoice", + "download-packing-slip": "Download Packing Slip", + "refund": "Create a new refund", + "void": "Cancel authorization", + "refund-whole-line-item": "Refund whole line item", + "refund-line-item-by-quantity": "Refund by quantity", + "refund-line-item-selected": "Rembourser sélectionnés", + "refund-line-item-selected": "Refund selected", + "refund-line-item-parial": "Partial refund" + } + }, + "captureAction": { + "button": { + "text": "Capture payment" + }, + "currentAmount": "Amount", + "isFinal": "This is final capture", + "maxAmount": "Maximum amount", + "successMessage": "Your capture was successful.", + "successTitle": "Success" + }, + "general": { + "title": "Orders" + }, + "header": "VRPayment Payment", + "lineItem": { + "cardTitle": "Line Items", + "types": { + "amountIncludingTax": "Amount", + "name": "Name", + "quantity": "Quantity", + "taxAmount": "Taxes", + "type": "Type", + "uniqueId": "Unique ID" + } + }, + "modal": { + "title": { + "capture": "Capture", + "refund": "New refund", + "void": "Cancel authorization" + } + }, + "paymentDetails": { + "cardTitle": "Payment", + "error": { + "title": "Error fetching payment details from VRPayment" + } + }, + "refund": { + "cardTitle": "Refunds", + "refundAmount": { + "label": "Refund Amount" + }, + "refundQuantity": { + "label": "Refund Quantity" + }, + "types": { + "amount": "Amount", + "createdOn": "Created On", + "id": "ID", + "state": "State" + } + }, + "refundAction": { + "confirmButton": { + "text": "Execute" + }, + "refundAmount": { + "label": "Amount", + "placeholder": "Enter a amount" + }, + "successMessage": "Your refund was successful.", + "successTitle": "Success", + "maxAvailableItemsToRefund": "Maximum available items to refund", + "maxAvailableAmountToRefund": "Maximum available amount to refund", + "refundExceedsTotalError": { + "title": "Error while creating the refund.", + "messageRefundAmountExceedsAvailableBalance": "Refund amount exceeds available balance." + } + }, + "transactionHistory": { + "cardTitle": "Details", + "types": { + "authorized_amount": "Authorized Amount", + "currency": "Currency", + "customer": "Customer", + "payment_method": "Payment Method", + "state": "State", + "transaction": "Transaction" + }, + "customerId": "Customer ID", + "customerName": "Customer Name", + "creditCardHolder": "Credit Card Holder", + "paymentMethod": "Payment Method", + "paymentMethodBrand": "Payment Method Brand", + "PseudoCreditCardNumber": "Pseudo Credit Card Number", + "CardExpire": "Card Expire" + }, + "voidAction": { + "confirm": { + "button": { + "cancel": "No", + "confirm": "Cancel authorization" + }, + "message": "Do you really want to cancel this payment?" + }, + "successMessage": "The payment was successfully voided.", + "successTitle": "Success" + } + } +} diff --git a/src/Resources/app/administration/src/module/vrpayment-order/snippet/fr-FR.json b/src/Resources/app/administration/src/module/vrpayment-order/snippet/fr-FR.json new file mode 100644 index 0000000..b4e9f00 --- /dev/null +++ b/src/Resources/app/administration/src/module/vrpayment-order/snippet/fr-FR.json @@ -0,0 +1,116 @@ +{ + "vrpayment-order": { + "buttons": { + "label": { + "completion": "Terminée", + "download-invoice": "Télécharger la facture", + "download-packing-slip": "Télécharger le bordereau d'expédition", + "refund": "Créer un nouveau remboursement", + "void": "Annulez l'autorisation", + "refund-whole-line-item": "Remboursement de la ligne entière", + "refund-line-item-by-quantity": "Remboursement par quantité", + "refund-line-item-selected": "Rembourser sélectionnés", + "refund-line-item-parial": "Remboursement partiel" + } + }, + "captureAction": { + "button": { + "text": "Capture du paiement" + }, + "currentAmount": "Montant", + "isFinal": "C'est la capture finale", + "maxAmount": "Montant maximal", + "successMessage": "Votre capture a été réussie.", + "successTitle": "Succès" + }, + "general": { + "title": "Commandes" + }, + "header": "VRPayment Paiement", + "lineItem": { + "cardTitle": "Articles de ligne", + "types": { + "amountIncludingTax": "Montant", + "name": "Nom", + "quantity": "Quantité", + "taxAmount": "Taxes", + "type": "Type", + "uniqueId": "ID unique" + } + }, + "modal": { + "title": { + "capture": "Capture", + "refund": "Nouveau remboursement", + "void": "Annulez l'autorisation" + } + }, + "paymentDetails": { + "cardTitle": "Paiement", + "error": { + "title": "Erreur dans la récupération des détails du paiement à partir de VRPayment" + } + }, + "refund": { + "cardTitle": "Remboursements", + "refundAmount": { + "label": "Montant du remboursement" + }, + "refundQuantity": { + "label": "Quantité à rembourser" + }, + "types": { + "amount": "Montant", + "createdOn": "Créé le", + "id": "ID", + "state": "État" + } + }, + "refundAction": { + "confirmButton": { + "text": "Exécutez" + }, + "refundAmount": { + "label": "Montant", + "placeholder": "Entrez un montant" + }, + "successMessage": "Votre remboursement a été effectué avec succès.", + "successTitle": "Succès", + "maxAvailableItemsToRefund": "Nombre maximum d'articles disponibles pour le remboursement", + "maxAvailableAmountToRefund": "Montant maximal disponible pour le remboursement", + "refundExceedsTotalError": { + "title": "Erreur lors de la création du remboursement.", + "messageRefundAmountExceedsAvailableBalance": "Le montant du remboursement dépasse le solde disponible." + } + }, + "transactionHistory": { + "cardTitle": "Détails", + "types": { + "authorized_amount": "Montant autorisé", + "currency": "Monnaie", + "customer": "Client", + "payment_method": "Mode de paiement", + "state": "État", + "transaction": "Transaction" + }, + "customerId": "Customer ID", + "customerName": "Customer Name", + "creditCardHolder": "Titulaire de la carte de crédit", + "paymentMethod": "Mode de paiement", + "paymentMethodBrand": "Marque du mode de paiement", + "PseudoCreditCardNumber": "Pseudo numéro de carte de crédit", + "CardExpire": "La carte expire" + }, + "voidAction": { + "confirm": { + "button": { + "cancel": "Non", + "confirm": "Annulez l'autorisation" + }, + "message": "Voulez-vous vraiment annuler ce paiement?" + }, + "successMessage": "Le paiement a été annulé avec succès.", + "successTitle": "Succès" + } + } +} diff --git a/src/Resources/app/administration/src/module/vrpayment-order/snippet/it-IT.json b/src/Resources/app/administration/src/module/vrpayment-order/snippet/it-IT.json new file mode 100644 index 0000000..12a9ac8 --- /dev/null +++ b/src/Resources/app/administration/src/module/vrpayment-order/snippet/it-IT.json @@ -0,0 +1,116 @@ +{ + "vrpayment-order": { + "buttons": { + "label": { + "completion": "Completato", + "download-invoice": "Scarica fattura", + "download-packing-slip": "Scarica distinta di imballaggio", + "refund": "Crea un nuovo rimborso", + "void": "Annulla autorizzazione", + "refund-whole-line-item": "Rimborso intera riga", + "refund-line-item-by-quantity": "Rimborso per quantità", + "refund-line-item-selected": "Rimborso selezionati", + "refund-line-item-parial": "Rimborso parziale" + } + }, + "captureAction": { + "button": { + "text": "Cattura pagamento" + }, + "currentAmount": "Importo", + "isFinal": "Questa è la cattura finale", + "maxAmount": "Importo massimo", + "successMessage": "La tua cattura ha avuto successo.", + "successTitle": "Successo" + }, + "general": { + "title": "Ordini" + }, + "header": "Pagamento VRPayment", + "lineItem": { + "cardTitle": "Articoli di linea", + "types": { + "amountIncludingTax": "Importo", + "name": "Nome", + "quantity": "Quantità", + "taxAmount": "Tasse", + "type": "Tipo", + "uniqueId": "ID unico" + } + }, + "modal": { + "title": { + "capture": "Cattura", + "refund": "Nuovo rimborso", + "void": "Annulla autorizzazione" + } + }, + "paymentDetails": { + "cardTitle": "Pagamento", + "error": { + "title": "Errore nel recupero dei dettagli del pagamento da VRPayment" + } + }, + "refund": { + "cardTitle": "Rimborsi", + "refundAmount": { + "label": "Importo del rimborso" + }, + "refundQuantity": { + "label": "Quantità di rimborso" + }, + "types": { + "amount": "Importo", + "createdOn": "Creato il", + "id": "ID", + "state": "Stato" + } + }, + "refundAction": { + "confirmButton": { + "text": "Esegui" + }, + "refundAmount": { + "label": "Importo", + "placeholder": "Inserisci un importo" + }, + "successMessage": "Il tuo rimborso è andato a buon fine.", + "successTitle": "Successo", + "maxAvailableItemsToRefund": "Numero massimo di articoli disponibili da rimborsare", + "maxAvailableAmountToRefund": "Importo massimo disponibile per il rimborso", + "refundExceedsTotalError": { + "title": "Errore durante la creazione del rimborso.", + "messageRefundAmountExceedsAvailableBalance": "LL'importo del rimborso supera il saldo disponibile." + } + }, + "transactionHistory": { + "cardTitle": "Dettagli", + "types": { + "authorized_amount": "Importo autorizzato", + "currency": "Valuta", + "customer": "Cliente", + "payment_method": "Metodo di pagamento", + "state": "Stato", + "transaction": "Transazione" + }, + "customerId": "Customer ID", + "customerName": "Customer Name", + "creditCardHolder": "Proprietario della carta di credito", + "paymentMethod": "Metodo di pagamento", + "paymentMethodBrand": "Metodo di pagamento Marca", + "PseudoCreditCardNumber": "Numero di carta di credito pseudo", + "CardExpire": "La carta scade" + }, + "voidAction": { + "confirm": { + "button": { + "cancel": "No", + "confirm": "Annulla autorizzazione" + }, + "message": "Vuoi davvero annullare questo pagamento?" + }, + "successMessage": "Il pagamento è stato annullato con successo.", + "successTitle": "Successo" + } + } +} diff --git a/src/Resources/app/administration/src/module/vrpayment-settings/acl/index.js b/src/Resources/app/administration/src/module/vrpayment-settings/acl/index.js new file mode 100644 index 0000000..d512d74 --- /dev/null +++ b/src/Resources/app/administration/src/module/vrpayment-settings/acl/index.js @@ -0,0 +1,58 @@ +Shopware.Service('privileges').addPrivilegeMappingEntry({ + category: 'permissions', + parent: 'vrpayment', + key: 'vrpayment', + roles: { + viewer: { + privileges: [ + 'sales_channel:read', + 'sales_channel_payment_method:read', + 'system_config:read' + ], + dependencies: [] + }, + editor: { + privileges: [ + 'sales_channel:update', + 'sales_channel_payment_method:create', + 'sales_channel_payment_method:update', + 'system_config:update', + 'system_config:create', + 'system_config:delete' + ], + dependencies: [ + 'vrpayment.viewer' + ] + } + } +}); + +Shopware.Service('privileges').addPrivilegeMappingEntry({ + category: 'permissions', + parent: null, + key: 'sales_channel', + roles: { + viewer: { + privileges: [ + 'sales_channel_payment_method:read' + ] + }, + editor: { + privileges: [ + 'payment_method:update' + ] + }, + creator: { + privileges: [ + 'payment_method:create', + 'shipping_method:create', + 'delivery_time:create' + ] + }, + deleter: { + privileges: [ + 'payment_method:delete' + ] + } + } +}); diff --git a/src/Resources/app/administration/src/module/vrpayment-settings/component/sw-vrpayment-advanced-options/index.html.twig b/src/Resources/app/administration/src/module/vrpayment-settings/component/sw-vrpayment-advanced-options/index.html.twig new file mode 100644 index 0000000..68ca180 --- /dev/null +++ b/src/Resources/app/administration/src/module/vrpayment-settings/component/sw-vrpayment-advanced-options/index.html.twig @@ -0,0 +1,43 @@ + + +
+ + + + + + + +
+
+
+ diff --git a/src/Resources/app/administration/src/module/vrpayment-settings/component/sw-vrpayment-advanced-options/index.js b/src/Resources/app/administration/src/module/vrpayment-settings/component/sw-vrpayment-advanced-options/index.js new file mode 100644 index 0000000..c745cd8 --- /dev/null +++ b/src/Resources/app/administration/src/module/vrpayment-settings/component/sw-vrpayment-advanced-options/index.js @@ -0,0 +1,66 @@ +/* global Shopware */ + +import template from './index.html.twig'; +import constants from '../../page/vrpayment-settings/configuration-constants' + +const {Component, Mixin} = Shopware; + +Component.register('sw-vrpayment-advanced-options', { + template: template, + + name: 'VRPaymentAdvancedOptions', + + inject: [ + 'acl' + ], + + mixins: [ + Mixin.getByName('notification') + ], + + props: { + actualConfigData: { + type: Object, + required: true + }, + allConfigs: { + type: Object, + required: true + }, + selectedSalesChannelId: { + required: true + }, + isLoading: { + type: Boolean, + required: true + } + }, + + data() { + return { + ...constants + }; + }, + + methods: { + checkTextFieldInheritance(value) { + if (typeof value !== 'string') { + return true; + } + + return value.length <= 0; + }, + + checkNumberFieldInheritance(value) { + if (typeof value !== 'number') { + return true; + } + + return value.length <= 0; + }, + + checkBoolFieldInheritance(value) { + return typeof value !== 'boolean'; + } + } +}); diff --git a/src/Resources/app/administration/src/module/vrpayment-settings/component/sw-vrpayment-credentials/index.html.twig b/src/Resources/app/administration/src/module/vrpayment-settings/component/sw-vrpayment-credentials/index.html.twig new file mode 100644 index 0000000..117ac2d --- /dev/null +++ b/src/Resources/app/administration/src/module/vrpayment-settings/component/sw-vrpayment-credentials/index.html.twig @@ -0,0 +1,95 @@ +{% block vrpayment_settings_content_card_channel_config_credentials %} + + + {% block vrpayment_settings_content_card_channel_config_credentials_card_container %} + + + {% block vrpayment_settings_content_card_channel_config_credentials_card_container_settings %} +
+ + {% block vrpayment_settings_content_card_channel_config_credentials_card_container_settings_space_id %} + + + + {% endblock %} + + {% block vrpayment_settings_content_card_channel_config_credentials_card_container_settings_user_id %} + + + + {% endblock %} + + {% block vrpayment_settings_content_card_channel_config_credentials_card_container_settings_application_key %} + + + + {% endblock %} +
+ {% endblock %} + + {% verbatim %} + + + {{ $tc('vrpayment-settings.settingForm.credentials.button.label') }} + + + {% endverbatim %} + +
+ {% endblock %} +
+ +{% endblock %} diff --git a/src/Resources/app/administration/src/module/vrpayment-settings/component/sw-vrpayment-credentials/index.js b/src/Resources/app/administration/src/module/vrpayment-settings/component/sw-vrpayment-credentials/index.js new file mode 100644 index 0000000..4478e90 --- /dev/null +++ b/src/Resources/app/administration/src/module/vrpayment-settings/component/sw-vrpayment-credentials/index.js @@ -0,0 +1,105 @@ +/* global Shopware */ + +import template from './index.html.twig'; +import constants from '../../page/vrpayment-settings/configuration-constants' + +const {Component, Mixin} = Shopware; + +Component.register('sw-vrpayment-credentials', { + template: template, + + name: 'VRPaymentCredentials', + + inject: [ + 'acl' + ], + + mixins: [ + Mixin.getByName('notification') + ], + + props: { + actualConfigData: { + type: Object, + required: true + }, + allConfigs: { + type: Object, + required: true + }, + + selectedSalesChannelId: { + required: true + }, + spaceIdFilled: { + type: Boolean, + required: true + }, + spaceIdErrorState: { + required: true + }, + userIdFilled: { + type: Boolean, + required: true + }, + userIdErrorState: { + required: true + }, + applicationKeyFilled: { + type: Boolean, + required: true + }, + applicationKeyErrorState: { + required: true + }, + isLoading: { + type: Boolean, + required: true + }, + isTesting: { + type: Boolean, + required: false + } + }, + + data() { + return { + ...constants + }; + }, + + methods: { + + checkTextFieldInheritance(value) { + if (typeof value !== 'string') { + return true; + } + + return value.length <= 0; + }, + + checkNumberFieldInheritance(value) { + if (typeof value !== 'number') { + return true; + } + + return value.length <= 0; + }, + + checkBoolFieldInheritance(value) { + return typeof value !== 'boolean'; + }, + + // Emits the 'check-api-connection-event' with the current API connection parameters. + // Used to trigger API connection testing from this component. + emitCheckApiConnectionEvent() { + const apiConnectionParams = { + spaceId: this.actualConfigData[constants.CONFIG_SPACE_ID], + userId: this.actualConfigData[constants.CONFIG_USER_ID], + applicationKey: this.actualConfigData[constants.CONFIG_APPLICATION_KEY] + }; + + this.$emit('check-api-connection-event', apiConnectionParams); + } + } +}); diff --git a/src/Resources/app/administration/src/module/vrpayment-settings/component/sw-vrpayment-options/index.html.twig b/src/Resources/app/administration/src/module/vrpayment-settings/component/sw-vrpayment-options/index.html.twig new file mode 100644 index 0000000..252b010 --- /dev/null +++ b/src/Resources/app/administration/src/module/vrpayment-settings/component/sw-vrpayment-options/index.html.twig @@ -0,0 +1,97 @@ +{% block vrpayment_settings_content_card_channel_config_options %} + + + {% block vrpayment_settings_content_card_channel_config_credentials_card_container %} + + + {% block vrpayment_settings_content_card_channel_config_credentials_card_container_settings %} +
+ + {% block vrpayment_settings_content_card_channel_config_credentials_card_container_settings_space_view_id %} + + + + {% endblock %} + + {% block vrpayment_settings_content_card_channel_config_credentials_card_container_settings_integration %} + + + + {% endblock %} + + {% block vrpayment_settings_content_card_channel_config_credentials_card_container_settings_line_item_consistency_enabled %} + + + + {% endblock %} + + {% block vrpayment_settings_content_card_channel_config_credentials_card_container_settings_email_enabled %} + + + + {% endblock %} +
+ {% endblock %} +
+ {% endblock %} +
+ +{% endblock %} diff --git a/src/Resources/app/administration/src/module/vrpayment-settings/component/sw-vrpayment-options/index.js b/src/Resources/app/administration/src/module/vrpayment-settings/component/sw-vrpayment-options/index.js new file mode 100644 index 0000000..9c2e230 --- /dev/null +++ b/src/Resources/app/administration/src/module/vrpayment-settings/component/sw-vrpayment-options/index.js @@ -0,0 +1,77 @@ +/* global Shopware */ + +import template from './index.html.twig'; +import constants from '../../page/vrpayment-settings/configuration-constants' + +const {Component, Mixin} = Shopware; + +Component.register('sw-vrpayment-options', { + template: template, + + name: 'VRPaymentOptions', + + mixins: [ + Mixin.getByName('notification') + ], + + props: { + actualConfigData: { + type: Object, + required: true + }, + allConfigs: { + type: Object, + required: true + }, + selectedSalesChannelId: { + required: true + }, + isLoading: { + type: Boolean, + required: true + } + }, + + data() { + return { + ...constants + }; + }, + + computed: { + integrationOptions() { + return [ + { + id: 'payment_page', + name: this.$tc('vrpayment-settings.settingForm.options.integration.options.payment_page') + }, + { + id: 'iframe', + name: this.$tc('vrpayment-settings.settingForm.options.integration.options.iframe') + } + ]; + } + }, + + methods: { + checkTextFieldInheritance(value) { + if (typeof value !== 'string') { + return true; + } + + return value.length <= 0; + }, + + checkNumberFieldInheritance(value) { + if (typeof value !== 'number') { + return true; + } + + return value.length <= 0; + }, + + checkBoolFieldInheritance(value) { + return typeof value !== 'boolean'; + } + } +}); diff --git a/src/Resources/app/administration/src/module/vrpayment-settings/component/sw-vrpayment-settings-icon/index.html.twig b/src/Resources/app/administration/src/module/vrpayment-settings/component/sw-vrpayment-settings-icon/index.html.twig new file mode 100644 index 0000000..3fad643 --- /dev/null +++ b/src/Resources/app/administration/src/module/vrpayment-settings/component/sw-vrpayment-settings-icon/index.html.twig @@ -0,0 +1,31 @@ +{% block vrpayment_settings_icon %} + + + + + + + + + + +{% endblock %} diff --git a/src/Resources/app/administration/src/module/vrpayment-settings/component/sw-vrpayment-settings-icon/index.js b/src/Resources/app/administration/src/module/vrpayment-settings/component/sw-vrpayment-settings-icon/index.js new file mode 100644 index 0000000..100915d --- /dev/null +++ b/src/Resources/app/administration/src/module/vrpayment-settings/component/sw-vrpayment-settings-icon/index.js @@ -0,0 +1,7 @@ +import template from './index.html.twig'; + +const { Component } = Shopware; + +Component.register('sw-vrpayment-settings-icon', { + template +}); diff --git a/src/Resources/app/administration/src/module/vrpayment-settings/component/sw-vrpayment-storefront-options/index.html.twig b/src/Resources/app/administration/src/module/vrpayment-settings/component/sw-vrpayment-storefront-options/index.html.twig new file mode 100644 index 0000000..5da3aca --- /dev/null +++ b/src/Resources/app/administration/src/module/vrpayment-settings/component/sw-vrpayment-storefront-options/index.html.twig @@ -0,0 +1,25 @@ + + +
+ + + +
+
+
+ diff --git a/src/Resources/app/administration/src/module/vrpayment-settings/component/sw-vrpayment-storefront-options/index.js b/src/Resources/app/administration/src/module/vrpayment-settings/component/sw-vrpayment-storefront-options/index.js new file mode 100644 index 0000000..009d501 --- /dev/null +++ b/src/Resources/app/administration/src/module/vrpayment-settings/component/sw-vrpayment-storefront-options/index.js @@ -0,0 +1,62 @@ +/* global Shopware */ + +import template from './index.html.twig'; +import constants from '../../page/vrpayment-settings/configuration-constants' + +const {Component, Mixin} = Shopware; + +Component.register('sw-vrpayment-storefront-options', { + template: template, + + name: 'VRPaymentStorefrontOptions', + + mixins: [ + Mixin.getByName('notification') + ], + + props: { + actualConfigData: { + type: Object, + required: true + }, + allConfigs: { + type: Object, + required: true + }, + selectedSalesChannelId: { + required: true + }, + isLoading: { + type: Boolean, + required: true + } + }, + + data() { + return { + ...constants + }; + }, + + methods: { + checkTextFieldInheritance(value) { + if (typeof value !== 'string') { + return true; + } + + return value.length <= 0; + }, + + checkNumberFieldInheritance(value) { + if (typeof value !== 'number') { + return true; + } + + return value.length <= 0; + }, + + checkBoolFieldInheritance(value) { + return typeof value !== 'boolean'; + } + } +}); diff --git a/src/Resources/app/administration/src/module/vrpayment-settings/index.js b/src/Resources/app/administration/src/module/vrpayment-settings/index.js new file mode 100644 index 0000000..b43dfbf --- /dev/null +++ b/src/Resources/app/administration/src/module/vrpayment-settings/index.js @@ -0,0 +1,61 @@ +/* global Shopware */ + +import './acl'; +import './page/vrpayment-settings'; +import './component/sw-vrpayment-credentials'; +import './component/sw-vrpayment-options'; +import './component/sw-vrpayment-settings-icon'; +import './component/sw-vrpayment-storefront-options'; +import './component/sw-vrpayment-advanced-options'; + +import deDE from './snippet/de-DE.json'; +import enGB from './snippet/en-GB.json'; +import frFR from './snippet/fr-FR.json'; +import itIT from './snippet/it-IT.json'; + +const {Module} = Shopware; + +Module.register('vrpayment-settings', { + type: 'plugin', + name: 'VRPayment', + title: 'vrpayment-settings.general.descriptionTextModule', + description: 'vrpayment-settings.general.descriptionTextModule', + color: '#28d8ff', + icon: 'default-action-settings', + version: '1.0.1', + targetVersion: '1.0.1', + + snippets: { + 'de-DE': deDE, + 'en-GB': enGB, + 'fr-FR': frFR, + 'it-IT': itIT, + }, + + routes: { + index: { + component: 'vrpayment-settings', + path: 'index', + meta: { + parentPath: 'sw.settings.index', + privilege: 'vrpayment.viewer' + }, + props: { + default: (route) => { + return { + hash: route.params.hash, + }; + }, + }, + } + }, + + settingsItem: { + group: 'plugins', + to: 'vrpayment.settings.index', + iconComponent: 'sw-vrpayment-settings-icon', + backgroundEnabled: true, + privilege: 'vrpayment.viewer' + } + +}); diff --git a/src/Resources/app/administration/src/module/vrpayment-settings/page/vrpayment-settings/configuration-constants.js b/src/Resources/app/administration/src/module/vrpayment-settings/page/vrpayment-settings/configuration-constants.js new file mode 100644 index 0000000..15853d1 --- /dev/null +++ b/src/Resources/app/administration/src/module/vrpayment-settings/page/vrpayment-settings/configuration-constants.js @@ -0,0 +1,25 @@ +export const CONFIG_DOMAIN = 'VRPaymentPayment.config'; +export const CONFIG_APPLICATION_KEY = CONFIG_DOMAIN + '.' + 'applicationKey'; +export const CONFIG_EMAIL_ENABLED = CONFIG_DOMAIN + '.' + 'emailEnabled'; +export const CONFIG_INTEGRATION = CONFIG_DOMAIN + '.' + 'integration'; +export const CONFIG_LINE_ITEM_CONSISTENCY_ENABLED = CONFIG_DOMAIN + '.' + 'lineItemConsistencyEnabled'; +export const CONFIG_SPACE_ID = CONFIG_DOMAIN + '.' + 'spaceId'; +export const CONFIG_SPACE_VIEW_ID = CONFIG_DOMAIN + '.' + 'spaceViewId'; +export const CONFIG_STOREFRONT_INVOICE_DOWNLOAD_ENABLED = CONFIG_DOMAIN + '.' + 'storefrontInvoiceDownloadEnabled'; +export const CONFIG_USER_ID = CONFIG_DOMAIN + '.' + 'userId'; +export const CONFIG_STOREFRONT_WEBHOOKS_UPDATE_ENABLED = CONFIG_DOMAIN + '.' + 'storefrontWebhooksUpdateEnabled'; +export const CONFIG_STOREFRONT_PAYMENTS_UPDATE_ENABLED = CONFIG_DOMAIN + '.' + 'storefrontPaymentsUpdateEnabled'; + +export default { + CONFIG_DOMAIN, + CONFIG_APPLICATION_KEY, + CONFIG_EMAIL_ENABLED, + CONFIG_INTEGRATION, + CONFIG_LINE_ITEM_CONSISTENCY_ENABLED, + CONFIG_SPACE_ID, + CONFIG_SPACE_VIEW_ID, + CONFIG_STOREFRONT_INVOICE_DOWNLOAD_ENABLED, + CONFIG_USER_ID, + CONFIG_STOREFRONT_WEBHOOKS_UPDATE_ENABLED, + CONFIG_STOREFRONT_PAYMENTS_UPDATE_ENABLED +}; \ No newline at end of file diff --git a/src/Resources/app/administration/src/module/vrpayment-settings/page/vrpayment-settings/index.html.twig b/src/Resources/app/administration/src/module/vrpayment-settings/page/vrpayment-settings/index.html.twig new file mode 100644 index 0000000..377d7a2 --- /dev/null +++ b/src/Resources/app/administration/src/module/vrpayment-settings/page/vrpayment-settings/index.html.twig @@ -0,0 +1,146 @@ +{% block vrpayment_settings %} + + + {% block vrpayment_settings_header %} + + {% endblock %} + + {% block vrpayment_settings_actions %} + + {% endblock %} + + {% block vrpayment_settings_content %} + + {% endblock %} + +{% endblock %} diff --git a/src/Resources/app/administration/src/module/vrpayment-settings/page/vrpayment-settings/index.js b/src/Resources/app/administration/src/module/vrpayment-settings/page/vrpayment-settings/index.js new file mode 100644 index 0000000..56add61 --- /dev/null +++ b/src/Resources/app/administration/src/module/vrpayment-settings/page/vrpayment-settings/index.js @@ -0,0 +1,317 @@ +/* global Shopware */ + +import template from './index.html.twig'; +import constants from './configuration-constants'; + +const {Component, Mixin} = Shopware; + +Component.register('vrpayment-settings', { + + template: template, + + inject: [ + 'acl', + 'VRPaymentConfigurationService' + ], + + mixins: [ + Mixin.getByName('notification'), + Mixin.getByName('sw-inline-snippet') + ], + + data() { + return { + + config: {}, + + isLoading: false, + isTesting: false, + + isSaveSuccessful: false, + + applicationKeyFilled: false, + applicationKeyErrorState: false, + + spaceIdFilled: false, + spaceIdErrorState: false, + + userIdFilled: false, + userIdErrorState: false, + + isSetDefaultPaymentSuccessful: false, + isSettingDefaultPaymentMethods: false, + + configIntegrationDefaultValue: 'payment_page', + configEmailEnabledDefaultValue: true, + configLineItemConsistencyEnabledDefaultValue: true, + configStorefrontInvoiceDownloadEnabledEnabledDefaultValue: true, + configStorefrontWebhooksUpdateEnabledDefaultValue: true, + configStorefrontPaymentsUpdateEnabledDefaultValue: true, + + ...constants + }; + }, + + props: { + isLoading: { + type: Boolean, + required: true + } + }, + + metaInfo() { + return { + title: this.$createTitle() + }; + }, + + watch: { + config: { + handler(configData) { + const defaultConfig = this.$refs.configComponent.allConfigs.null; + const salesChannelId = this.$refs.configComponent.selectedSalesChannelId; + if (salesChannelId === null) { + + this.applicationKeyFilled = !!this.config[this.CONFIG_APPLICATION_KEY]; + this.spaceIdFilled = !!this.config[this.CONFIG_SPACE_ID]; + this.userIdFilled = !!this.config[this.CONFIG_USER_ID]; + + if (!(this.CONFIG_INTEGRATION in this.config)) { + this.config[this.CONFIG_INTEGRATION] = this.configIntegrationDefaultValue; + } + + if (!(this.CONFIG_EMAIL_ENABLED in this.config)) { + this.config[this.CONFIG_EMAIL_ENABLED] = this.configEmailEnabledDefaultValue; + } + + if (!(this.CONFIG_LINE_ITEM_CONSISTENCY_ENABLED in this.config)) { + this.config[this.CONFIG_LINE_ITEM_CONSISTENCY_ENABLED] = this.configLineItemConsistencyEnabledDefaultValue; + } + + if (!(this.CONFIG_STOREFRONT_INVOICE_DOWNLOAD_ENABLED in this.config)) { + this.config[this.CONFIG_STOREFRONT_INVOICE_DOWNLOAD_ENABLED] = this.configStorefrontInvoiceDownloadEnabledEnabledDefaultValue; + } + + if (!(this.CONFIG_STOREFRONT_WEBHOOKS_UPDATE_ENABLED in this.config)) { + this.config[this.CONFIG_STOREFRONT_WEBHOOKS_UPDATE_ENABLED] = this.configStorefrontWebhooksUpdateEnabledDefaultValue; + } + + if (!(this.CONFIG_STOREFRONT_PAYMENTS_UPDATE_ENABLED in this.config)) { + this.config[this.CONFIG_STOREFRONT_PAYMENTS_UPDATE_ENABLED] = this.configStorefrontPaymentsUpdateEnabledDefaultValue; + } + + } else { + + this.applicationKeyFilled = !!this.config[this.CONFIG_APPLICATION_KEY] || !!defaultConfig[this.CONFIG_APPLICATION_KEY]; + this.spaceIdFilled = !!this.config[this.CONFIG_SPACE_ID] || !!defaultConfig[this.CONFIG_SPACE_ID]; + this.userIdFilled = !!this.config[this.CONFIG_USER_ID] || !!defaultConfig[this.CONFIG_USER_ID]; + + + if (!(this.CONFIG_INTEGRATION in this.config) || !(this.CONFIG_INTEGRATION in defaultConfig)) { + this.config[this.CONFIG_INTEGRATION] = this.configIntegrationDefaultValue; + } + + if (!(this.CONFIG_EMAIL_ENABLED in this.config) || !(this.CONFIG_EMAIL_ENABLED in defaultConfig)) { + this.config[this.CONFIG_EMAIL_ENABLED] = this.configEmailEnabledDefaultValue; + } + + if (!(this.CONFIG_LINE_ITEM_CONSISTENCY_ENABLED in this.config) || !(this.CONFIG_LINE_ITEM_CONSISTENCY_ENABLED in defaultConfig)) { + this.config[this.CONFIG_LINE_ITEM_CONSISTENCY_ENABLED] = this.configLineItemConsistencyEnabledDefaultValue; + } + + if (!(this.CONFIG_STOREFRONT_INVOICE_DOWNLOAD_ENABLED in this.config) || !(this.CONFIG_STOREFRONT_INVOICE_DOWNLOAD_ENABLED in defaultConfig)) { + this.config[this.CONFIG_STOREFRONT_INVOICE_DOWNLOAD_ENABLED] = this.configStorefrontInvoiceDownloadEnabledEnabledDefaultValue; + } + + if (!(this.CONFIG_STOREFRONT_WEBHOOKS_UPDATE_ENABLED in this.config) || !(this.CONFIG_STOREFRONT_WEBHOOKS_UPDATE_ENABLED in defaultConfig)) { + this.config[this.CONFIG_STOREFRONT_WEBHOOKS_UPDATE_ENABLED] = this.configStorefrontWebhooksUpdateEnabledDefaultValue; + } + + if (!(this.CONFIG_STOREFRONT_PAYMENTS_UPDATE_ENABLED in this.config) || !(this.CONFIG_STOREFRONT_PAYMENTS_UPDATE_ENABLED in defaultConfig)) { + this.config[this.CONFIG_STOREFRONT_PAYMENTS_UPDATE_ENABLED] = this.configStorefrontPaymentsUpdateEnabledDefaultValue; + } + } + + this.$emit('salesChannelChanged'); + this.$emit('update:value', configData); + }, + deep: true + } + }, + + methods: { + checkTextFieldInheritance(value) { + if (typeof value !== 'string') { + return true; + } + + return value.length <= 0; + }, + + checkNumberFieldInheritance(value) { + if (typeof value !== 'number') { + 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]; + } + }, + + onSave() { + if (!(this.spaceIdFilled && this.userIdFilled && this.applicationKeyFilled)) { + this.setErrorStates(); + return; + } + this.save(); + }, + + save() { + this.isLoading = true; + + this.$refs.configComponent.save().then((res) => { + if (res) { + this.config = res; + } + this.registerWebHooks(); + this.synchronizePaymentMethodConfiguration(); + this.installOrderDeliveryStates(); + }).catch((e) => { + console.error('Error:', e); + this.isLoading = false; + }); + }, + + registerWebHooks() { + if (this.config[this.CONFIG_STOREFRONT_WEBHOOKS_UPDATE_ENABLED] === false) { + return false; + } + + this.VRPaymentConfigurationService.registerWebHooks(this.$refs.configComponent.selectedSalesChannelId) + .then(() => { + this.createNotificationSuccess({ + title: this.$tc('vrpayment-settings.settingForm.titleSuccess'), + message: this.$tc('vrpayment-settings.settingForm.messageWebHookUpdated') + }); + }).catch((e) => { + this.createNotificationError({ + title: this.$tc('vrpayment-settings.settingForm.titleError'), + message: this.$tc('vrpayment-settings.settingForm.messageWebHookError') + }); + this.isLoading = false; + console.error('Error:', e); + }); + }, + + synchronizePaymentMethodConfiguration() { + if (this.config[this.CONFIG_STOREFRONT_PAYMENTS_UPDATE_ENABLED] === false) { + return false; + } + + this.VRPaymentConfigurationService.synchronizePaymentMethodConfiguration(this.$refs.configComponent.selectedSalesChannelId) + .then(() => { + this.createNotificationSuccess({ + title: this.$tc('vrpayment-settings.settingForm.titleSuccess'), + message: this.$tc('vrpayment-settings.settingForm.messagePaymentMethodConfigurationUpdated') + }); + this.isLoading = false; + }).catch((e) => { + this.createNotificationError({ + title: this.$tc('vrpayment-settings.settingForm.titleError'), + message: this.$tc('vrpayment-settings.settingForm.messagePaymentMethodConfigurationError') + }); + this.isLoading = false; + console.error('Error:', e); + }); + }, + + installOrderDeliveryStates(){ + this.VRPaymentConfigurationService.installOrderDeliveryStates() + .then(() => { + this.createNotificationSuccess({ + title: this.$tc('vrpayment-settings.settingForm.titleSuccess'), + message: this.$tc('vrpayment-settings.settingForm.messageOrderDeliveryStateUpdated') + }); + this.isLoading = false; + }).catch(() => { + this.createNotificationError({ + title: this.$tc('vrpayment-settings.settingForm.titleError'), + message: this.$tc('vrpayment-settings.settingForm.messageOrderDeliveryStateError') + }); + this.isLoading = false; + }); + }, + + onSetPaymentMethodDefault() { + this.isSettingDefaultPaymentMethods = true; + this.VRPaymentConfigurationService.setVRPaymentAsSalesChannelPaymentDefault( + this.$refs.configComponent.selectedSalesChannelId + ).then(() => { + this.isSettingDefaultPaymentMethods = false; + this.isSetDefaultPaymentSuccessful = true; + this.createNotificationSuccess({ + title: this.$tc('vrpayment-settings.settingForm.titleSuccess'), + message: this.$tc('vrpayment-settings.salesChannelCard.messageDefaultPaymentUpdated') + }); + }); + }, + + setErrorStates() { + const messageNotBlankErrorState = { + code: 1, + detail: this.$tc('vrpayment-settings.messageNotBlank') + }; + + if (!this.spaceIdFilled) { + this.spaceIdErrorState = messageNotBlankErrorState; + } + + if (!this.userIdFilled) { + this.userIdErrorState = messageNotBlankErrorState; + } + + if (!this.applicationKeyFilled) { + this.applicationKeyErrorState = messageNotBlankErrorState; + } + }, + + // Handles the 'check-api-connection-event'. + // Uses the provided apiConnectionData to perform API connection checks. + onCheckApiConnection(apiConnectionData) { + const { spaceId, userId, applicationKey } = apiConnectionData; + this.isTesting = true; + + this.VRPaymentConfigurationService.checkApiConnection(spaceId, userId, applicationKey) + .then((res) => { + if (res.result === 200) { + this.createNotificationSuccess({ + title: this.$tc('vrpayment-settings.settingForm.credentials.alert.title'), + message: this.$tc('vrpayment-settings.settingForm.credentials.alert.successMessage') + }); + } else { + this.createNotificationError({ + title: this.$tc('vrpayment-settings.settingForm.credentials.alert.title'), + message: this.$tc('vrpayment-settings.settingForm.credentials.alert.errorMessage') + }); + } + this.isTesting = false; + }).catch(() => { + this.createNotificationError({ + title: this.$tc('vrpayment-settings.settingForm.credentials.alert.title'), + message: this.$tc('vrpayment-settings.settingForm.credentials.alert.errorMessage') + }); + this.isTesting = false; + }); + } + } +}); diff --git a/src/Resources/app/administration/src/module/vrpayment-settings/snippet/de-DE.json b/src/Resources/app/administration/src/module/vrpayment-settings/snippet/de-DE.json new file mode 100644 index 0000000..e0a79c8 --- /dev/null +++ b/src/Resources/app/administration/src/module/vrpayment-settings/snippet/de-DE.json @@ -0,0 +1,105 @@ +{ + "sw-privileges": { + "permissions": { + "parents": { + "vrpayment": "VRPayment plugin" + }, + "vrpayment": { + "label": "VRPayment berechtigungen" + } + } + }, + "vrpayment-settings": { + "general": { + "descriptionTextModule": "VRPayment-Einstellungen", + "mainMenuItemGeneral": "VRPayment" + }, + "header": "VRPayment", + "messageNotBlank": "Dieser Wert sollte nicht leer sein.", + "salesChannelCard": { + "button": { + "description": "Klicken Sie auf diese Schaltfläche, um VRPayment als Standard-Zahlungsabwickler im ausgewählten Vertriebskanal festzulegen", + "label": "VRPayment als Standard-Zahlungsabwickler festlegen" + }, + "messageDefaultPaymentError": "VRPayment als Standard-Zahlungsabwickler konnte nicht festgelegt werden..", + "messageDefaultPaymentUpdated": "VRPayment als Standard-Zahlungsabwickler wurde festgelegt." + }, + "settingForm": { + "credentials": { + "applicationKey": { + "label": "Application Key", + "tooltipText": "Der Anwendungsschlüssel wird verwendet, um dieses Plugin mit der API VRPayment zu authentifizieren." + }, + "cardTitle": "Anmeldedaten", + "spaceId": { + "label": "Space ID", + "tooltipText": "Die Space ID wird verwendet, um dieses Plugin mit der API VRPayment zu authentifizieren." + }, + "userId": { + "label": "User ID", + "tooltipText": "Die Benutzer-ID wird verwendet, um dieses Plugin mit der VRPayment-API zu authentifizieren." + }, + "button": { + "description": "Klicken Sie auf diese Schaltfläche, um die VRPayment API zu testen", + "label": "API Verbindung testen" + }, + "alert": { + "title": "API-Test", + "successMessage": "Die Verbindung wurde erfolgreich getestet.", + "errorMessage": "Die Verbindung ist fehlgeschlagen. Versuchen Sie es erneut." + } + }, + "messageSaveSuccess": "VRPayment-Einstellungen wurden gespeichert.", + "messageOrderDeliveryStateError": "VRPayment OrderDeliveryState konnte nicht gespeichert werden.", + "messageOrderDeliveryStateUpdated": "VRPayment OrderDeliveryState wurde aktualisiert.", + "messagePaymentMethodConfigurationError": "VRPayment PaymentMethodConfiguration konnte nicht gespeichert werden. Bitte überprüfen Sie Ihre Anmeldedaten.", + "messagePaymentMethodConfigurationUpdated": "VRPayment PaymentMethodConfiguration wurde registriert.", + "messageWebHookError": "VRPayment WebHook konnte nicht gespeichert werden. Bitte überprüfen Sie Ihre Zugangsdaten.", + "messageWebHookUpdated": "VRPayment WebHook wurde aktualisiert.", + "options": { + "cardTitle": "Optionen", + "emailEnabled": { + "label": "Auftragsbestätigung per E-Mail senden", + "tooltipText": "Wenn diese Einstellung aktiviert ist, erhalten Ihre Kunden eine E-Mail von Ihrem Geschäft, wenn die Zahlung ihrer Bestellung autorisiert ist." + }, + "integration": { + "label": "Integration", + "options": { + "iframe": "Iframe", + "payment_page": "Payment Page" + }, + "tooltipText": "Integration" + }, + "lineItemConsistencyEnabled": { + "label": "Konsistenz der Einzelposten", + "tooltipText": "Wenn diese Option aktiviert ist, stimmen die Summen der Einzelposten in VRPaymentPayment immer mit der Shopware-Bestellsumme überein." + }, + "spaceViewId": { + "label": "Space View ID", + "tooltipText": "Space View ID" + } + }, + "save": "Speichern", + "storefrontOptions": { + "cardTitle": "Storefront-Optionen", + "invoiceDownloadEnabled": { + "label": "Rechnung Download", + "tooltipText": "Wenn diese Einstellung aktiviert ist, können Ihre Kunden Auftragsrechnungen von VRPayment herunterladen." + } + }, + "advancedOptions": { + "cardTitle": "Erweiterte-Optionen", + "webhooksUpdateEnabled": { + "label": "Webhooks-Update", + "tooltipText": "Wenn diese Einstellung aktiviert ist, wird das Webhook-Update ausgelöst, wenn Sie die Einstellungen speichern" + }, + "paymentsUpdateEnabled": { + "label": "Payments-Update", + "tooltipText": "Wenn diese Einstellung aktiviert ist, wird die Aktualisierung der Zahlungsmethoden ausgelöst, wenn Sie die Einstellungen speichern" + } + }, + "titleError": "Fehler", + "titleSuccess": "Erfolg" + } + } +} \ No newline at end of file diff --git a/src/Resources/app/administration/src/module/vrpayment-settings/snippet/en-GB.json b/src/Resources/app/administration/src/module/vrpayment-settings/snippet/en-GB.json new file mode 100644 index 0000000..506d73f --- /dev/null +++ b/src/Resources/app/administration/src/module/vrpayment-settings/snippet/en-GB.json @@ -0,0 +1,105 @@ +{ + "sw-privileges": { + "permissions": { + "parents": { + "vrpayment": "VRPayment plugin" + }, + "vrpayment": { + "label": "VRPayment permissions" + } + } + }, + "vrpayment-settings": { + "general": { + "descriptionTextModule": "VRPayment settings", + "mainMenuItemGeneral": "VRPayment" + }, + "header": "VRPayment", + "messageNotBlank": "This value should not be blank.", + "salesChannelCard": { + "button": { + "description": "Click this button to set VRPayment as default payment handler in the selected SalesChannel", + "label": "Set VRPayment as default payment handler" + }, + "messageDefaultPaymentError": "VRPayment as default payment could not be set.", + "messageDefaultPaymentUpdated": "VRPayment as default payment has been set." + }, + "settingForm": { + "credentials": { + "applicationKey": { + "label": "Application Key", + "tooltipText": "The Application Key is used to authenticate this plugin with the VRPayment API." + }, + "cardTitle": "Credentials", + "spaceId": { + "label": "Space ID", + "tooltipText": "The space ID is used to authenticate this plugin with the VRPayment API." + }, + "userId": { + "label": "User ID", + "tooltipText": "The user ID is used to authenticate this plugin with the VRPayment API." + }, + "button": { + "description": "Click this button to test the VRPayment API", + "label": "API connection test" + }, + "alert": { + "title": "API Test", + "successMessage": "The connection was successfully tested.", + "errorMessage": "The connection was failed. Try it again." + } + }, + "messageSaveSuccess": "VRPayment settings have been saved.", + "messageOrderDeliveryStateError": "VRPayment OrderDeliveryState could not be saved.", + "messageOrderDeliveryStateUpdated": "VRPayment OrderDeliveryState has been updated.", + "messagePaymentMethodConfigurationError": "VRPayment PaymentMethodConfiguration could not be saved. Please check your credentials.", + "messagePaymentMethodConfigurationUpdated": "VRPayment PaymentMethodConfiguration has been registered.", + "messageWebHookError": "VRPayment WebHook could not be saved. Please check your credentials.", + "messageWebHookUpdated": "VRPayment WebHook has been updated.", + "options": { + "cardTitle": "Options", + "emailEnabled": { + "label": "Send order confirmation email", + "tooltipText": "If this setting is enabled your customers will receive an email from your store when their order payment is authorised" + }, + "integration": { + "label": "Integration", + "options": { + "iframe": "Iframe", + "payment_page": "Payment Page" + }, + "tooltipText": "Integration" + }, + "lineItemConsistencyEnabled": { + "label": "Line item consistency", + "tooltipText": "If this option is enabled line item totals in VRPaymentPayment will always match Shopware order total" + }, + "spaceViewId": { + "label": "Space View ID", + "tooltipText": "Space View ID" + } + }, + "save": "Save", + "storefrontOptions": { + "cardTitle": "Storefront Options", + "invoiceDownloadEnabled": { + "label": "Invoice Download", + "tooltipText": "If this setting is enabled your customers will be able to download order invoices from VRPayment" + } + }, + "advancedOptions": { + "cardTitle": "Advanced Options", + "webhooksUpdateEnabled": { + "label": "Webhooks Update", + "tooltipText": "If this setting is enabled webhook update will be triggered when you save settings" + }, + "paymentsUpdateEnabled": { + "label": "Payments Update", + "tooltipText": "If this setting is enabled payment methods update will be triggered when you save settings" + } + }, + "titleError": "Error", + "titleSuccess": "Success" + } + } +} \ No newline at end of file diff --git a/src/Resources/app/administration/src/module/vrpayment-settings/snippet/fr-FR.json b/src/Resources/app/administration/src/module/vrpayment-settings/snippet/fr-FR.json new file mode 100644 index 0000000..d8f2445 --- /dev/null +++ b/src/Resources/app/administration/src/module/vrpayment-settings/snippet/fr-FR.json @@ -0,0 +1,105 @@ +{ + "sw-privileges": { + "permissions": { + "parents": { + "vrpayment": "VRPayment brancher" + }, + "vrpayment": { + "label": "VRPayment autorisations" + } + } + }, + "vrpayment-settings": { + "general": { + "descriptionTextModule": "Paramètres de VRPayment", + "mainMenuItemGeneral": "VRPayment" + }, + "header": "VRPayment", + "messageNotBlank": "Cette valeur ne doit pas être vide.", + "salesChannelCard": { + "button": { + "description": "Cliquez sur ce bouton pour définir VRPayment comme gestionnaire de paiement par défaut dans le canal de vente sélectionné.", + "label": "Définir VRPayment comme gestionnaire de paiement par défaut" + }, + "messageDefaultPaymentError": "VRPayment comme paiement par défaut n'a pas pu être défini.", + "messageDefaultPaymentUpdated": "VRPayment comme paiement par défaut a été défini." + }, + "settingForm": { + "credentials": { + "applicationKey": { + "label": "Application Key", + "tooltipText": "La clé d'application est utilisée pour authentifier ce plugin avec l'API." + }, + "cardTitle": "Références", + "spaceId": { + "label": "Space ID", + "tooltipText": "L'ID de l'espace est utilisé pour authentifier ce plugin avec l'API VRPayment.." + }, + "userId": { + "label": "User ID", + "tooltipText": "L'ID utilisateur est utilisé pour authentifier ce plugin avec l'API VRPayment." + }, + "button": { + "description": "Cliquez sur ce bouton pour tester l'API VRPayment.", + "label": "Test de connexion à l'API" + }, + "alert": { + "title": "Test API", + "successMessage": "La connexion a été testée avec succès.", + "errorMessage": "La connexion a échoué. Réessayez." + } + }, + "messageSaveSuccess": "Les paramètres de VRPayment ont été enregistrés.", + "messageOrderDeliveryStateError": "Les paramètres de VRPayment OrderDeliveryState n'ont pas pu être enregistrés.", + "messageOrderDeliveryStateUpdated": "VRPayment OrderDeliveryState a été mis à jour.", + "messagePaymentMethodConfigurationError": "VRPayment PaymentMethodConfiguration n'a pas pu être enregistré. Veuillez vérifier vos informations d'identification.", + "messagePaymentMethodConfigurationUpdated": "VRPayment PaymentMethodConfiguration a été enregistré.", + "messageWebHookError": "VRPayment WebHook n'a pas pu être enregistré. Veuillez vérifier vos informations d'identification.", + "messageWebHookUpdated": "VRPayment WebHook a été mis à jour.", + "options": { + "cardTitle": "Options", + "emailEnabled": { + "label": "Envoyer un e-mail de confirmation de commande", + "tooltipText": "If this setting is enabled your customers will receive an email from your store when their order payment is authorised" + }, + "integration": { + "label": "Integration", + "options": { + "iframe": "Iframe", + "payment_page": "Page de paiement" + }, + "tooltipText": "Integration" + }, + "lineItemConsistencyEnabled": { + "label": "Cohérence des postes de ligne", + "tooltipText": "Si cette option est activée, les totaux des articles dans VRPaymentPayment correspondront toujours au total de la commande Shopware." + }, + "spaceViewId": { + "label": "Space View ID", + "tooltipText": "Space View ID" + } + }, + "save": "Enregistrer", + "storefrontOptions": { + "cardTitle": "Storefront Options", + "invoiceDownloadEnabled": { + "label": "Téléchargement de facture", + "tooltipText": "Si ce paramètre est activé, vos clients pourront télécharger les factures de commande depuis VRPayment" + } + }, + "advancedOptions": { + "cardTitle": "Options avancées", + "webhooksUpdateEnabled": { + "label": "Mise à jour des webhooks", + "tooltipText": "Si ce paramètre est activé, la mise à jour des webhooks sera déclenchée lorsque vous enregistrerez les paramètres." + }, + "paymentsUpdateEnabled": { + "label": "Mise à jour des paiements", + "tooltipText": "Si ce paramètre est activé, la mise à jour des méthodes de paiement sera déclenchée lorsque vous enregistrez les paramètres." + } + }, + "titleError": "Erreur", + "titleSuccess": "Succès" + } + } + } \ No newline at end of file diff --git a/src/Resources/app/administration/src/module/vrpayment-settings/snippet/it-IT.json b/src/Resources/app/administration/src/module/vrpayment-settings/snippet/it-IT.json new file mode 100644 index 0000000..ef8f8b0 --- /dev/null +++ b/src/Resources/app/administration/src/module/vrpayment-settings/snippet/it-IT.json @@ -0,0 +1,105 @@ +{ + "sw-privileges": { + "permissions": { + "parents": { + "vrpayment": "VRPayment brancher" + }, + "vrpayment": { + "label": "VRPayment autorisations" + } + } + }, + "vrpayment-settings": { + "general": { + "descriptionTextModule": "Impostazioni VRPayment", + "mainMenuItemGeneral": "VRPayment" + }, + "header": "VRPayment", + "messageNotBlank": "Questo valore non dovrebbe essere vuoto.", + "salesChannelCard": { + "button": { + "description": "Fai clic su questo pulsante per impostare VRPayment come gestore di pagamento predefinito nel SalesChannel selezionato", + "label": "Imposta VRPayment come gestore di pagamento predefinito" + }, + "messageDefaultPaymentError": "Non è stato possibile impostare VRPayment come pagamento predefinito.", + "messageDefaultPaymentUpdated": "VRPayment come pagamento predefinito è stato impostato." + }, + "settingForm": { + "credentials": { + "applicationKey": { + "label": "Chiave di applicazione", + "tooltipText": "La chiave dell'applicazione è usata per autenticare questo plugin con l'API VRPayment." + }, + "cardTitle": "Credenziali", + "spaceId": { + "label": "ID spazio", + "tooltipText": "L'ID dello spazio è usato per autenticare questo plugin con l'API VRPayment." + }, + "userId": { + "label": "ID utente", + "tooltipText": "L'ID utente è usato per autenticare questo plugin con l'API VRPayment." + }, + "button": { + "description": "Fare clic su questo pulsante per testare l'API VRPayment.", + "label": "Test di connessione API" + }, + "alert": { + "title": "Test API", + "successMessage": "La connessione è stata testata con successo.", + "errorMessage": "La connessione è fallita. Riprovare." + } + }, + "messageSaveSuccess": "Le impostazioni di VRPayment sono state salvate.", + "messageOrderDeliveryStateError": "VRPayment OrderDeliveryState non può essere salvato.", + "messageOrderDeliveryStateUpdated": "VRPayment OrderDeliveryState è stato aggiornato.", + "messagePaymentMethodConfigurationError": "VRPayment PaymentMethodConfiguration non può essere salvato. Per favore controlla le tue credenziali.", + "messagePaymentMethodConfigurationUpdated": "VRPayment PaymentMethodConfiguration è stato registrato.", + "messageWebHookError": "VRPayment WebHook non può essere salvato. Per favore controlla le tue credenziali.", + "messageWebHookUpdated": "VRPayment WebHook è stato aggiornato.", + "options": { + "cardTitle": "Opzioni", + "emailEnabled": { + "label": "Invia email di conferma dell'ordine", + "tooltipText": "Se questa impostazione è abilitata i tuoi clienti riceveranno un'email dal tuo negozio quando il pagamento del loro ordine sarà autorizzato" + }, + "integration": { + "label": "Integrazione", + "options": { + "iframe": "Iframe", + "payment_page": "Pagina di pagamento" + }, + "tooltipText": "Integrazione" + }, + "lineItemConsistencyEnabled": { + "label": "Coerenza dell'elemento linea", + "tooltipText": "Se questa opzione è abilitata i totali degli articoli in VRPaymentPayment corrisponderanno sempre al totale dell'ordine Shopware" + }, + "spaceViewId": { + "label": "ID della vista spazio", + "tooltipText": "ID della vista spaziale" + } + }, + "save": "Salva", + "storefrontOptions": { + "cardTitle": "Opzioni vetrina", + "invoiceDownloadEnabled": { + "label": "Scaricamento fattura", + "tooltipText": "Se questa impostazione è abilitata i tuoi clienti potranno scaricare le fatture degli ordini da VRPayment" + } + }, + "advancedOptions": { + "cardTitle": "Opzioni avanzate", + "webhooksUpdateEnabled": { + "label": "Aggiornamento webhooks", + "tooltipText": "Se questa impostazione è abilitata l'aggiornamento dei webhook sarà attivato quando si salvano le impostazioni" + }, + "paymentsUpdateEnabled": { + "label": "Aggiornamento pagamenti", + "tooltipText": "Se questa impostazione è abilitata l'aggiornamento dei metodi di pagamento verrà attivato quando si salvano le impostazioni" + } + }, + "titleError": "Errore", + "titleSuccess": "Successo" + } + } +} diff --git a/src/Resources/app/storefront/dist/storefront/js/v-r-payment-payment/v-r-payment-payment.js b/src/Resources/app/storefront/dist/storefront/js/v-r-payment-payment/v-r-payment-payment.js new file mode 100644 index 0000000..7b271f7 --- /dev/null +++ b/src/Resources/app/storefront/dist/storefront/js/v-r-payment-payment/v-r-payment-payment.js @@ -0,0 +1 @@ +(()=>{"use strict";var e={156: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)?o(Array.isArray(e)?[]:{},e,t):e}function s(e,t,r){return e.concat(t).map(function(e){return n(e,r)})}function i(e){return Object.keys(e).concat(Object.getOwnPropertySymbols?Object.getOwnPropertySymbols(e).filter(function(t){return Object.propertyIsEnumerable.call(e,t)}):[])}function a(e,t){try{return t in e}catch(e){return!1}}function o(e,r,l){(l=l||{}).arrayMerge=l.arrayMerge||s,l.isMergeableObject=l.isMergeableObject||t,l.cloneUnlessOtherwiseSpecified=n;var u,c,h=Array.isArray(r);return h!==Array.isArray(e)?n(r,l):h?l.arrayMerge(e,r,l):(c={},(u=l).isMergeableObject(e)&&i(e).forEach(function(t){c[t]=n(e[t],u)}),i(r).forEach(function(t){(!a(e,t)||Object.hasOwnProperty.call(e,t)&&Object.propertyIsEnumerable.call(e,t))&&(a(e,t)&&u.isMergeableObject(r[t])?c[t]=(function(e,t){if(!t.customMerge)return o;var r=t.customMerge(e);return"function"==typeof r?r:o})(t,u)(e[t],r[t],u):c[t]=n(r[t],u))}),c)}o.all=function(e,t){if(!Array.isArray(e))throw Error("first argument should be an array");return e.reduce(function(e,r){return o(e,r,t)},{})},e.exports=o}},t={};function r(n){var s=t[n];if(void 0!==s)return s.exports;var i=t[n]={exports:{}};return e[n](i,i.exports,r),i.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 n=r(156),s=r.n(n);class i{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=i.toUpperCamelCase(e,t);return i.lcFirst(r)}static toUpperCamelCase(e,t){return t?e.split(t).map(e=>i.ucFirst(e.toLowerCase())).join(""):i.ucFirst(e.toLowerCase())}static parsePrimitive(e){try{return/^\d+(.|,)\d+$/.test(e)&&(e=e.replace(",",".")),JSON.parse(e)}catch(t){return e.toString()}}}class a{constructor(e=document){this._el=e,e.$emitter=this,this._listeners=[]}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,s=e.split("."),i=r.scope?t.bind(r.scope):t;if(r.once&&!0===r.once){let t=i;i=function(r){n.unsubscribe(e),t(r)}}return this.el.addEventListener(s[0],i),this.listeners.push({splitEventName:s,opts:r,cb:i}),!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}}class o{constructor(e,t={},r=!1){if(!(e instanceof Node))throw Error("There is no valid element given.");this.el=e,this.$emitter=new a(this.el),this._pluginName=this._getPluginName(r),this.options=this._mergeOptions(t),this._initialized=!1,this._registerInstance(),this._init()}init(){throw Error(`The "init" method for the plugin "${this._pluginName}" is not defined.`)}update(){}_init(){this._initialized||(this.init(),this._initialized=!0)}_update(){this._initialized&&this.update()}_mergeOptions(e){let t=[this.constructor.options,this.options,e];return t.push(this._getConfigFromDataAttribute()),t.push(this._getOptionsFromDataAttribute()),s().all(t.filter(e=>e instanceof Object&&!(e instanceof Array)).map(e=>e||{}))}_getConfigFromDataAttribute(){let e={};if("function"!=typeof this.el.getAttribute)return e;let t=i.toDashCase(this._pluginName),r=this.el.getAttribute(`data-${t}-config`);return r?window.PluginConfigManager.get(this._pluginName,r):e}_getOptionsFromDataAttribute(){let e={};if("function"!=typeof this.el.getAttribute)return e;let t=i.toDashCase(this._pluginName),r=this.el.getAttribute(`data-${t}-options`);if(r)try{return JSON.parse(r)}catch(e){console.error(`The data attribute "data-${t}-options" could not be parsed to json: ${e.message}`)}return 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}}class l{constructor(){this._request=null,this._errorHandlingInternal=!1}get(e,t){let r=arguments.length>2&&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 s=this._createPreparedRequest("POST",e,n);return this._sendRequest(s,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 s=this._createPreparedRequest("DELETE",e,n);return this._sendRequest(s,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 s=this._createPreparedRequest("PATCH",e,n);return this._sendRequest(s,t,r)}abort(){if(this._request)return this._request.abort()}setErrorHandlingInternal(e){this._errorHandlingInternal=e}_registerOnLoaded(e,t){t&&(!0===this._errorHandlingInternal?(e.addEventListener("load",()=>{t(e.responseText,e)}),e.addEventListener("abort",()=>{console.warn(`the request to ${e.responseURL} was aborted`)}),e.addEventListener("error",()=>{console.warn(`the request to ${e.responseURL} failed with status ${e.status}`)}),e.addEventListener("timeout",()=>{console.warn(`the request to ${e.responseURL} timed out`)})):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}}class u extends o{static #e=this.options={payment_method_tabs:"ul.vrpayment-payment-panel li",payment_method_iframe_prefix:"iframe_payment_method_",payment_method_iframe_class:".vrpayment-payment-iframe",payment_method_handler_name:"vrpayment_payment_handler",payment_method_handler_prefix:"vrpayment_handler_",payment_method_handler_status:'input[name="vrpayment_payment_handler_validation_status"]',payment_form:"confirmOrderForm"};init(){this._client=new l(window.accessKey)}}window.PluginManager.register("VRPaymentCheckoutPlugin",u,"[data-vrpayment-checkout-plugin]")})(); \ No newline at end of file diff --git a/src/Resources/app/storefront/src/main.js b/src/Resources/app/storefront/src/main.js new file mode 100644 index 0000000..429526e --- /dev/null +++ b/src/Resources/app/storefront/src/main.js @@ -0,0 +1,16 @@ +// Import all necessary Storefront plugins and scss files +import VRPaymentCheckoutPlugin + from './vrpayment-checkout-plugin/vrpayment-checkout-plugin.plugin'; + +// Register them via the existing PluginManager +const PluginManager = window.PluginManager; +PluginManager.register( + 'VRPaymentCheckoutPlugin', + VRPaymentCheckoutPlugin, + '[data-vrpayment-checkout-plugin]' +); + +if (module.hot) { + // noinspection JSValidateTypes + module.hot.accept(); +} \ No newline at end of file diff --git a/src/Resources/app/storefront/src/scss/base.scss b/src/Resources/app/storefront/src/scss/base.scss new file mode 100644 index 0000000..2a1dd3e --- /dev/null +++ b/src/Resources/app/storefront/src/scss/base.scss @@ -0,0 +1,65 @@ +.vrpayment-payment { + .vrpayment-payment-panel { + position: relative; + } + + /* Center the loader */ + #vrpaymentLoader { + margin: 3em 0; + + div { + left: 50%; + top: 50%; + width: 7.5em; + height: 7.5em; + margin: 0 auto; + border: 0.5em solid #f3f3f3; + border-radius: 50%; + border-top: 0.5em solid #008490; + animation: spin 2s linear infinite; + -moz-animation: spin 2s linear infinite; + -webkit-animation: spin 2s linear infinite; + -o-animation: spin 2s linear infinite; + } + } + + #confirmOrderForm { + overflow: hidden; + } + + @-moz-keyframes spin { + 0% { + -webkit-transform: rotate(0deg); + } + 100% { + -webkit-transform: rotate(360deg); + } + } + + @keyframes spin { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } + } + + @-o-keyframes spin { + 0% { + -webkit-transform: rotate(0deg); + } + 100% { + -webkit-transform: rotate(360deg); + } + } + + @-webkit-keyframes spin { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } + } +} diff --git a/src/Resources/app/storefront/src/vrpayment-checkout-plugin/vrpayment-checkout-plugin.plugin.js b/src/Resources/app/storefront/src/vrpayment-checkout-plugin/vrpayment-checkout-plugin.plugin.js new file mode 100644 index 0000000..5c0c033 --- /dev/null +++ b/src/Resources/app/storefront/src/vrpayment-checkout-plugin/vrpayment-checkout-plugin.plugin.js @@ -0,0 +1,27 @@ +/* eslint-disable import/no-unresolved */ + +// noinspection NpmUsedModulesInstalled +import Plugin from 'src/plugin-system/plugin.class'; +import HttpClient from 'src/service/http-client.service'; + + +class VRPaymentCheckoutPlugin extends Plugin { + + static options = { + payment_method_tabs: 'ul.vrpayment-payment-panel li', + payment_method_iframe_prefix: 'iframe_payment_method_', + payment_method_iframe_class: '.vrpayment-payment-iframe', + payment_method_handler_name: 'vrpayment_payment_handler', + payment_method_handler_prefix: 'vrpayment_handler_', + payment_method_handler_status: 'input[name="vrpayment_payment_handler_validation_status"]', + payment_form: 'confirmOrderForm', + }; + + init() { + // @TODO Move JS to Plugin + this._client = new HttpClient(window.accessKey); + } + +} + +export default VRPaymentCheckoutPlugin; \ No newline at end of file diff --git a/src/Resources/config/packages/monolog.yaml b/src/Resources/config/packages/monolog.yaml new file mode 100644 index 0000000..bf1c251 --- /dev/null +++ b/src/Resources/config/packages/monolog.yaml @@ -0,0 +1,9 @@ +monolog: + channels: ['vrpayment_payment'] + handlers: + security: + # log all messages (since debug is the lowest level) + level: debug + type: stream + path: '%kernel.logs_dir%/vrpayment.log' + channels: [ 'vrpayment_payment' ] diff --git a/src/Resources/config/plugin.png b/src/Resources/config/plugin.png new file mode 100644 index 0000000000000000000000000000000000000000..11fd7cbf2e192bff3aedab89cf7a7ee2b2738f4b GIT binary patch literal 665 zcmV;K0%rY*P)004R> z004l5008;`004mK004C`008P>0026e000+ooVrmw00004XF*Lt006O$eEU(800001 zb5ch_0Itp)=>Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L01m_e01m_fl`9S#0005- zNkl3_-=T6?3L>c^92udNyfRLGp1VtFsLKB3@{8=T=47Er|aN$Y} z6bf1dfeJz^``L}sro|A5phH<1DoBP4W2PxZv6&I4b9x;M?pkmY-vgKP^L^(#&pqc} zKoA5$5ClOGgrlR}gS(TTMq*#L@3`kqp75mK_FX7NX!;>GQv)b;uEMAQER=I~9!8r( z=$X0%Wwk?@3`Vc_9@(ZRauee%FWpnNd$60)6@H&Lf4DV(M{mAhsHF<^!!y2IQ0oNc zu28BC8gpzDFt~`|I~z(B!ns`1x zSh$Ayapomi_XdK~NsKi||2bK1!BdW%S&9F`;>Hf{HJ(P-!~)6+9i0n$^$Tv=!D3mi z+GFto3T81@xBqu}Ersq=BEXIE*iYo-fD|7;v>Br4F54L8Iu4hZa^8jmPpY4_Dmk&_ zmOH@X9?W<7&-L1`tIztjlVufW*1OjR?Qp@zt-;9RW5k~C;C$ybzVR!jrn%YY+zr&+ zTBW!{@fD0WMNzlf2BW_*KEwIjFxCUMet4}}%9X%U-zolqFUXTajFM@1ohW{rD)!^3 zZ7;$5$>rn!y2DeKdEHOtHx~p!5ClOG{%igKF_WP!=#_Y%00000NkvXXu0mjfe&8aQ literal 0 HcmV?d00001 diff --git a/src/Resources/config/routes.xml b/src/Resources/config/routes.xml new file mode 100644 index 0000000..dc96175 --- /dev/null +++ b/src/Resources/config/routes.xml @@ -0,0 +1,8 @@ + + + + + diff --git a/src/Resources/config/services.xml b/src/Resources/config/services.xml new file mode 100644 index 0000000..b0ce66c --- /dev/null +++ b/src/Resources/config/services.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Resources/config/services/core/api/configuration.xml b/src/Resources/config/services/core/api/configuration.xml new file mode 100644 index 0000000..32d3644 --- /dev/null +++ b/src/Resources/config/services/core/api/configuration.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Resources/config/services/core/api/order_delivery_state.xml b/src/Resources/config/services/core/api/order_delivery_state.xml new file mode 100644 index 0000000..c6b21e5 --- /dev/null +++ b/src/Resources/config/services/core/api/order_delivery_state.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Resources/config/services/core/api/payment_method_configuration.xml b/src/Resources/config/services/core/api/payment_method_configuration.xml new file mode 100644 index 0000000..843d07b --- /dev/null +++ b/src/Resources/config/services/core/api/payment_method_configuration.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Resources/config/services/core/api/refund.xml b/src/Resources/config/services/core/api/refund.xml new file mode 100644 index 0000000..6ecf374 --- /dev/null +++ b/src/Resources/config/services/core/api/refund.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Resources/config/services/core/api/space.xml b/src/Resources/config/services/core/api/space.xml new file mode 100644 index 0000000..aab44c6 --- /dev/null +++ b/src/Resources/config/services/core/api/space.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + diff --git a/src/Resources/config/services/core/api/transaction.xml b/src/Resources/config/services/core/api/transaction.xml new file mode 100644 index 0000000..1ab8f77 --- /dev/null +++ b/src/Resources/config/services/core/api/transaction.xml @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Resources/config/services/core/api/webhooks.xml b/src/Resources/config/services/core/api/webhooks.xml new file mode 100644 index 0000000..fa6e6ad --- /dev/null +++ b/src/Resources/config/services/core/api/webhooks.xml @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Resources/config/services/core/checkout.xml b/src/Resources/config/services/core/checkout.xml new file mode 100644 index 0000000..efeef09 --- /dev/null +++ b/src/Resources/config/services/core/checkout.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Resources/config/services/core/settings.xml b/src/Resources/config/services/core/settings.xml new file mode 100644 index 0000000..73d15ff --- /dev/null +++ b/src/Resources/config/services/core/settings.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Resources/config/services/core/storefront/account.xml b/src/Resources/config/services/core/storefront/account.xml new file mode 100644 index 0000000..11447b3 --- /dev/null +++ b/src/Resources/config/services/core/storefront/account.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Resources/config/services/core/storefront/checkout.xml b/src/Resources/config/services/core/storefront/checkout.xml new file mode 100644 index 0000000..427f4d5 --- /dev/null +++ b/src/Resources/config/services/core/storefront/checkout.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Resources/config/services/core/util.xml b/src/Resources/config/services/core/util.xml new file mode 100644 index 0000000..35ec889 --- /dev/null +++ b/src/Resources/config/services/core/util.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Resources/public/administration/.vite/entrypoints.json b/src/Resources/public/administration/.vite/entrypoints.json new file mode 100644 index 0000000..a1b018c --- /dev/null +++ b/src/Resources/public/administration/.vite/entrypoints.json @@ -0,0 +1,25 @@ +{ + "base": "/bundles/vrpaymentpayment/administration/", + "entryPoints": { + "v-r-payment-payment": { + "css": [ + "/bundles/vrpaymentpayment/administration/assets/v-r-payment-payment-D4AH6HY2.css" + ], + "dynamic": [], + "js": [ + "/bundles/vrpaymentpayment/administration/assets/v-r-payment-payment-C6eiDWfX.js" + ], + "legacy": false, + "preload": [] + } + }, + "legacy": false, + "metadatas": {}, + "version": [ + "7.1.0", + 7, + 1, + 0 + ], + "viteServer": null +} \ No newline at end of file diff --git a/src/Resources/public/administration/.vite/manifest.json b/src/Resources/public/administration/.vite/manifest.json new file mode 100644 index 0000000..076a386 --- /dev/null +++ b/src/Resources/public/administration/.vite/manifest.json @@ -0,0 +1,11 @@ +{ + "main.js": { + "file": "assets/v-r-payment-payment-C6eiDWfX.js", + "name": "v-r-payment-payment", + "src": "main.js", + "isEntry": true, + "css": [ + "assets/v-r-payment-payment-D4AH6HY2.css" + ] + } +} \ No newline at end of file diff --git a/src/Resources/public/administration/assets/v-r-payment-payment-C6eiDWfX.js b/src/Resources/public/administration/assets/v-r-payment-payment-C6eiDWfX.js new file mode 100644 index 0000000..4246b25 --- /dev/null +++ b/src/Resources/public/administration/assets/v-r-payment-payment-C6eiDWfX.js @@ -0,0 +1,2 @@ +const A=`{% block sw_order_detail_content_tabs_general %} {% parent %} {# sw-tabs-item will dissappear. See: https://github.com/shopware/shopware/blob/trunk/UPGRADE-6.7.md#sw-tabs-is-removed #} {{ $tc('vrpayment-order.header') }} {% endblock %} {% block sw_order_detail_actions_slot_smart_bar_actions %} {% endblock %}`,{Component:T,Context:g}=Shopware,P=Shopware.Data.Criteria,D="handler_vrpaymentpayment_vrpaymentpaymenthandler";T.override("sw-order-detail",{template:A,data(){return{isVRPaymentPayment:!1}},computed:{isEditable(){return!this.isVRPaymentPayment||this.$route.name!=="vrpayment.order.detail"},showTabs(){return!0}},watch:{orderId:{deep:!0,handler(){if(!this.orderId){this.setIsVRPaymentPayment(null);return}const e=this.repositoryFactory.create("order"),t=new P(1,1);t.addAssociation("transactions"),e.get(this.orderId,g.api,t).then(a=>{if(a.amountTotal<=0||a.transactions.length<=0||!a.transactions[0].paymentMethodId){this.setIsVRPaymentPayment(null);return}const n=a.transactions[0].paymentMethodId;n!=null&&this.setIsVRPaymentPayment(n)})},immediate:!0}},methods:{setIsVRPaymentPayment(e){if(!e)return;this.repositoryFactory.create("payment_method").get(e,g.api).then(a=>{this.isVRPaymentPayment=a.formattedHandlerIdentifier===D})}}});const N=`{% block vrpayment_order_action_completion %} {% block vrpayment_order_action_completion_amount %} {% endblock %} {% block vrpayment_order_action_completion_confirm_button %} {% endblock %} {% endblock %}`,{Component:k,Mixin:$,Filter:R,Utils:y}=Shopware;k.register("vrpayment-order-action-completion",{template:N,inject:["VRPaymentTransactionCompletionService"],mixins:[$.getByName("notification")],props:{transactionData:{type:Object,required:!0}},data(){return{isLoading:!0,isCompletion:!1}},computed:{dateFilter(){return R.getByName("date")}},created(){this.createdComponent()},methods:{createdComponent(){this.isLoading=!1},completion(){this.isCompletion&&(this.isLoading=!0,this.VRPaymentTransactionCompletionService.createTransactionCompletion(this.transactionData.transactions[0].metaData.salesChannelId,this.transactionData.transactions[0].id).then(()=>{this.createNotificationSuccess({title:this.$tc("vrpayment-order.captureAction.successTitle"),message:this.$tc("vrpayment-order.captureAction.successMessage")}),this.isLoading=!1,this.$emit("modal-close"),this.$nextTick(()=>{this.$router.replace(`${this.$route.path}?hash=${y.createId()}`)})}).catch(e=>{try{this.createNotificationError({title:e.response.data.errors[0].title,message:e.response.data.errors[0].detail,autoClose:!1})}catch{this.createNotificationError({title:e.title,message:e.message,autoClose:!1})}finally{this.isLoading=!1,this.$emit("modal-close"),this.$nextTick(()=>{this.$router.replace(`${this.$route.path}?hash=${y.createId()}`)})}}))}}});const x=`{% block vrpayment_order_action_refund %} {% block vrpayment_order_action_refund_amount %}
{{ $tc('vrpayment-order.refundAction.maxAvailableItemsToRefund') }}: {{ this.$parent.$parent.itemRefundableQuantity }}
{% endblock %} {% block vrpayment_order_action_refund_confirm_button %} {% endblock %}
{% endblock %}`,{Component:O,Mixin:F,Filter:V,Utils:f}=Shopware;O.register("vrpayment-order-action-refund",{template:x,inject:["VRPaymentRefundService"],mixins:[F.getByName("notification")],props:{transactionData:{type:Object,required:!0},orderId:{type:String,required:!0}},data(){return{refundQuantity:0,isLoading:!0,currentLineItem:""}},computed:{dateFilter(){return V.getByName("date")}},created(){this.createdComponent()},methods:{createdComponent(){this.isLoading=!1,this.refundQuantity=1},refund(){this.isLoading=!0,this.VRPaymentRefundService.createRefund(this.transactionData.transactions[0].metaData.salesChannelId,this.transactionData.transactions[0].id,this.refundQuantity,this.$parent.$parent.currentLineItem).then(()=>{this.createNotificationSuccess({title:this.$tc("vrpayment-order.refundAction.successTitle"),message:this.$tc("vrpayment-order.refundAction.successMessage")}),this.isLoading=!1,this.$emit("modal-close"),this.$nextTick(()=>{this.$router.replace(`${this.$route.path}?hash=${f.createId()}`)})}).catch(e=>{try{this.createNotificationError({title:e.response.data.errors[0].title,message:e.response.data.errors[0].detail,autoClose:!1})}catch{this.createNotificationError({title:e.title,message:e.message,autoClose:!1})}finally{this.isLoading=!1,this.$emit("modal-close"),this.$nextTick(()=>{this.$router.replace(`${this.$route.path}?hash=${f.createId()}`)})}})}}});const L=`{% block vrpayment_order_action_refund_partial %} {% block vrpayment_order_action_refund_amount_partial %}
{{ $tc('vrpayment-order.refundAction.maxAvailableAmountToRefund') }}: {{ this.$parent.$parent.itemRefundableAmount }}
{% endblock %} {% block vrpayment_order_action_refund_confirm_button_partial %} {% endblock %}
{% endblock %}`,{Component:M,Mixin:B,Filter:z,Utils:b}=Shopware;M.register("vrpayment-order-action-refund-partial",{template:L,inject:["VRPaymentRefundService"],mixins:[B.getByName("notification")],props:{transactionData:{type:Object,required:!0},orderId:{type:String,required:!0}},data(){return{isLoading:!0,currency:this.transactionData.transactions[0].currency,refundAmount:0}},computed:{dateFilter(){return z.getByName("date")}},created(){this.createdComponent()},methods:{createdComponent(){this.isLoading=!1,this.currency=this.transactionData.transactions[0].currency,this.refundAmount||(this.refundAmount=this.$parent.$parent.itemRefundableAmount)},createPartialRefund(e){this.isLoading=!0,this.VRPaymentRefundService.createPartialRefund(this.transactionData.transactions[0].metaData.salesChannelId,this.transactionData.transactions[0].id,this.refundAmount,e).then(()=>{this.createNotificationSuccess({title:this.$tc("vrpayment-order.refundAction.successTitle"),message:this.$tc("vrpayment-order.refundAction.successMessage")}),this.isLoading=!1,this.$emit("modal-close"),this.$nextTick(()=>{this.$router.replace(`${this.$route.path}?hash=${b.createId()}`)})}).catch(t=>{try{this.createNotificationError({title:t.response.data.errors[0].title,message:t.response.data.errors[0].detail,autoClose:!1})}catch{this.createNotificationError({title:t.title,message:t.message,autoClose:!1})}finally{this.isLoading=!1,this.$emit("modal-close"),this.$nextTick(()=>{this.$router.replace(`${this.$route.path}?hash=${b.createId()}`)})}})}},watch:{refundAmount(e){e!==null&&(this.refundAmount=Math.round(e*100)/100)}}});const G=`{% block vrpayment_order_action_refund_by_amount %} {% block vrpayment_order_action_refund_amount_by_amount %} {% endblock %} {% block vrpayment_order_action_refund_confirm_button_by_amount %} {% endblock %} {% endblock %}`,{Component:U,Mixin:q,Filter:H,Utils:v}=Shopware;U.register("vrpayment-order-action-refund-by-amount",{template:G,inject:["VRPaymentRefundService"],mixins:[q.getByName("notification")],props:{transactionData:{type:Object,required:!0},orderId:{type:String,required:!0}},data(){return{isLoading:!0,currency:this.transactionData.transactions[0].currency,refundAmount:0,refundableAmount:0}},computed:{dateFilter(){return H.getByName("date")}},created(){this.createdComponent()},methods:{createdComponent(){this.isLoading=!1,this.currency=this.transactionData.transactions[0].currency,this.refundAmount=Number(this.transactionData.transactions[0].amountIncludingTax),this.refundableAmount=Number(this.transactionData.transactions[0].amountIncludingTax)},refundByAmount(){this.isLoading=!0,this.VRPaymentRefundService.createRefundByAmount(this.transactionData.transactions[0].metaData.salesChannelId,this.transactionData.transactions[0].id,this.refundAmount).then(()=>{this.createNotificationSuccess({title:this.$tc("vrpayment-order.refundAction.successTitle"),message:this.$tc("vrpayment-order.refundAction.successMessage")}),this.isLoading=!1,this.$emit("modal-close"),this.$nextTick(()=>{this.$router.replace(`${this.$route.path}?hash=${v.createId()}`)})}).catch(e=>{try{var t,a;e.response.data=="refundExceedsAmount"?(t=this.$tc("vrpayment-order.refundAction.refundExceedsTotalError.title"),a=this.$tc("vrpayment-order.refundAction.refundExceedsTotalError.messageRefundAmountExceedsAvailableBalance")):(t=e.response.data.errors[0].title,a=e.response.data.errors[0].detail),this.createNotificationError({title:t,message:a,autoClose:!1})}catch{this.createNotificationError({title:e.title,message:e.message,autoClose:!1})}finally{this.isLoading=!1,this.$emit("modal-close"),this.$nextTick(()=>{this.$router.replace(`${this.$route.path}?hash=${v.createId()}`)})}})}}});const W=`{% block vrpayment_order_action_void %} {% block vrpayment_order_action_void_amount %} {# Review if this v-model:checked="isVoid" needs to change to checked #} {% endblock %} {% block vrpayment_order_action_void_confirm_button %} {% endblock %} {% endblock %}`,{Component:K,Mixin:Q,Filter:Y,Utils:_}=Shopware;K.register("vrpayment-order-action-void",{template:W,inject:["VRPaymentTransactionVoidService"],mixins:[Q.getByName("notification")],props:{transactionData:{type:Object,required:!0}},data(){return{isLoading:!0,isVoid:!1}},computed:{dateFilter(){return Y.getByName("date")},lineItemColumns(){return[{property:"uniqueId",label:this.$tc("vrpayment-order.refund.types.uniqueId"),rawData:!1,allowResize:!0,primary:!0,width:"auto"},{property:"name",label:this.$tc("vrpayment-order.refund.types.name"),rawData:!0,allowResize:!0,sortable:!0,width:"auto"},{property:"quantity",label:this.$tc("vrpayment-order.refund.types.quantity"),rawData:!0,allowResize:!0,width:"auto"},{property:"amountIncludingTax",label:this.$tc("vrpayment-order.refund.types.amountIncludingTax"),rawData:!0,allowResize:!0,inlineEdit:"string",width:"auto"},{property:"type",label:this.$tc("vrpayment-order.refund.types.type"),rawData:!0,allowResize:!0,sortable:!0,width:"auto"},{property:"taxAmount",label:this.$tc("vrpayment-order.refund.types.taxAmount"),rawData:!0,allowResize:!0,width:"auto"}]}},created(){this.createdComponent()},methods:{createdComponent(){this.isLoading=!1,this.currency=this.transactionData.transactions[0].currency,this.refundableAmount=this.transactionData.transactions[0].amountIncludingTax,this.refundAmount=this.transactionData.transactions[0].amountIncludingTax},voidPayment(){this.isVoid&&(this.isLoading=!0,this.VRPaymentTransactionVoidService.createTransactionVoid(this.transactionData.transactions[0].metaData.salesChannelId,this.transactionData.transactions[0].id).then(()=>{this.createNotificationSuccess({title:this.$tc("vrpayment-order.voidAction.successTitle"),message:this.$tc("vrpayment-order.voidAction.successMessage")}),this.isLoading=!1,this.$emit("modal-close"),this.$nextTick(()=>{this.$router.replace(`${this.$route.path}?hash=${_.createId()}`)})}).catch(e=>{try{this.createNotificationError({title:e.response.data.errors[0].title,message:e.response.data.errors[0].detail,autoClose:!1})}catch{this.createNotificationError({title:e.title,message:e.message,autoClose:!1})}finally{this.isLoading=!1,this.$emit("modal-close"),this.$nextTick(()=>{this.$router.replace(`${this.$route.path}?hash=${_.createId()}`)})}}))}}});const j=`{% block vrpayment_order_detail %}
{% block vrpayment_order_transaction_history_card %} {% endblock %} {% block vrpayment_order_transaction_line_items_card %} {% endblock %} {% block vrpayment_order_transaction_refunds_card %} {% endblock %} {% block vrpayment_order_actions_modal_refund_partial %} {% endblock %} {% block vrpayment_order_actions_modal_refund %} {% endblock %} {% block vrpayment_order_actions_modal_refund_by_amount %} {% endblock %} {% block vrpayment_order_actions_modal_completion%} {% endblock %} {% block vrpayment_order_actions_modal_void %} {% endblock %}
{% endblock %}`,{Component:Z,Mixin:J,Filter:X,Context:ee,Utils:u}=Shopware,I=Shopware.Data.Criteria;Z.register("vrpayment-order-detail",{template:j,inject:["VRPaymentTransactionService","VRPaymentRefundService","repositoryFactory"],mixins:[J.getByName("notification")],data(){return{transactionData:{transactions:[],refunds:[]},transaction:{},lineItems:[],refundableQuantity:0,itemRefundableQuantity:0,isLoading:!0,orderId:"",currency:"",modalType:"",refundAmount:0,refundableAmount:0,itemRefundedAmount:0,itemRefundedQuantity:0,itemRefundableAmount:0,currentLineItem:"",refundLineItemQuantity:[],refundLineItemAmount:[],selectedItems:[]}},metaInfo(){return{title:this.$tc("vrpayment-order.header")}},computed:{dateFilter(){return X.getByName("date")},relatedResourceColumns(){return[{property:"paymentMethodName",label:this.$tc("vrpayment-order.transactionHistory.types.payment_method"),rawData:!0},{property:"state",label:this.$tc("vrpayment-order.transactionHistory.types.state"),rawData:!0},{property:"currency",label:this.$tc("vrpayment-order.transactionHistory.types.currency"),rawData:!0},{property:"authorized_amount",label:this.$tc("vrpayment-order.transactionHistory.types.authorized_amount"),rawData:!0},{property:"id",label:this.$tc("vrpayment-order.transactionHistory.types.transaction"),rawData:!0},{property:"customerId",label:this.$tc("vrpayment-order.transactionHistory.types.customer"),rawData:!0}]},lineItemColumns(){return[{property:"id",rawData:!0,visible:!1,primary:!0},{property:"uniqueId",label:this.$tc("vrpayment-order.lineItem.types.uniqueId"),rawData:!0,visible:!1,primary:!0},{property:"name",label:this.$tc("vrpayment-order.lineItem.types.name"),rawData:!0},{property:"quantity",label:this.$tc("vrpayment-order.lineItem.types.quantity"),rawData:!0},{property:"amountIncludingTax",label:this.$tc("vrpayment-order.lineItem.types.amountIncludingTax"),rawData:!0},{property:"type",label:this.$tc("vrpayment-order.lineItem.types.type"),rawData:!0},{property:"taxAmount",label:this.$tc("vrpayment-order.lineItem.types.taxAmount"),rawData:!0},{property:"refundableQuantity",rawData:!0,visible:!1}]},refundColumns(){return[{property:"id",label:this.$tc("vrpayment-order.refund.types.id"),rawData:!0,visible:!0,primary:!0},{property:"amount",label:this.$tc("vrpayment-order.refund.types.amount"),rawData:!0},{property:"state",label:this.$tc("vrpayment-order.refund.types.state"),rawData:!0},{property:"createdOn",label:this.$tc("vrpayment-order.refund.types.createdOn"),rawData:!0}]}},watch:{$route(){this.resetDataAttributes(),this.createdComponent()}},created(){this.createdComponent()},methods:{createdComponent(){this.orderId=this.$route.params.id;const e=this.repositoryFactory.create("order"),t=new I(1,1);t.addAssociation("transactions"),t.getAssociation("transactions").addSorting(I.sort("createdAt","DESC")),e.get(this.orderId,ee.api,t).then(a=>{this.order=a,this.isLoading=!1;var n=0,r=0;const o=a.transactions[0].customFields.vrpayment_transaction_id;this.VRPaymentTransactionService.getTransactionData(a.salesChannelId,o).then(s=>{this.currency=s.transactions[0].currency,s.transactions[0].authorized_amount=u.format.currency(s.transactions[0].authorizationAmount,this.currency),s.refunds.forEach(i=>{r=parseFloat(parseFloat(r)+parseFloat(i.amount)),i.amount=u.format.currency(i.amount,this.currency),i.reductions.forEach(l=>{l.quantityReduction>0&&(this.refundLineItemQuantity[l.lineItemUniqueId]===void 0?this.refundLineItemQuantity[l.lineItemUniqueId]=l.quantityReduction:this.refundLineItemQuantity[l.lineItemUniqueId]+=l.quantityReduction),l.unitPriceReduction>0&&(this.refundLineItemAmount[l.lineItemUniqueId]===void 0?this.refundLineItemAmount[l.lineItemUniqueId]=l.unitPriceReduction:this.refundLineItemAmount[l.lineItemUniqueId]+=l.unitPriceReduction)})}),s.transactions[0].lineItems.forEach(i=>{i.id||(i.id=i.uniqueId),i.itemRefundedAmount=parseFloat(this.refundLineItemAmount[i.uniqueId]||0)*parseInt(i.quantity),i.amountIncludingTax=parseFloat(i.amountIncludingTax)||0,i.itemRefundedQuantity=parseInt(this.refundLineItemQuantity[i.uniqueId])||0,i.refundableAmount=parseFloat((i.amountIncludingTax-i.itemRefundedAmount).toFixed(2)),i.amountIncludingTax=u.format.currency(i.amountIncludingTax,this.currency),i.taxAmount=u.format.currency(i.taxAmount,this.currency),n=parseFloat(parseFloat(n)+parseFloat(i.unitPriceIncludingTax*i.quantity)),i.refundableQuantity=parseInt(parseInt(i.quantity)-parseInt(this.refundLineItemQuantity[i.uniqueId]||0))}),this.lineItems=s.transactions[0].lineItems,this.transactionData=s,this.transaction=this.transactionData.transactions[0],this.refundAmount=Number(this.transactionData.transactions[0].amountIncludingTax),this.refundableAmount=parseFloat(parseFloat(n)-parseFloat(r))}).catch(s=>{try{this.createNotificationError({title:this.$tc("vrpayment-order.paymentDetails.error.title"),message:s.message,autoClose:!1})}catch{this.createNotificationError({title:this.$tc("vrpayment-order.paymentDetails.error.title"),message:s.message,autoClose:!1})}finally{this.isLoading=!1}})})},downloadPackingSlip(){window.open(this.VRPaymentTransactionService.getPackingSlip(this.transaction.metaData.salesChannelId,this.transaction.id),"_blank")},downloadInvoice(){window.open(this.VRPaymentTransactionService.getInvoiceDocument(this.transaction.metaData.salesChannelId,this.transaction.id),"_blank")},resetDataAttributes(){this.transactionData={transactions:[],refunds:[]},this.lineItems=[],this.refundLineItemQuantity=[],this.refundLineItemAmount=[],this.isLoading=!0},spawnModal(e,t,a,n){this.modalType=e,this.currentLineItem=t,this.itemRefundableQuantity=a,this.itemRefundableAmount=isNaN(n)?0:Math.round(n*100)/100},closeModal(){this.modalType=""},lineItemRefund(e){this.isLoading=!0,this.VRPaymentRefundService.createRefund(this.transactionData.transactions[0].metaData.salesChannelId,this.transactionData.transactions[0].id,0,e).then(()=>{this.createNotificationSuccess({title:this.$tc("vrpayment-order.refundAction.successTitle"),message:this.$tc("vrpayment-order.refundAction.successMessage")}),this.isLoading=!1,this.$emit("modal-close"),this.$nextTick(()=>{this.$router.replace(`${this.$route.path}?hash=${u.createId()}`)})}).catch(t=>{try{this.createNotificationError({title:t.response.data.errors[0].title,message:t.response.data.errors[0].detail,autoClose:!1})}catch{this.createNotificationError({title:t.title,message:t.response.data,autoClose:!1})}finally{this.isLoading=!1,this.$emit("modal-close"),this.$nextTick(()=>{this.$router.replace(`${this.$route.path}?hash=${u.createId()}`)})}})},isSelectable(e){return e.refundableQuantity>0&&e.refundableAmount>0&&e.itemRefundedAmount==0&&e.itemRefundedQuantity==0},onSelectionChanged(e){this.selectedItems=Object.values(e)},onPerformBulkAction(){this.selectedItems.length&&(this.isLoading=!0,this.$nextTick(()=>{const e=this.selectedItems.map(t=>this.lineItemRefundBulk(t.uniqueId));Promise.all(e).then(()=>{this.isLoading=!1,this.$emit("modal-close"),this.$nextTick(()=>{this.$router.replace(`${this.$route.path}?hash=${u.createId()}`)})}).catch(t=>{this.createNotificationError({title:"Error",message:"Something went wrong with the refunds",autoClose:!1}),this.isLoading=!1})}))},lineItemRefundBulk(e){return new Promise((t,a)=>{this.VRPaymentRefundService.createRefund(this.transactionData.transactions[0].metaData.salesChannelId,this.transactionData.transactions[0].id,0,e).then(()=>{this.createNotificationSuccess({title:this.$tc("vrpayment-order.refundAction.successTitle"),message:this.$tc("vrpayment-order.refundAction.successMessage")}),t()}).catch(n=>{try{this.createNotificationError({title:n.response.data.errors[0].title,message:n.response.data.errors[0].detail,autoClose:!1})}catch{this.createNotificationError({title:n.title,message:n.response.data,autoClose:!1})}finally{a()}})})}}});const te={"vrpayment-order":{buttons:{label:{completion:"Abschluss","download-invoice":"Rechnung herunterladen","download-packing-slip":"Packzettel herunterladen",refund:"Eine neue Rückerstattung erstellen",void:"Genehmigung annullieren","refund-whole-line-item":"Gesamte Werbebuchung erstatten","refund-line-item-by-quantity":"Rückerstattung nach Menge","refund-line-item-selected":"Rückerstattung auswählen","refund-line-item-parial":"Teilweise Rückerstattung"}},captureAction:{button:{text:"Zahlung erfassen"},currentAmount:"Betrag",isFinal:"Dies ist die endgültige Verbuchung",maxAmount:"Maximaler Betrag",successMessage:"Ihre Verbuchung war erfolgreich",successTitle:"Erfolg"},general:{title:"Bestellungen"},header:"VRPayment Payment",lineItem:{cardTitle:"Einzelposten",types:{amountIncludingTax:"Betrag",name:"Name",quantity:"Anzahl",taxAmount:"Steuern",type:"Typ",uniqueId:"Eindeutige ID"}},modal:{title:{capture:"Erfassen",refund:"Neue Gutschrift",void:"Autorisierung aufheben"}},paymentDetails:{cardTitle:"Zahlung",error:{title:"Fehler beim Abrufen von Zahlungsdetails von VRPayment"}},refund:{cardTitle:"Gutschriften",refundAmount:{label:"Gutschriftsbetrag"},refundQuantity:{label:"Refund Menge"},types:{amount:"Betrag",createdOn:"Erstellt am",id:"ID",state:"Staat"}},refundAction:{confirmButton:{text:"Ausführen"},refundAmount:{label:"Betrag",placeholder:"Einen Betrag eingeben"},successMessage:"Ihre Rückerstattung war erfolgreich",successTitle:"Erfolg",maxAvailableItemsToRefund:"Maximal Verfügbare Artikel zum Erstatten",maxAvailableAmountToRefund:"Maximal verfügbarer Erstattungsbetrag",refundExceedsTotalError:{title:"Fehler beim Erstellen der Rückerstattung.",messageRefundAmountExceedsAvailableBalance:"Der Rückerstattungsbetrag übersteigt das verfügbare Guthaben."}},transactionHistory:{cardTitle:"Einzelheiten",types:{authorized_amount:"Autorisierter Betrag",currency:"Währung",customer:"Kunde",payment_method:"Zahlungsweise",state:"Staat",transaction:"Transaktion"},customerId:"Customer ID",customerName:"Customer Name",creditCardHolder:"Kreditkarteninhaber",paymentMethod:"Zahlungsart",paymentMethodBrand:"Marke der Zahlungsmethode",PseudoCreditCardNumber:"Pseudo-Kreditkartennummer",CardExpire:"Karte verfällt"},voidAction:{confirm:{button:{cancel:"Nein",confirm:"Autorisierung aufheben"},message:"Wollen Sie diese Zahlung wirklich stornieren?"},successMessage:"Die Zahlung wurde erfolgreich annulliert",successTitle:"Erfolg"}}},ae={"vrpayment-order":{buttons:{label:{completion:"Complete","download-invoice":"Download Invoice","download-packing-slip":"Download Packing Slip",refund:"Create a new refund",void:"Cancel authorization","refund-whole-line-item":"Refund whole line item","refund-line-item-by-quantity":"Refund by quantity","refund-line-item-selected":"Refund selected","refund-line-item-parial":"Partial refund"}},captureAction:{button:{text:"Capture payment"},currentAmount:"Amount",isFinal:"This is final capture",maxAmount:"Maximum amount",successMessage:"Your capture was successful.",successTitle:"Success"},general:{title:"Orders"},header:"VRPayment Payment",lineItem:{cardTitle:"Line Items",types:{amountIncludingTax:"Amount",name:"Name",quantity:"Quantity",taxAmount:"Taxes",type:"Type",uniqueId:"Unique ID"}},modal:{title:{capture:"Capture",refund:"New refund",void:"Cancel authorization"}},paymentDetails:{cardTitle:"Payment",error:{title:"Error fetching payment details from VRPayment"}},refund:{cardTitle:"Refunds",refundAmount:{label:"Refund Amount"},refundQuantity:{label:"Refund Quantity"},types:{amount:"Amount",createdOn:"Created On",id:"ID",state:"State"}},refundAction:{confirmButton:{text:"Execute"},refundAmount:{label:"Amount",placeholder:"Enter a amount"},successMessage:"Your refund was successful.",successTitle:"Success",maxAvailableItemsToRefund:"Maximum available items to refund",maxAvailableAmountToRefund:"Maximum available amount to refund",refundExceedsTotalError:{title:"Error while creating the refund.",messageRefundAmountExceedsAvailableBalance:"Refund amount exceeds available balance."}},transactionHistory:{cardTitle:"Details",types:{authorized_amount:"Authorized Amount",currency:"Currency",customer:"Customer",payment_method:"Payment Method",state:"State",transaction:"Transaction"},customerId:"Customer ID",customerName:"Customer Name",creditCardHolder:"Credit Card Holder",paymentMethod:"Payment Method",paymentMethodBrand:"Payment Method Brand",PseudoCreditCardNumber:"Pseudo Credit Card Number",CardExpire:"Card Expire"},voidAction:{confirm:{button:{cancel:"No",confirm:"Cancel authorization"},message:"Do you really want to cancel this payment?"},successMessage:"The payment was successfully voided.",successTitle:"Success"}}},ne={"vrpayment-order":{buttons:{label:{completion:"Terminée","download-invoice":"Télécharger la facture","download-packing-slip":"Télécharger le bordereau d'expédition",refund:"Créer un nouveau remboursement",void:"Annulez l'autorisation","refund-whole-line-item":"Remboursement de la ligne entière","refund-line-item-by-quantity":"Remboursement par quantité","refund-line-item-selected":"Rembourser sélectionnés","refund-line-item-parial":"Remboursement partiel"}},captureAction:{button:{text:"Capture du paiement"},currentAmount:"Montant",isFinal:"C'est la capture finale",maxAmount:"Montant maximal",successMessage:"Votre capture a été réussie.",successTitle:"Succès"},general:{title:"Commandes"},header:"VRPayment Paiement",lineItem:{cardTitle:"Articles de ligne",types:{amountIncludingTax:"Montant",name:"Nom",quantity:"Quantité",taxAmount:"Taxes",type:"Type",uniqueId:"ID unique"}},modal:{title:{capture:"Capture",refund:"Nouveau remboursement",void:"Annulez l'autorisation"}},paymentDetails:{cardTitle:"Paiement",error:{title:"Erreur dans la récupération des détails du paiement à partir de VRPayment"}},refund:{cardTitle:"Remboursements",refundAmount:{label:"Montant du remboursement"},refundQuantity:{label:"Quantité à rembourser"},types:{amount:"Montant",createdOn:"Créé le",id:"ID",state:"État"}},refundAction:{confirmButton:{text:"Exécutez"},refundAmount:{label:"Montant",placeholder:"Entrez un montant"},successMessage:"Votre remboursement a été effectué avec succès.",successTitle:"Succès",maxAvailableItemsToRefund:"Nombre maximum d'articles disponibles pour le remboursement",maxAvailableAmountToRefund:"Montant maximal disponible pour le remboursement",refundExceedsTotalError:{title:"Erreur lors de la création du remboursement.",messageRefundAmountExceedsAvailableBalance:"Le montant du remboursement dépasse le solde disponible."}},transactionHistory:{cardTitle:"Détails",types:{authorized_amount:"Montant autorisé",currency:"Monnaie",customer:"Client",payment_method:"Mode de paiement",state:"État",transaction:"Transaction"},customerId:"Customer ID",customerName:"Customer Name",creditCardHolder:"Titulaire de la carte de crédit",paymentMethod:"Mode de paiement",paymentMethodBrand:"Marque du mode de paiement",PseudoCreditCardNumber:"Pseudo numéro de carte de crédit",CardExpire:"La carte expire"},voidAction:{confirm:{button:{cancel:"Non",confirm:"Annulez l'autorisation"},message:"Voulez-vous vraiment annuler ce paiement?"},successMessage:"Le paiement a été annulé avec succès.",successTitle:"Succès"}}},ie={"vrpayment-order":{buttons:{label:{completion:"Completato","download-invoice":"Scarica fattura","download-packing-slip":"Scarica distinta di imballaggio",refund:"Crea un nuovo rimborso",void:"Annulla autorizzazione","refund-whole-line-item":"Rimborso intera riga","refund-line-item-by-quantity":"Rimborso per quantità","refund-line-item-selected":"Rimborso selezionati","refund-line-item-parial":"Rimborso parziale"}},captureAction:{button:{text:"Cattura pagamento"},currentAmount:"Importo",isFinal:"Questa è la cattura finale",maxAmount:"Importo massimo",successMessage:"La tua cattura ha avuto successo.",successTitle:"Successo"},general:{title:"Ordini"},header:"Pagamento VRPayment",lineItem:{cardTitle:"Articoli di linea",types:{amountIncludingTax:"Importo",name:"Nome",quantity:"Quantità",taxAmount:"Tasse",type:"Tipo",uniqueId:"ID unico"}},modal:{title:{capture:"Cattura",refund:"Nuovo rimborso",void:"Annulla autorizzazione"}},paymentDetails:{cardTitle:"Pagamento",error:{title:"Errore nel recupero dei dettagli del pagamento da VRPayment"}},refund:{cardTitle:"Rimborsi",refundAmount:{label:"Importo del rimborso"},refundQuantity:{label:"Quantità di rimborso"},types:{amount:"Importo",createdOn:"Creato il",id:"ID",state:"Stato"}},refundAction:{confirmButton:{text:"Esegui"},refundAmount:{label:"Importo",placeholder:"Inserisci un importo"},successMessage:"Il tuo rimborso è andato a buon fine.",successTitle:"Successo",maxAvailableItemsToRefund:"Numero massimo di articoli disponibili da rimborsare",maxAvailableAmountToRefund:"Importo massimo disponibile per il rimborso",refundExceedsTotalError:{title:"Errore durante la creazione del rimborso.",messageRefundAmountExceedsAvailableBalance:"LL'importo del rimborso supera il saldo disponibile."}},transactionHistory:{cardTitle:"Dettagli",types:{authorized_amount:"Importo autorizzato",currency:"Valuta",customer:"Cliente",payment_method:"Metodo di pagamento",state:"Stato",transaction:"Transazione"},customerId:"Customer ID",customerName:"Customer Name",creditCardHolder:"Proprietario della carta di credito",paymentMethod:"Metodo di pagamento",paymentMethodBrand:"Metodo di pagamento Marca",PseudoCreditCardNumber:"Numero di carta di credito pseudo",CardExpire:"La carta scade"},voidAction:{confirm:{button:{cancel:"No",confirm:"Annulla autorizzazione"},message:"Vuoi davvero annullare questo pagamento?"},successMessage:"Il pagamento è stato annullato con successo.",successTitle:"Successo"}}},{Module:re}=Shopware;re.register("vrpayment-order",{type:"plugin",name:"VRPayment",title:"vrpayment-order.general.title",description:"vrpayment-order.general.descriptionTextModule",version:"1.0.1",targetVersion:"1.0.1",color:"#2b52ff",snippets:{"de-DE":te,"en-GB":ae,"fr-FR":ne,"it-IT":ie},routeMiddleware(e,t){t.name==="sw.order.detail"&&t.children.push({component:"vrpayment-order-detail",name:"vrpayment.order.detail",isChildren:!0,path:"/sw/order/vrpayment/detail/:id"}),e(t)}});Shopware.Service("privileges").addPrivilegeMappingEntry({category:"permissions",parent:"vrpayment",key:"vrpayment",roles:{viewer:{privileges:["sales_channel:read","sales_channel_payment_method:read","system_config:read"],dependencies:[]},editor:{privileges:["sales_channel:update","sales_channel_payment_method:create","sales_channel_payment_method:update","system_config:update","system_config:create","system_config:delete"],dependencies:["vrpayment.viewer"]}}});Shopware.Service("privileges").addPrivilegeMappingEntry({category:"permissions",parent:null,key:"sales_channel",roles:{viewer:{privileges:["sales_channel_payment_method:read"]},editor:{privileges:["payment_method:update"]},creator:{privileges:["payment_method:create","shipping_method:create","delivery_time:create"]},deleter:{privileges:["payment_method:delete"]}}});const se=`{% block vrpayment_settings %} {% block vrpayment_settings_header %} {% endblock %} {% block vrpayment_settings_actions %} {% endblock %} {% block vrpayment_settings_content %} {% endblock %} {% endblock %}`,c="VRPaymentPayment.config",oe=c+".applicationKey",le=c+".emailEnabled",ce=c+".integration",de=c+".lineItemConsistencyEnabled",me=c+".spaceId",ue=c+".spaceViewId",pe=c+".storefrontInvoiceDownloadEnabled",he=c+".userId",ge=c+".storefrontWebhooksUpdateEnabled",ye=c+".storefrontPaymentsUpdateEnabled",m={CONFIG_DOMAIN:c,CONFIG_APPLICATION_KEY:oe,CONFIG_EMAIL_ENABLED:le,CONFIG_INTEGRATION:ce,CONFIG_LINE_ITEM_CONSISTENCY_ENABLED:de,CONFIG_SPACE_ID:me,CONFIG_SPACE_VIEW_ID:ue,CONFIG_STOREFRONT_INVOICE_DOWNLOAD_ENABLED:pe,CONFIG_USER_ID:he,CONFIG_STOREFRONT_WEBHOOKS_UPDATE_ENABLED:ge,CONFIG_STOREFRONT_PAYMENTS_UPDATE_ENABLED:ye},{Component:fe,Mixin:C}=Shopware;fe.register("vrpayment-settings",{template:se,inject:["acl","VRPaymentConfigurationService"],mixins:[C.getByName("notification"),C.getByName("sw-inline-snippet")],data(){return{config:{},isLoading:!1,isTesting:!1,isSaveSuccessful:!1,applicationKeyFilled:!1,applicationKeyErrorState:!1,spaceIdFilled:!1,spaceIdErrorState:!1,userIdFilled:!1,userIdErrorState:!1,isSetDefaultPaymentSuccessful:!1,isSettingDefaultPaymentMethods:!1,configIntegrationDefaultValue:"payment_page",configEmailEnabledDefaultValue:!0,configLineItemConsistencyEnabledDefaultValue:!0,configStorefrontInvoiceDownloadEnabledEnabledDefaultValue:!0,configStorefrontWebhooksUpdateEnabledDefaultValue:!0,configStorefrontPaymentsUpdateEnabledDefaultValue:!0,...m}},props:{isLoading:{type:Boolean,required:!0}},metaInfo(){return{title:this.$createTitle()}},watch:{config:{handler(e){const t=this.$refs.configComponent.allConfigs.null;this.$refs.configComponent.selectedSalesChannelId===null?(this.applicationKeyFilled=!!this.config[this.CONFIG_APPLICATION_KEY],this.spaceIdFilled=!!this.config[this.CONFIG_SPACE_ID],this.userIdFilled=!!this.config[this.CONFIG_USER_ID],this.CONFIG_INTEGRATION in this.config||(this.config[this.CONFIG_INTEGRATION]=this.configIntegrationDefaultValue),this.CONFIG_EMAIL_ENABLED in this.config||(this.config[this.CONFIG_EMAIL_ENABLED]=this.configEmailEnabledDefaultValue),this.CONFIG_LINE_ITEM_CONSISTENCY_ENABLED in this.config||(this.config[this.CONFIG_LINE_ITEM_CONSISTENCY_ENABLED]=this.configLineItemConsistencyEnabledDefaultValue),this.CONFIG_STOREFRONT_INVOICE_DOWNLOAD_ENABLED in this.config||(this.config[this.CONFIG_STOREFRONT_INVOICE_DOWNLOAD_ENABLED]=this.configStorefrontInvoiceDownloadEnabledEnabledDefaultValue),this.CONFIG_STOREFRONT_WEBHOOKS_UPDATE_ENABLED in this.config||(this.config[this.CONFIG_STOREFRONT_WEBHOOKS_UPDATE_ENABLED]=this.configStorefrontWebhooksUpdateEnabledDefaultValue),this.CONFIG_STOREFRONT_PAYMENTS_UPDATE_ENABLED in this.config||(this.config[this.CONFIG_STOREFRONT_PAYMENTS_UPDATE_ENABLED]=this.configStorefrontPaymentsUpdateEnabledDefaultValue)):(this.applicationKeyFilled=!!this.config[this.CONFIG_APPLICATION_KEY]||!!t[this.CONFIG_APPLICATION_KEY],this.spaceIdFilled=!!this.config[this.CONFIG_SPACE_ID]||!!t[this.CONFIG_SPACE_ID],this.userIdFilled=!!this.config[this.CONFIG_USER_ID]||!!t[this.CONFIG_USER_ID],(!(this.CONFIG_INTEGRATION in this.config)||!(this.CONFIG_INTEGRATION in t))&&(this.config[this.CONFIG_INTEGRATION]=this.configIntegrationDefaultValue),(!(this.CONFIG_EMAIL_ENABLED in this.config)||!(this.CONFIG_EMAIL_ENABLED in t))&&(this.config[this.CONFIG_EMAIL_ENABLED]=this.configEmailEnabledDefaultValue),(!(this.CONFIG_LINE_ITEM_CONSISTENCY_ENABLED in this.config)||!(this.CONFIG_LINE_ITEM_CONSISTENCY_ENABLED in t))&&(this.config[this.CONFIG_LINE_ITEM_CONSISTENCY_ENABLED]=this.configLineItemConsistencyEnabledDefaultValue),(!(this.CONFIG_STOREFRONT_INVOICE_DOWNLOAD_ENABLED in this.config)||!(this.CONFIG_STOREFRONT_INVOICE_DOWNLOAD_ENABLED in t))&&(this.config[this.CONFIG_STOREFRONT_INVOICE_DOWNLOAD_ENABLED]=this.configStorefrontInvoiceDownloadEnabledEnabledDefaultValue),(!(this.CONFIG_STOREFRONT_WEBHOOKS_UPDATE_ENABLED in this.config)||!(this.CONFIG_STOREFRONT_WEBHOOKS_UPDATE_ENABLED in t))&&(this.config[this.CONFIG_STOREFRONT_WEBHOOKS_UPDATE_ENABLED]=this.configStorefrontWebhooksUpdateEnabledDefaultValue),(!(this.CONFIG_STOREFRONT_PAYMENTS_UPDATE_ENABLED in this.config)||!(this.CONFIG_STOREFRONT_PAYMENTS_UPDATE_ENABLED in t))&&(this.config[this.CONFIG_STOREFRONT_PAYMENTS_UPDATE_ENABLED]=this.configStorefrontPaymentsUpdateEnabledDefaultValue)),this.$emit("salesChannelChanged"),this.$emit("update:value",e)},deep:!0}},methods:{checkTextFieldInheritance(e){return typeof e!="string"?!0:e.length<=0},checkNumberFieldInheritance(e){return typeof e!="number"?!0:e.length<=0},checkBoolFieldInheritance(e){return typeof e!="boolean"},getInheritValue(e){return this.selectedSalesChannelId==null?this.actualConfigData[e]:this.allConfigs.null[e]},onSave(){if(!(this.spaceIdFilled&&this.userIdFilled&&this.applicationKeyFilled)){this.setErrorStates();return}this.save()},save(){this.isLoading=!0,this.$refs.configComponent.save().then(e=>{e&&(this.config=e),this.registerWebHooks(),this.synchronizePaymentMethodConfiguration(),this.installOrderDeliveryStates()}).catch(e=>{console.error("Error:",e),this.isLoading=!1})},registerWebHooks(){if(this.config[this.CONFIG_STOREFRONT_WEBHOOKS_UPDATE_ENABLED]===!1)return!1;this.VRPaymentConfigurationService.registerWebHooks(this.$refs.configComponent.selectedSalesChannelId).then(()=>{this.createNotificationSuccess({title:this.$tc("vrpayment-settings.settingForm.titleSuccess"),message:this.$tc("vrpayment-settings.settingForm.messageWebHookUpdated")})}).catch(e=>{this.createNotificationError({title:this.$tc("vrpayment-settings.settingForm.titleError"),message:this.$tc("vrpayment-settings.settingForm.messageWebHookError")}),this.isLoading=!1,console.error("Error:",e)})},synchronizePaymentMethodConfiguration(){if(this.config[this.CONFIG_STOREFRONT_PAYMENTS_UPDATE_ENABLED]===!1)return!1;this.VRPaymentConfigurationService.synchronizePaymentMethodConfiguration(this.$refs.configComponent.selectedSalesChannelId).then(()=>{this.createNotificationSuccess({title:this.$tc("vrpayment-settings.settingForm.titleSuccess"),message:this.$tc("vrpayment-settings.settingForm.messagePaymentMethodConfigurationUpdated")}),this.isLoading=!1}).catch(e=>{this.createNotificationError({title:this.$tc("vrpayment-settings.settingForm.titleError"),message:this.$tc("vrpayment-settings.settingForm.messagePaymentMethodConfigurationError")}),this.isLoading=!1,console.error("Error:",e)})},installOrderDeliveryStates(){this.VRPaymentConfigurationService.installOrderDeliveryStates().then(()=>{this.createNotificationSuccess({title:this.$tc("vrpayment-settings.settingForm.titleSuccess"),message:this.$tc("vrpayment-settings.settingForm.messageOrderDeliveryStateUpdated")}),this.isLoading=!1}).catch(()=>{this.createNotificationError({title:this.$tc("vrpayment-settings.settingForm.titleError"),message:this.$tc("vrpayment-settings.settingForm.messageOrderDeliveryStateError")}),this.isLoading=!1})},onSetPaymentMethodDefault(){this.isSettingDefaultPaymentMethods=!0,this.VRPaymentConfigurationService.setVRPaymentAsSalesChannelPaymentDefault(this.$refs.configComponent.selectedSalesChannelId).then(()=>{this.isSettingDefaultPaymentMethods=!1,this.isSetDefaultPaymentSuccessful=!0,this.createNotificationSuccess({title:this.$tc("vrpayment-settings.settingForm.titleSuccess"),message:this.$tc("vrpayment-settings.salesChannelCard.messageDefaultPaymentUpdated")})})},setErrorStates(){const e={code:1,detail:this.$tc("vrpayment-settings.messageNotBlank")};this.spaceIdFilled||(this.spaceIdErrorState=e),this.userIdFilled||(this.userIdErrorState=e),this.applicationKeyFilled||(this.applicationKeyErrorState=e)},onCheckApiConnection(e){const{spaceId:t,userId:a,applicationKey:n}=e;this.isTesting=!0,this.VRPaymentConfigurationService.checkApiConnection(t,a,n).then(r=>{r.result===200?this.createNotificationSuccess({title:this.$tc("vrpayment-settings.settingForm.credentials.alert.title"),message:this.$tc("vrpayment-settings.settingForm.credentials.alert.successMessage")}):this.createNotificationError({title:this.$tc("vrpayment-settings.settingForm.credentials.alert.title"),message:this.$tc("vrpayment-settings.settingForm.credentials.alert.errorMessage")}),this.isTesting=!1}).catch(()=>{this.createNotificationError({title:this.$tc("vrpayment-settings.settingForm.credentials.alert.title"),message:this.$tc("vrpayment-settings.settingForm.credentials.alert.errorMessage")}),this.isTesting=!1})}}});const be=`{% block vrpayment_settings_content_card_channel_config_credentials %} {% block vrpayment_settings_content_card_channel_config_credentials_card_container %} {% block vrpayment_settings_content_card_channel_config_credentials_card_container_settings %}
{% block vrpayment_settings_content_card_channel_config_credentials_card_container_settings_space_id %} {% endblock %} {% block vrpayment_settings_content_card_channel_config_credentials_card_container_settings_user_id %} {% endblock %} {% block vrpayment_settings_content_card_channel_config_credentials_card_container_settings_application_key %} {% endblock %}
{% endblock %} {% verbatim %} {{ $tc('vrpayment-settings.settingForm.credentials.button.label') }} {% endverbatim %}
{% endblock %}
{% endblock %}`,{Component:ve,Mixin:_e}=Shopware;ve.register("sw-vrpayment-credentials",{template:be,name:"VRPaymentCredentials",inject:["acl"],mixins:[_e.getByName("notification")],props:{actualConfigData:{type:Object,required:!0},allConfigs:{type:Object,required:!0},selectedSalesChannelId:{required:!0},spaceIdFilled:{type:Boolean,required:!0},spaceIdErrorState:{required:!0},userIdFilled:{type:Boolean,required:!0},userIdErrorState:{required:!0},applicationKeyFilled:{type:Boolean,required:!0},applicationKeyErrorState:{required:!0},isLoading:{type:Boolean,required:!0},isTesting:{type:Boolean,required:!1}},data(){return{...m}},methods:{checkTextFieldInheritance(e){return typeof e!="string"?!0:e.length<=0},checkNumberFieldInheritance(e){return typeof e!="number"?!0:e.length<=0},checkBoolFieldInheritance(e){return typeof e!="boolean"},emitCheckApiConnectionEvent(){const e={spaceId:this.actualConfigData[m.CONFIG_SPACE_ID],userId:this.actualConfigData[m.CONFIG_USER_ID],applicationKey:this.actualConfigData[m.CONFIG_APPLICATION_KEY]};this.$emit("check-api-connection-event",e)}}});const Ie=`{% block vrpayment_settings_content_card_channel_config_options %} {% block vrpayment_settings_content_card_channel_config_credentials_card_container %} {% block vrpayment_settings_content_card_channel_config_credentials_card_container_settings %}
{% block vrpayment_settings_content_card_channel_config_credentials_card_container_settings_space_view_id %} {% endblock %} {% block vrpayment_settings_content_card_channel_config_credentials_card_container_settings_integration %} {% endblock %} {% block vrpayment_settings_content_card_channel_config_credentials_card_container_settings_line_item_consistency_enabled %} {% endblock %} {% block vrpayment_settings_content_card_channel_config_credentials_card_container_settings_email_enabled %} {% endblock %}
{% endblock %}
{% endblock %}
{% endblock %}`,{Component:Ce,Mixin:Ee}=Shopware;Ce.register("sw-vrpayment-options",{template:Ie,name:"VRPaymentOptions",mixins:[Ee.getByName("notification")],props:{actualConfigData:{type:Object,required:!0},allConfigs:{type:Object,required:!0},selectedSalesChannelId:{required:!0},isLoading:{type:Boolean,required:!0}},data(){return{...m}},computed:{integrationOptions(){return[{id:"payment_page",name:this.$tc("vrpayment-settings.settingForm.options.integration.options.payment_page")},{id:"iframe",name:this.$tc("vrpayment-settings.settingForm.options.integration.options.iframe")}]}},methods:{checkTextFieldInheritance(e){return typeof e!="string"?!0:e.length<=0},checkNumberFieldInheritance(e){return typeof e!="number"?!0:e.length<=0},checkBoolFieldInheritance(e){return typeof e!="boolean"}}});const Se='{% block vrpayment_settings_icon %} {% endblock %}',{Component:we}=Shopware;we.register("sw-vrpayment-settings-icon",{template:Se});const Ae=`
`,{Component:Te,Mixin:Pe}=Shopware;Te.register("sw-vrpayment-storefront-options",{template:Ae,name:"VRPaymentStorefrontOptions",mixins:[Pe.getByName("notification")],props:{actualConfigData:{type:Object,required:!0},allConfigs:{type:Object,required:!0},selectedSalesChannelId:{required:!0},isLoading:{type:Boolean,required:!0}},data(){return{...m}},methods:{checkTextFieldInheritance(e){return typeof e!="string"?!0:e.length<=0},checkNumberFieldInheritance(e){return typeof e!="number"?!0:e.length<=0},checkBoolFieldInheritance(e){return typeof e!="boolean"}}});const De=`
`,{Component:Ne,Mixin:ke}=Shopware;Ne.register("sw-vrpayment-advanced-options",{template:De,name:"VRPaymentAdvancedOptions",inject:["acl"],mixins:[ke.getByName("notification")],props:{actualConfigData:{type:Object,required:!0},allConfigs:{type:Object,required:!0},selectedSalesChannelId:{required:!0},isLoading:{type:Boolean,required:!0}},data(){return{...m}},methods:{checkTextFieldInheritance(e){return typeof e!="string"?!0:e.length<=0},checkNumberFieldInheritance(e){return typeof e!="number"?!0:e.length<=0},checkBoolFieldInheritance(e){return typeof e!="boolean"}}});const $e={"sw-privileges":{permissions:{parents:{vrpayment:"VRPayment plugin"},vrpayment:{label:"VRPayment berechtigungen"}}},"vrpayment-settings":{general:{descriptionTextModule:"VRPayment-Einstellungen",mainMenuItemGeneral:"VRPayment"},header:"VRPayment",messageNotBlank:"Dieser Wert sollte nicht leer sein.",salesChannelCard:{button:{description:"Klicken Sie auf diese Schaltfläche, um VRPayment als Standard-Zahlungsabwickler im ausgewählten Vertriebskanal festzulegen",label:"VRPayment als Standard-Zahlungsabwickler festlegen"},messageDefaultPaymentError:"VRPayment als Standard-Zahlungsabwickler konnte nicht festgelegt werden..",messageDefaultPaymentUpdated:"VRPayment als Standard-Zahlungsabwickler wurde festgelegt."},settingForm:{credentials:{applicationKey:{label:"Application Key",tooltipText:"Der Anwendungsschlüssel wird verwendet, um dieses Plugin mit der API VRPayment zu authentifizieren."},cardTitle:"Anmeldedaten",spaceId:{label:"Space ID",tooltipText:"Die Space ID wird verwendet, um dieses Plugin mit der API VRPayment zu authentifizieren."},userId:{label:"User ID",tooltipText:"Die Benutzer-ID wird verwendet, um dieses Plugin mit der VRPayment-API zu authentifizieren."},button:{description:"Klicken Sie auf diese Schaltfläche, um die VRPayment API zu testen",label:"API Verbindung testen"},alert:{title:"API-Test",successMessage:"Die Verbindung wurde erfolgreich getestet.",errorMessage:"Die Verbindung ist fehlgeschlagen. Versuchen Sie es erneut."}},messageSaveSuccess:"VRPayment-Einstellungen wurden gespeichert.",messageOrderDeliveryStateError:"VRPayment OrderDeliveryState konnte nicht gespeichert werden.",messageOrderDeliveryStateUpdated:"VRPayment OrderDeliveryState wurde aktualisiert.",messagePaymentMethodConfigurationError:"VRPayment PaymentMethodConfiguration konnte nicht gespeichert werden. Bitte überprüfen Sie Ihre Anmeldedaten.",messagePaymentMethodConfigurationUpdated:"VRPayment PaymentMethodConfiguration wurde registriert.",messageWebHookError:"VRPayment WebHook konnte nicht gespeichert werden. Bitte überprüfen Sie Ihre Zugangsdaten.",messageWebHookUpdated:"VRPayment WebHook wurde aktualisiert.",options:{cardTitle:"Optionen",emailEnabled:{label:"Auftragsbestätigung per E-Mail senden",tooltipText:"Wenn diese Einstellung aktiviert ist, erhalten Ihre Kunden eine E-Mail von Ihrem Geschäft, wenn die Zahlung ihrer Bestellung autorisiert ist."},integration:{label:"Integration",options:{iframe:"Iframe",payment_page:"Payment Page"},tooltipText:"Integration"},lineItemConsistencyEnabled:{label:"Konsistenz der Einzelposten",tooltipText:"Wenn diese Option aktiviert ist, stimmen die Summen der Einzelposten in VRPaymentPayment immer mit der Shopware-Bestellsumme überein."},spaceViewId:{label:"Space View ID",tooltipText:"Space View ID"}},save:"Speichern",storefrontOptions:{cardTitle:"Storefront-Optionen",invoiceDownloadEnabled:{label:"Rechnung Download",tooltipText:"Wenn diese Einstellung aktiviert ist, können Ihre Kunden Auftragsrechnungen von VRPayment herunterladen."}},advancedOptions:{cardTitle:"Erweiterte-Optionen",webhooksUpdateEnabled:{label:"Webhooks-Update",tooltipText:"Wenn diese Einstellung aktiviert ist, wird das Webhook-Update ausgelöst, wenn Sie die Einstellungen speichern"},paymentsUpdateEnabled:{label:"Payments-Update",tooltipText:"Wenn diese Einstellung aktiviert ist, wird die Aktualisierung der Zahlungsmethoden ausgelöst, wenn Sie die Einstellungen speichern"}},titleError:"Fehler",titleSuccess:"Erfolg"}}},Re={"sw-privileges":{permissions:{parents:{vrpayment:"VRPayment plugin"},vrpayment:{label:"VRPayment permissions"}}},"vrpayment-settings":{general:{descriptionTextModule:"VRPayment settings",mainMenuItemGeneral:"VRPayment"},header:"VRPayment",messageNotBlank:"This value should not be blank.",salesChannelCard:{button:{description:"Click this button to set VRPayment as default payment handler in the selected SalesChannel",label:"Set VRPayment as default payment handler"},messageDefaultPaymentError:"VRPayment as default payment could not be set.",messageDefaultPaymentUpdated:"VRPayment as default payment has been set."},settingForm:{credentials:{applicationKey:{label:"Application Key",tooltipText:"The Application Key is used to authenticate this plugin with the VRPayment API."},cardTitle:"Credentials",spaceId:{label:"Space ID",tooltipText:"The space ID is used to authenticate this plugin with the VRPayment API."},userId:{label:"User ID",tooltipText:"The user ID is used to authenticate this plugin with the VRPayment API."},button:{description:"Click this button to test the VRPayment API",label:"API connection test"},alert:{title:"API Test",successMessage:"The connection was successfully tested.",errorMessage:"The connection was failed. Try it again."}},messageSaveSuccess:"VRPayment settings have been saved.",messageOrderDeliveryStateError:"VRPayment OrderDeliveryState could not be saved.",messageOrderDeliveryStateUpdated:"VRPayment OrderDeliveryState has been updated.",messagePaymentMethodConfigurationError:"VRPayment PaymentMethodConfiguration could not be saved. Please check your credentials.",messagePaymentMethodConfigurationUpdated:"VRPayment PaymentMethodConfiguration has been registered.",messageWebHookError:"VRPayment WebHook could not be saved. Please check your credentials.",messageWebHookUpdated:"VRPayment WebHook has been updated.",options:{cardTitle:"Options",emailEnabled:{label:"Send order confirmation email",tooltipText:"If this setting is enabled your customers will receive an email from your store when their order payment is authorised"},integration:{label:"Integration",options:{iframe:"Iframe",payment_page:"Payment Page"},tooltipText:"Integration"},lineItemConsistencyEnabled:{label:"Line item consistency",tooltipText:"If this option is enabled line item totals in VRPaymentPayment will always match Shopware order total"},spaceViewId:{label:"Space View ID",tooltipText:"Space View ID"}},save:"Save",storefrontOptions:{cardTitle:"Storefront Options",invoiceDownloadEnabled:{label:"Invoice Download",tooltipText:"If this setting is enabled your customers will be able to download order invoices from VRPayment"}},advancedOptions:{cardTitle:"Advanced Options",webhooksUpdateEnabled:{label:"Webhooks Update",tooltipText:"If this setting is enabled webhook update will be triggered when you save settings"},paymentsUpdateEnabled:{label:"Payments Update",tooltipText:"If this setting is enabled payment methods update will be triggered when you save settings"}},titleError:"Error",titleSuccess:"Success"}}},xe={"sw-privileges":{permissions:{parents:{vrpayment:"VRPayment brancher"},vrpayment:{label:"VRPayment autorisations"}}},"vrpayment-settings":{general:{descriptionTextModule:"Paramètres de VRPayment",mainMenuItemGeneral:"VRPayment"},header:"VRPayment",messageNotBlank:"Cette valeur ne doit pas être vide.",salesChannelCard:{button:{description:"Cliquez sur ce bouton pour définir VRPayment comme gestionnaire de paiement par défaut dans le canal de vente sélectionné.",label:"Définir VRPayment comme gestionnaire de paiement par défaut"},messageDefaultPaymentError:"VRPayment comme paiement par défaut n'a pas pu être défini.",messageDefaultPaymentUpdated:"VRPayment comme paiement par défaut a été défini."},settingForm:{credentials:{applicationKey:{label:"Application Key",tooltipText:"La clé d'application est utilisée pour authentifier ce plugin avec l'API."},cardTitle:"Références",spaceId:{label:"Space ID",tooltipText:"L'ID de l'espace est utilisé pour authentifier ce plugin avec l'API VRPayment.."},userId:{label:"User ID",tooltipText:"L'ID utilisateur est utilisé pour authentifier ce plugin avec l'API VRPayment."},button:{description:"Cliquez sur ce bouton pour tester l'API VRPayment.",label:"Test de connexion à l'API"},alert:{title:"Test API",successMessage:"La connexion a été testée avec succès.",errorMessage:"La connexion a échoué. Réessayez."}},messageSaveSuccess:"Les paramètres de VRPayment ont été enregistrés.",messageOrderDeliveryStateError:"Les paramètres de VRPayment OrderDeliveryState n'ont pas pu être enregistrés.",messageOrderDeliveryStateUpdated:"VRPayment OrderDeliveryState a été mis à jour.",messagePaymentMethodConfigurationError:"VRPayment PaymentMethodConfiguration n'a pas pu être enregistré. Veuillez vérifier vos informations d'identification.",messagePaymentMethodConfigurationUpdated:"VRPayment PaymentMethodConfiguration a été enregistré.",messageWebHookError:"VRPayment WebHook n'a pas pu être enregistré. Veuillez vérifier vos informations d'identification.",messageWebHookUpdated:"VRPayment WebHook a été mis à jour.",options:{cardTitle:"Options",emailEnabled:{label:"Envoyer un e-mail de confirmation de commande",tooltipText:"If this setting is enabled your customers will receive an email from your store when their order payment is authorised"},integration:{label:"Integration",options:{iframe:"Iframe",payment_page:"Page de paiement"},tooltipText:"Integration"},lineItemConsistencyEnabled:{label:"Cohérence des postes de ligne",tooltipText:"Si cette option est activée, les totaux des articles dans VRPaymentPayment correspondront toujours au total de la commande Shopware."},spaceViewId:{label:"Space View ID",tooltipText:"Space View ID"}},save:"Enregistrer",storefrontOptions:{cardTitle:"Storefront Options",invoiceDownloadEnabled:{label:"Téléchargement de facture",tooltipText:"Si ce paramètre est activé, vos clients pourront télécharger les factures de commande depuis VRPayment"}},advancedOptions:{cardTitle:"Options avancées",webhooksUpdateEnabled:{label:"Mise à jour des webhooks",tooltipText:"Si ce paramètre est activé, la mise à jour des webhooks sera déclenchée lorsque vous enregistrerez les paramètres."},paymentsUpdateEnabled:{label:"Mise à jour des paiements",tooltipText:"Si ce paramètre est activé, la mise à jour des méthodes de paiement sera déclenchée lorsque vous enregistrez les paramètres."}},titleError:"Erreur",titleSuccess:"Succès"}}},Oe={"sw-privileges":{permissions:{parents:{vrpayment:"VRPayment brancher"},vrpayment:{label:"VRPayment autorisations"}}},"vrpayment-settings":{general:{descriptionTextModule:"Impostazioni VRPayment",mainMenuItemGeneral:"VRPayment"},header:"VRPayment",messageNotBlank:"Questo valore non dovrebbe essere vuoto.",salesChannelCard:{button:{description:"Fai clic su questo pulsante per impostare VRPayment come gestore di pagamento predefinito nel SalesChannel selezionato",label:"Imposta VRPayment come gestore di pagamento predefinito"},messageDefaultPaymentError:"Non è stato possibile impostare VRPayment come pagamento predefinito.",messageDefaultPaymentUpdated:"VRPayment come pagamento predefinito è stato impostato."},settingForm:{credentials:{applicationKey:{label:"Chiave di applicazione",tooltipText:"La chiave dell'applicazione è usata per autenticare questo plugin con l'API VRPayment."},cardTitle:"Credenziali",spaceId:{label:"ID spazio",tooltipText:"L'ID dello spazio è usato per autenticare questo plugin con l'API VRPayment."},userId:{label:"ID utente",tooltipText:"L'ID utente è usato per autenticare questo plugin con l'API VRPayment."},button:{description:"Fare clic su questo pulsante per testare l'API VRPayment.",label:"Test di connessione API"},alert:{title:"Test API",successMessage:"La connessione è stata testata con successo.",errorMessage:"La connessione è fallita. Riprovare."}},messageSaveSuccess:"Le impostazioni di VRPayment sono state salvate.",messageOrderDeliveryStateError:"VRPayment OrderDeliveryState non può essere salvato.",messageOrderDeliveryStateUpdated:"VRPayment OrderDeliveryState è stato aggiornato.",messagePaymentMethodConfigurationError:"VRPayment PaymentMethodConfiguration non può essere salvato. Per favore controlla le tue credenziali.",messagePaymentMethodConfigurationUpdated:"VRPayment PaymentMethodConfiguration è stato registrato.",messageWebHookError:"VRPayment WebHook non può essere salvato. Per favore controlla le tue credenziali.",messageWebHookUpdated:"VRPayment WebHook è stato aggiornato.",options:{cardTitle:"Opzioni",emailEnabled:{label:"Invia email di conferma dell'ordine",tooltipText:"Se questa impostazione è abilitata i tuoi clienti riceveranno un'email dal tuo negozio quando il pagamento del loro ordine sarà autorizzato"},integration:{label:"Integrazione",options:{iframe:"Iframe",payment_page:"Pagina di pagamento"},tooltipText:"Integrazione"},lineItemConsistencyEnabled:{label:"Coerenza dell'elemento linea",tooltipText:"Se questa opzione è abilitata i totali degli articoli in VRPaymentPayment corrisponderanno sempre al totale dell'ordine Shopware"},spaceViewId:{label:"ID della vista spazio",tooltipText:"ID della vista spaziale"}},save:"Salva",storefrontOptions:{cardTitle:"Opzioni vetrina",invoiceDownloadEnabled:{label:"Scaricamento fattura",tooltipText:"Se questa impostazione è abilitata i tuoi clienti potranno scaricare le fatture degli ordini da VRPayment"}},advancedOptions:{cardTitle:"Opzioni avanzate",webhooksUpdateEnabled:{label:"Aggiornamento webhooks",tooltipText:"Se questa impostazione è abilitata l'aggiornamento dei webhook sarà attivato quando si salvano le impostazioni"},paymentsUpdateEnabled:{label:"Aggiornamento pagamenti",tooltipText:"Se questa impostazione è abilitata l'aggiornamento dei metodi di pagamento verrà attivato quando si salvano le impostazioni"}},titleError:"Errore",titleSuccess:"Successo"}}},{Module:Fe}=Shopware;Fe.register("vrpayment-settings",{type:"plugin",name:"VRPayment",title:"vrpayment-settings.general.descriptionTextModule",description:"vrpayment-settings.general.descriptionTextModule",color:"#28d8ff",icon:"default-action-settings",version:"1.0.1",targetVersion:"1.0.1",snippets:{"de-DE":$e,"en-GB":Re,"fr-FR":xe,"it-IT":Oe},routes:{index:{component:"vrpayment-settings",path:"index",meta:{parentPath:"sw.settings.index",privilege:"vrpayment.viewer"},props:{default:e=>({hash:e.params.hash})}}},settingsItem:{group:"plugins",to:"vrpayment.settings.index",iconComponent:"sw-vrpayment-settings-icon",backgroundEnabled:!0,privilege:"vrpayment.viewer"}});const p=Shopware.Classes.ApiService;class Ve extends p{constructor(t,a,n="vrpayment"){super(t,a,n)}registerWebHooks(t=null){const a=this.getBasicHeaders(),n=`${Shopware.Context.api.apiPath}/_action/${this.getApiBasePath()}/configuration/register-web-hooks`;return this.httpClient.post(n,{salesChannelId:t},{headers:a}).then(r=>p.handleResponse(r))}checkApiConnection(t=null,a=null,n=null){const r=this.getBasicHeaders(),o=`${Shopware.Context.api.apiPath}/_action/${this.getApiBasePath()}/configuration/check-api-connection`;return this.httpClient.post(o,{spaceId:t,userId:a,applicationId:n},{headers:r}).then(s=>p.handleResponse(s))}setVRPaymentAsSalesChannelPaymentDefault(t=null){const a=this.getBasicHeaders(),n=`${Shopware.Context.api.apiPath}/_action/${this.getApiBasePath()}/configuration/set-vrpayment-as-sales-channel-payment-default`;return this.httpClient.post(n,{salesChannelId:t},{headers:a}).then(r=>p.handleResponse(r))}synchronizePaymentMethodConfiguration(t=null){const a=this.getBasicHeaders(),n=`${Shopware.Context.api.apiPath}/_action/${this.getApiBasePath()}/configuration/synchronize-payment-method-configuration`;return this.httpClient.post(n,{salesChannelId:t},{headers:a}).then(r=>p.handleResponse(r))}installOrderDeliveryStates(){const t=this.getBasicHeaders(),a=`${Shopware.Context.api.apiPath}/_action/${this.getApiBasePath()}/configuration/install-order-delivery-states`;return this.httpClient.post(a,{},{headers:t}).then(n=>p.handleResponse(n))}}const h=Shopware.Classes.ApiService;class Le extends h{constructor(t,a,n="vrpayment"){super(t,a,n)}createRefund(t,a,n,r){const o=this.getBasicHeaders(),s=`${Shopware.Context.api.apiPath}/_action/${this.getApiBasePath()}/refund/create-refund/`;return this.httpClient.post(s,{salesChannelId:t,transactionId:a,quantity:n,lineItemId:r},{headers:o}).then(i=>h.handleResponse(i))}createRefundByAmount(t,a,n){const r=this.getBasicHeaders(),o=`${Shopware.Context.api.apiPath}/_action/${this.getApiBasePath()}/refund/create-refund-by-amount/`;return this.httpClient.post(o,{salesChannelId:t,transactionId:a,refundableAmount:n},{headers:r}).then(s=>h.handleResponse(s))}createPartialRefund(t,a,n,r){const o=this.getBasicHeaders(),s=`${Shopware.Context.api.apiPath}/_action/${this.getApiBasePath()}/refund/create-partial-refund/`;return this.httpClient.post(s,{salesChannelId:t,transactionId:a,refundableAmount:n,lineItemId:r},{headers:o}).then(i=>h.handleResponse(i))}}const E=Shopware.Classes.ApiService;class Me extends E{constructor(t,a,n="vrpayment"){super(t,a,n)}getTransactionData(t,a){const n=this.getBasicHeaders(),r=`${Shopware.Context.api.apiPath}/_action/${this.getApiBasePath()}/transaction/get-transaction-data/`;return this.httpClient.post(r,{salesChannelId:t,transactionId:a},{headers:n}).then(o=>E.handleResponse(o))}getInvoiceDocument(t,a){return`${Shopware.Context.api.apiPath}/_action/${this.getApiBasePath()}/transaction/get-invoice-document/${t}/${a}`}getPackingSlip(t,a){return`${Shopware.Context.api.apiPath}/_action/${this.getApiBasePath()}/transaction/get-packing-slip/${t}/${a}`}}const S=Shopware.Classes.ApiService;class Be extends S{constructor(t,a,n="vrpayment"){super(t,a,n)}createTransactionCompletion(t,a){const n=this.getBasicHeaders(),r=`${Shopware.Context.api.apiPath}/_action/${this.getApiBasePath()}/transaction-completion/create-transaction-completion/`;return this.httpClient.post(r,{salesChannelId:t,transactionId:a},{headers:n}).then(o=>S.handleResponse(o))}}const w=Shopware.Classes.ApiService;class ze extends w{constructor(t,a,n="vrpayment"){super(t,a,n)}createTransactionVoid(t,a){const n=this.getBasicHeaders(),r=`${Shopware.Context.api.apiPath}/_action/${this.getApiBasePath()}/transaction-void/create-transaction-void/`;return this.httpClient.post(r,{salesChannelId:t,transactionId:a},{headers:n}).then(o=>w.handleResponse(o))}}const{Application:d}=Shopware;d.addServiceProvider("VRPaymentConfigurationService",e=>{const t=d.getContainer("init");return new Ve(t.httpClient,e.loginService)});d.addServiceProvider("VRPaymentRefundService",e=>{const t=d.getContainer("init");return new Le(t.httpClient,e.loginService)});d.addServiceProvider("VRPaymentTransactionService",e=>{const t=d.getContainer("init");return new Me(t.httpClient,e.loginService)});d.addServiceProvider("VRPaymentTransactionCompletionService",e=>{const t=d.getContainer("init");return new Be(t.httpClient,e.loginService)});d.addServiceProvider("VRPaymentTransactionVoidService",e=>{const t=d.getContainer("init");return new ze(t.httpClient,e.loginService)}); +//# sourceMappingURL=v-r-payment-payment-C6eiDWfX.js.map diff --git a/src/Resources/public/administration/assets/v-r-payment-payment-C6eiDWfX.js.map b/src/Resources/public/administration/assets/v-r-payment-payment-C6eiDWfX.js.map new file mode 100644 index 0000000..7aee46e --- /dev/null +++ b/src/Resources/public/administration/assets/v-r-payment-payment-C6eiDWfX.js.map @@ -0,0 +1 @@ +{"version":3,"file":"v-r-payment-payment-C6eiDWfX.js","sources":["../../../app/administration/src/module/vrpayment-order/extension/sw-order/sw-order.html.twig","../../../app/administration/src/module/vrpayment-order/extension/sw-order/index.js","../../../app/administration/src/module/vrpayment-order/component/vrpayment-order-action-completion/index.html.twig","../../../app/administration/src/module/vrpayment-order/component/vrpayment-order-action-completion/index.js","../../../app/administration/src/module/vrpayment-order/component/vrpayment-order-action-refund/index.html.twig","../../../app/administration/src/module/vrpayment-order/component/vrpayment-order-action-refund/index.js","../../../app/administration/src/module/vrpayment-order/component/vrpayment-order-action-refund-partial/index.html.twig","../../../app/administration/src/module/vrpayment-order/component/vrpayment-order-action-refund-partial/index.js","../../../app/administration/src/module/vrpayment-order/component/vrpayment-order-action-refund-by-amount/index.html.twig","../../../app/administration/src/module/vrpayment-order/component/vrpayment-order-action-refund-by-amount/index.js","../../../app/administration/src/module/vrpayment-order/component/vrpayment-order-action-void/index.html.twig","../../../app/administration/src/module/vrpayment-order/component/vrpayment-order-action-void/index.js","../../../app/administration/src/module/vrpayment-order/page/vrpayment-order-detail/index.html.twig","../../../app/administration/src/module/vrpayment-order/page/vrpayment-order-detail/index.js","../../../app/administration/src/module/vrpayment-order/index.js","../../../app/administration/src/module/vrpayment-settings/acl/index.js","../../../app/administration/src/module/vrpayment-settings/page/vrpayment-settings/index.html.twig","../../../app/administration/src/module/vrpayment-settings/page/vrpayment-settings/configuration-constants.js","../../../app/administration/src/module/vrpayment-settings/page/vrpayment-settings/index.js","../../../app/administration/src/module/vrpayment-settings/component/sw-vrpayment-credentials/index.html.twig","../../../app/administration/src/module/vrpayment-settings/component/sw-vrpayment-credentials/index.js","../../../app/administration/src/module/vrpayment-settings/component/sw-vrpayment-options/index.html.twig","../../../app/administration/src/module/vrpayment-settings/component/sw-vrpayment-options/index.js","../../../app/administration/src/module/vrpayment-settings/component/sw-vrpayment-settings-icon/index.html.twig","../../../app/administration/src/module/vrpayment-settings/component/sw-vrpayment-settings-icon/index.js","../../../app/administration/src/module/vrpayment-settings/component/sw-vrpayment-storefront-options/index.html.twig","../../../app/administration/src/module/vrpayment-settings/component/sw-vrpayment-storefront-options/index.js","../../../app/administration/src/module/vrpayment-settings/component/sw-vrpayment-advanced-options/index.html.twig","../../../app/administration/src/module/vrpayment-settings/component/sw-vrpayment-advanced-options/index.js","../../../app/administration/src/module/vrpayment-settings/index.js","../../../app/administration/src/core/service/api/vrpayment-configuration.service.js","../../../app/administration/src/core/service/api/vrpayment-refund.service.js","../../../app/administration/src/core/service/api/vrpayment-transaction.service.js","../../../app/administration/src/core/service/api/vrpayment-transaction-completion.service.js","../../../app/administration/src/core/service/api/vrpayment-transaction-void.service.js","../../../app/administration/src/init/api-service.init.js"],"sourcesContent":["{% block sw_order_detail_content_tabs_general %}\n {% parent %}\n\n{# sw-tabs-item will dissappear. See: https://github.com/shopware/shopware/blob/trunk/UPGRADE-6.7.md#sw-tabs-is-removed #}\n\n\t{{ $tc('vrpayment-order.header') }}\n\n{% endblock %}\n\n{% block sw_order_detail_actions_slot_smart_bar_actions %}\n\n{% endblock %}\n","/* global Shopware */\n\nimport template from './sw-order.html.twig';\nimport './sw-order.scss';\n\nconst {Component, Context} = Shopware;\nconst Criteria = Shopware.Data.Criteria;\n\nconst vrpaymentFormattedHandlerIdentifier = 'handler_vrpaymentpayment_vrpaymentpaymenthandler';\n\nComponent.override('sw-order-detail', {\n\ttemplate,\n\n\tdata() {\n\t\treturn {\n\t\t\tisVRPaymentPayment: false\n\t\t};\n\t},\n\n\tcomputed: {\n\t\tisEditable() {\n\t\t\treturn !this.isVRPaymentPayment || this.$route.name !== 'vrpayment.order.detail';\n\t\t},\n\t\tshowTabs() {\n\t\t\treturn true;\n\t\t}\n\t},\n\n\twatch: {\n\t\torderId: {\n\t\t\tdeep: true,\n\t\t\thandler() {\n\t\t\t\tif (!this.orderId) {\n\t\t\t\t\tthis.setIsVRPaymentPayment(null);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tconst orderRepository = this.repositoryFactory.create('order');\n\t\t\t\tconst orderCriteria = new Criteria(1, 1);\n\t\t\t\torderCriteria.addAssociation('transactions');\n\n\t\t\t\torderRepository.get(this.orderId, Context.api, orderCriteria).then((order) => {\n\t\t\t\t\tif (\n\t\t\t\t\t\t(order.amountTotal <= 0) ||\n\t\t\t\t\t\t(order.transactions.length <= 0) ||\n\t\t\t\t\t\t!order.transactions[0].paymentMethodId\n\t\t\t\t\t) {\n\t\t\t\t\t\tthis.setIsVRPaymentPayment(null);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tconst paymentMethodId = order.transactions[0].paymentMethodId;\n\t\t\t\t\tif (paymentMethodId !== undefined && paymentMethodId !== null) {\n\t\t\t\t\t\tthis.setIsVRPaymentPayment(paymentMethodId);\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t},\n\t\t\timmediate: true\n\t\t}\n\t},\n\n\tmethods: {\n\t\tsetIsVRPaymentPayment(paymentMethodId) {\n\t\t\tif (!paymentMethodId) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst paymentMethodRepository = this.repositoryFactory.create('payment_method');\n\t\t\tpaymentMethodRepository.get(paymentMethodId, Context.api).then(\n\t\t\t\t(paymentMethod) => {\n\t\t\t\t\tthis.isVRPaymentPayment = (paymentMethod.formattedHandlerIdentifier === vrpaymentFormattedHandlerIdentifier);\n\t\t\t\t}\n\t\t\t);\n\t\t}\n\t}\n});\n","{% block vrpayment_order_action_completion %}\n\n\n\t{% block vrpayment_order_action_completion_amount %}\n\t\t\n \n\t{% endblock %}\n\n\t{% block vrpayment_order_action_completion_confirm_button %}\n\t\n\t{% endblock %}\n\n\t\n\n{% endblock %}\n","/* global Shopware */\n\nimport template from './index.html.twig';\n\nconst {Component, Mixin, Filter, Utils} = Shopware;\n\nComponent.register('vrpayment-order-action-completion', {\n\n\ttemplate: template,\n\n\tinject: ['VRPaymentTransactionCompletionService'],\n\n\tmixins: [\n\t\tMixin.getByName('notification')\n\t],\n\n\tprops: {\n\t\ttransactionData: {\n\t\t\ttype: Object,\n\t\t\trequired: true\n\t\t}\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\tisLoading: true,\n\t\t\tisCompletion: false\n\t\t};\n\t},\n\n\tcomputed: {\n\t\tdateFilter() {\n\t\t\treturn Filter.getByName('date');\n\t\t}\n\t},\n\n\tcreated() {\n\t\tthis.createdComponent();\n\t},\n\n\tmethods: {\n\t\tcreatedComponent() {\n\t\t\tthis.isLoading = false;\n\t\t},\n\n\t\tcompletion() {\n\t\t\tif (this.isCompletion) {\n\t\t\t\tthis.isLoading = true;\n\t\t\t\tthis.VRPaymentTransactionCompletionService.createTransactionCompletion(\n\t\t\t\t\tthis.transactionData.transactions[0].metaData.salesChannelId,\n\t\t\t\t\tthis.transactionData.transactions[0].id\n\t\t\t\t).then(() => {\n\t\t\t\t\tthis.createNotificationSuccess({\n\t\t\t\t\t\ttitle: this.$tc('vrpayment-order.captureAction.successTitle'),\n\t\t\t\t\t\tmessage: this.$tc('vrpayment-order.captureAction.successMessage')\n\t\t\t\t\t});\n\t\t\t\t\tthis.isLoading = false;\n\t\t\t\t\tthis.$emit('modal-close');\n\t\t\t\t\tthis.$nextTick(() => {\n\t\t\t\t\t\tthis.$router.replace(`${this.$route.path}?hash=${Utils.createId()}`);\n\t\t\t\t\t});\n\t\t\t\t}).catch((errorResponse) => {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tthis.createNotificationError({\n\t\t\t\t\t\t\ttitle: errorResponse.response.data.errors[0].title,\n\t\t\t\t\t\t\tmessage: errorResponse.response.data.errors[0].detail,\n\t\t\t\t\t\t\tautoClose: false\n\t\t\t\t\t\t});\n\t\t\t\t\t} catch (e) {\n\t\t\t\t\t\tthis.createNotificationError({\n\t\t\t\t\t\t\ttitle: errorResponse.title,\n\t\t\t\t\t\t\tmessage: errorResponse.message,\n\t\t\t\t\t\t\tautoClose: false\n\t\t\t\t\t\t});\n\t\t\t\t\t} finally {\n\t\t\t\t\t\tthis.isLoading = false;\n\t\t\t\t\t\tthis.$emit('modal-close');\n\t\t\t\t\t\tthis.$nextTick(() => {\n\t\t\t\t\t\t\tthis.$router.replace(`${this.$route.path}?hash=${Utils.createId()}`);\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t}\n});\n","{% block vrpayment_order_action_refund %}\n\n\n\t{% block vrpayment_order_action_refund_amount %}\n\n\t\t\n\t\t\n\n\t\t
\n\t\t\t{{ $tc('vrpayment-order.refundAction.maxAvailableItemsToRefund') }}:\n\t\t\t{{ this.$parent.$parent.itemRefundableQuantity }}\n\t\t
\n\t{% endblock %}\n\n\t{% block vrpayment_order_action_refund_confirm_button %}\n\t\n\t{% endblock %}\n\n\t\n
\n{% endblock %}\n","/* global Shopware */\n\nimport template from './index.html.twig';\n\nconst {Component, Mixin, Filter, Utils} = Shopware;\n\nComponent.register('vrpayment-order-action-refund', {\n\ttemplate,\n\n\tinject: ['VRPaymentRefundService'],\n\n\tmixins: [\n\t\tMixin.getByName('notification')\n\t],\n\n\tprops: {\n\t\ttransactionData: {\n\t\t\ttype: Object,\n\t\t\trequired: true\n\t\t},\n\n\t\torderId: {\n\t\t\ttype: String,\n\t\t\trequired: true\n\t\t}\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\trefundQuantity: 0,\n\t\t\tisLoading: true,\n\t\t\tcurrentLineItem: '',\n\t\t};\n\t},\n\n\tcomputed: {\n\t\tdateFilter() {\n\t\t\treturn Filter.getByName('date');\n\t\t}\n\t},\n\n\tcreated() {\n\t\tthis.createdComponent();\n\t},\n\n\tmethods: {\n\t\tcreatedComponent() {\n\t\t\tthis.isLoading = false;\n\t\t\tthis.refundQuantity = 1;\n\t\t},\n\n\t\trefund() {\n\t\t\tthis.isLoading = true;\n\t\t\tthis.VRPaymentRefundService.createRefund(\n\t\t\t\tthis.transactionData.transactions[0].metaData.salesChannelId,\n\t\t\t\tthis.transactionData.transactions[0].id,\n\t\t\t\tthis.refundQuantity,\n\t\t\t\tthis.$parent.$parent.currentLineItem\n\t\t\t).then(() => {\n\t\t\t\tthis.createNotificationSuccess({\n\t\t\t\t\ttitle: this.$tc('vrpayment-order.refundAction.successTitle'),\n\t\t\t\t\tmessage: this.$tc('vrpayment-order.refundAction.successMessage')\n\t\t\t\t});\n\t\t\t\tthis.isLoading = false;\n\t\t\t\tthis.$emit('modal-close');\n\t\t\t\tthis.$nextTick(() => {\n\t\t\t\t\tthis.$router.replace(`${this.$route.path}?hash=${Utils.createId()}`);\n\t\t\t\t});\n\t\t\t}).catch((errorResponse) => {\n\t\t\t\ttry {\n\t\t\t\t\tthis.createNotificationError({\n\t\t\t\t\t\ttitle: errorResponse.response.data.errors[0].title,\n\t\t\t\t\t\tmessage: errorResponse.response.data.errors[0].detail,\n\t\t\t\t\t\tautoClose: false\n\t\t\t\t\t});\n\t\t\t\t} catch (e) {\n\t\t\t\t\tthis.createNotificationError({\n\t\t\t\t\t\ttitle: errorResponse.title,\n\t\t\t\t\t\tmessage: errorResponse.message,\n\t\t\t\t\t\tautoClose: false\n\t\t\t\t\t});\n\t\t\t\t} finally {\n\t\t\t\t\tthis.isLoading = false;\n\t\t\t\t\tthis.$emit('modal-close');\n\t\t\t\t\tthis.$nextTick(() => {\n\t\t\t\t\t\tthis.$router.replace(`${this.$route.path}?hash=${Utils.createId()}`);\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\t}\n});\n","{% block vrpayment_order_action_refund_partial %}\n\n\n\t{% block vrpayment_order_action_refund_amount_partial %}\n\t\t\n\t\t\n\n\t\t
\n\t\t\t{{ $tc('vrpayment-order.refundAction.maxAvailableAmountToRefund') }}:\n\t\t\t{{ this.$parent.$parent.itemRefundableAmount }}\n\t\t
\n\t{% endblock %}\n\n\t{% block vrpayment_order_action_refund_confirm_button_partial %}\n\t\n\t{% endblock %}\n\n\t\n
\n{% endblock %}\n","/* global Shopware */\n\nimport template from './index.html.twig';\n\nconst {Component, Mixin, Filter, Utils} = Shopware;\n\nComponent.register('vrpayment-order-action-refund-partial', {\n\ttemplate,\n\n\tinject: ['VRPaymentRefundService'],\n\n\tmixins: [\n\t\tMixin.getByName('notification')\n\t],\n\n\tprops: {\n\t\ttransactionData: {\n\t\t\ttype: Object,\n\t\t\trequired: true\n\t\t},\n\n\t\torderId: {\n\t\t\ttype: String,\n\t\t\trequired: true\n\t\t}\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\tisLoading: true,\n\t\t\tcurrency: this.transactionData.transactions[0].currency,\n\t\t\trefundAmount: 0.00,\n\t\t};\n\t},\n\n\tcomputed: {\n\t\tdateFilter() {\n\t\t\treturn Filter.getByName('date');\n\t\t}\n\t},\n\n\tcreated() {\n\t\tthis.createdComponent();\n\t},\n\n\tmethods: {\n createdComponent() {\n this.isLoading = false;\n this.currency = this.transactionData.transactions[0].currency;\n if (!this.refundAmount) {\n this.refundAmount = this.$parent.$parent.itemRefundableAmount;\n }\n },\n\n\t\tcreatePartialRefund(itemUniqueId) {\n\t\t\tthis.isLoading = true;\n\t\t\tthis.VRPaymentRefundService.createPartialRefund(\n\t\t\t\tthis.transactionData.transactions[0].metaData.salesChannelId,\n\t\t\t\tthis.transactionData.transactions[0].id,\n\t\t\t\tthis.refundAmount,\n\t\t\t\titemUniqueId\n\t\t\t).then(() => {\n\t\t\t\tthis.createNotificationSuccess({\n\t\t\t\t\ttitle: this.$tc('vrpayment-order.refundAction.successTitle'),\n\t\t\t\t\tmessage: this.$tc('vrpayment-order.refundAction.successMessage')\n\t\t\t\t});\n\t\t\t\tthis.isLoading = false;\n\t\t\t\tthis.$emit('modal-close');\n\t\t\t\tthis.$nextTick(() => {\n\t\t\t\t\tthis.$router.replace(`${this.$route.path}?hash=${Utils.createId()}`);\n\t\t\t\t});\n\t\t\t}).catch((errorResponse) => {\n\t\t\t\ttry {\n\t\t\t\t\tthis.createNotificationError({\n\t\t\t\t\t\ttitle: errorResponse.response.data.errors[0].title,\n\t\t\t\t\t\tmessage: errorResponse.response.data.errors[0].detail,\n\t\t\t\t\t\tautoClose: false\n\t\t\t\t\t});\n\t\t\t\t} catch (e) {\n\t\t\t\t\tthis.createNotificationError({\n\t\t\t\t\t\ttitle: errorResponse.title,\n\t\t\t\t\t\tmessage: errorResponse.message,\n\t\t\t\t\t\tautoClose: false\n\t\t\t\t\t});\n\t\t\t\t} finally {\n\t\t\t\t\tthis.isLoading = false;\n\t\t\t\t\tthis.$emit('modal-close');\n\t\t\t\t\tthis.$nextTick(() => {\n\t\t\t\t\t\tthis.$router.replace(`${this.$route.path}?hash=${Utils.createId()}`);\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\t},\n\n watch: {\n refundAmount(newValue) {\n if (newValue !== null) {\n this.refundAmount = Math.round(newValue * 100) / 100;\n }\n }\n }\n});\n","{% block vrpayment_order_action_refund_by_amount %}\n\n\n\t{% block vrpayment_order_action_refund_amount_by_amount %}\n\t\t\n\t\t\n\t{% endblock %}\n\n\t{% block vrpayment_order_action_refund_confirm_button_by_amount %}\n\t\n\t{% endblock %}\n\n\t\n\n{% endblock %}\n","/* global Shopware */\n\nimport template from './index.html.twig';\n\nconst {Component, Mixin, Filter, Utils} = Shopware;\n\nComponent.register('vrpayment-order-action-refund-by-amount', {\n\ttemplate,\n\n\tinject: ['VRPaymentRefundService'],\n\n\tmixins: [\n\t\tMixin.getByName('notification')\n\t],\n\n\tprops: {\n\t\ttransactionData: {\n\t\t\ttype: Object,\n\t\t\trequired: true\n\t\t},\n\n\t\torderId: {\n\t\t\ttype: String,\n\t\t\trequired: true\n\t\t}\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\tisLoading: true,\n\t\t\tcurrency: this.transactionData.transactions[0].currency,\n\t\t\trefundAmount: 0,\n\t\t\trefundableAmount: 0,\n\t\t};\n\t},\n\n\tcomputed: {\n\t\tdateFilter() {\n\t\t\treturn Filter.getByName('date');\n\t\t}\n\t},\n\n\tcreated() {\n\t\tthis.createdComponent();\n\t},\n\n\tmethods: {\n\t\tcreatedComponent() {\n\t\t\tthis.isLoading = false;\n\t\t\tthis.currency = this.transactionData.transactions[0].currency;\n\t\t\tthis.refundAmount = Number(this.transactionData.transactions[0].amountIncludingTax);\n\t\t\tthis.refundableAmount = Number(this.transactionData.transactions[0].amountIncludingTax);\n\t\t},\n\n\t\trefundByAmount() {\n\t\t\tthis.isLoading = true;\n\t\t\tthis.VRPaymentRefundService.createRefundByAmount(\n\t\t\t\tthis.transactionData.transactions[0].metaData.salesChannelId,\n\t\t\t\tthis.transactionData.transactions[0].id,\n\t\t\t\tthis.refundAmount\n\t\t\t).then(() => {\n\t\t\t\tthis.createNotificationSuccess({\n\t\t\t\t\ttitle: this.$tc('vrpayment-order.refundAction.successTitle'),\n\t\t\t\t\tmessage: this.$tc('vrpayment-order.refundAction.successMessage')\n\t\t\t\t});\n\t\t\t\tthis.isLoading = false;\n\t\t\t\tthis.$emit('modal-close');\n\t\t\t\tthis.$nextTick(() => {\n\t\t\t\t\tthis.$router.replace(`${this.$route.path}?hash=${Utils.createId()}`);\n\t\t\t\t});\n\t\t\t}).catch((errorResponse) => {\n\t\t\t\ttry {\n\t\t\t\t\tvar errorTitle;\n\t\t\t\t\tvar errorMessage;\n\t\t\t\t\tif (errorResponse.response.data == 'refundExceedsAmount') {\n\t\t\t\t\t\terrorTitle = this.$tc('vrpayment-order.refundAction.refundExceedsTotalError.title');\n\t\t\t\t\t\terrorMessage = this.$tc('vrpayment-order.refundAction.refundExceedsTotalError.messageRefundAmountExceedsAvailableBalance');\n\t\t\t\t\t} else {\n\t\t\t\t\t\terrorTitle = errorResponse.response.data.errors[0].title;\n\t\t\t\t\t\terrorMessage = errorResponse.response.data.errors[0].detail;\n\t\t\t\t\t}\n\t\t\t\t\tthis.createNotificationError({\n\t\t\t\t\t\ttitle: errorTitle,\n\t\t\t\t\t\tmessage: errorMessage,\n\t\t\t\t\t\tautoClose: false\n\t\t\t\t\t});\n\t\t\t\t} catch (e) {\n\t\t\t\t\tthis.createNotificationError({\n\t\t\t\t\t\ttitle: errorResponse.title,\n\t\t\t\t\t\tmessage: errorResponse.message,\n\t\t\t\t\t\tautoClose: false\n\t\t\t\t\t});\n\t\t\t\t} finally {\n\t\t\t\t\tthis.isLoading = false;\n\t\t\t\t\tthis.$emit('modal-close');\n\t\t\t\t\tthis.$nextTick(() => {\n\t\t\t\t\t\tthis.$router.replace(`${this.$route.path}?hash=${Utils.createId()}`);\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\t}\n});\n","{% block vrpayment_order_action_void %}\n\n\n\t{% block vrpayment_order_action_void_amount %}\n {# Review if this v-model:checked=\"isVoid\" needs to change to checked #}\n\t\t\n \n\t{% endblock %}\n\n\t{% block vrpayment_order_action_void_confirm_button %}\n\t\n\t{% endblock %}\n\n\t\n\n{% endblock %}\n","/* global Shopware */\n\nimport template from './index.html.twig';\n\nconst {Component, Mixin, Filter, Utils} = Shopware;\n\nComponent.register('vrpayment-order-action-void', {\n\ttemplate,\n\n\tinject: ['VRPaymentTransactionVoidService'],\n\n\tmixins: [\n\t\tMixin.getByName('notification')\n\t],\n\n\tprops: {\n\t\ttransactionData: {\n\t\t\ttype: Object,\n\t\t\trequired: true\n\t\t}\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\tisLoading: true,\n\t\t\tisVoid: false\n\t\t};\n\t},\n\n\tcomputed: {\n\t\tdateFilter() {\n\t\t\treturn Filter.getByName('date');\n\t\t},\n\t\tlineItemColumns() {\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\tproperty: 'uniqueId',\n\t\t\t\t\tlabel: this.$tc('vrpayment-order.refund.types.uniqueId'),\n\t\t\t\t\trawData: false,\n\t\t\t\t\tallowResize: true,\n\t\t\t\t\tprimary: true,\n\t\t\t\t\twidth: 'auto'\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tproperty: 'name',\n\t\t\t\t\tlabel: this.$tc('vrpayment-order.refund.types.name'),\n\t\t\t\t\trawData: true,\n\t\t\t\t\tallowResize: true,\n\t\t\t\t\tsortable: true,\n\t\t\t\t\twidth: 'auto'\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tproperty: 'quantity',\n\t\t\t\t\tlabel: this.$tc('vrpayment-order.refund.types.quantity'),\n\t\t\t\t\trawData: true,\n\t\t\t\t\tallowResize: true,\n\t\t\t\t\twidth: 'auto'\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tproperty: 'amountIncludingTax',\n\t\t\t\t\tlabel: this.$tc('vrpayment-order.refund.types.amountIncludingTax'),\n\t\t\t\t\trawData: true,\n\t\t\t\t\tallowResize: true,\n\t\t\t\t\tinlineEdit: 'string',\n\t\t\t\t\twidth: 'auto'\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tproperty: 'type',\n\t\t\t\t\tlabel: this.$tc('vrpayment-order.refund.types.type'),\n\t\t\t\t\trawData: true,\n\t\t\t\t\tallowResize: true,\n\t\t\t\t\tsortable: true,\n\t\t\t\t\twidth: 'auto'\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tproperty: 'taxAmount',\n\t\t\t\t\tlabel: this.$tc('vrpayment-order.refund.types.taxAmount'),\n\t\t\t\t\trawData: true,\n\t\t\t\t\tallowResize: true,\n\t\t\t\t\twidth: 'auto'\n\t\t\t\t}\n\t\t\t];\n\t\t}\n\t},\n\n\tcreated() {\n\t\tthis.createdComponent();\n\t},\n\n\tmethods: {\n\t\tcreatedComponent() {\n\t\t\tthis.isLoading = false;\n\t\t\tthis.currency = this.transactionData.transactions[0].currency;\n\t\t\tthis.refundableAmount = this.transactionData.transactions[0].amountIncludingTax;\n\t\t\tthis.refundAmount = this.transactionData.transactions[0].amountIncludingTax;\n\t\t},\n\n\t\tvoidPayment() {\n\t\t\tif (this.isVoid) {\n\t\t\t\tthis.isLoading = true;\n\t\t\t\tthis.VRPaymentTransactionVoidService.createTransactionVoid(\n\t\t\t\t\tthis.transactionData.transactions[0].metaData.salesChannelId,\n\t\t\t\t\tthis.transactionData.transactions[0].id\n\t\t\t\t).then(() => {\n\t\t\t\t\tthis.createNotificationSuccess({\n\t\t\t\t\t\ttitle: this.$tc('vrpayment-order.voidAction.successTitle'),\n\t\t\t\t\t\tmessage: this.$tc('vrpayment-order.voidAction.successMessage')\n\t\t\t\t\t});\n\t\t\t\t\tthis.isLoading = false;\n\t\t\t\t\tthis.$emit('modal-close');\n\t\t\t\t\tthis.$nextTick(() => {\n\t\t\t\t\t\tthis.$router.replace(`${this.$route.path}?hash=${Utils.createId()}`);\n\t\t\t\t\t});\n\t\t\t\t}).catch((errorResponse) => {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tthis.createNotificationError({\n\t\t\t\t\t\t\ttitle: errorResponse.response.data.errors[0].title,\n\t\t\t\t\t\t\tmessage: errorResponse.response.data.errors[0].detail,\n\t\t\t\t\t\t\tautoClose: false\n\t\t\t\t\t\t});\n\t\t\t\t\t} catch (e) {\n\t\t\t\t\t\tthis.createNotificationError({\n\t\t\t\t\t\t\ttitle: errorResponse.title,\n\t\t\t\t\t\t\tmessage: errorResponse.message,\n\t\t\t\t\t\t\tautoClose: false\n\t\t\t\t\t\t});\n\t\t\t\t\t} finally {\n\t\t\t\t\t\tthis.isLoading = false;\n\t\t\t\t\t\tthis.$emit('modal-close');\n\t\t\t\t\t\tthis.$nextTick(() => {\n\t\t\t\t\t\t\tthis.$router.replace(`${this.$route.path}?hash=${Utils.createId()}`);\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t}\n});\n","{% block vrpayment_order_detail %}\n
\n\t
\n\t\t\n\t\t\t\n\t\t\n\t\t{% block vrpayment_order_transaction_history_card %}\n\t\t\n\t\t\t\n\n\t\t\n\t\t{% endblock %}\n\t\t{% block vrpayment_order_transaction_line_items_card %}\n \n \n \n\t\t{% endblock %}\n\t\t{% block vrpayment_order_transaction_refunds_card %}\n\t\t 0\">\n\t\t\t\n\n\t\t\n\t\t{% endblock %}\n\t\t{% block vrpayment_order_actions_modal_refund_partial %}\n\t\t\t\n\t\t\t\n\t\t{% endblock %}\n\t\t{% block vrpayment_order_actions_modal_refund %}\n\t\t\n\t\t\n\t\t{% endblock %}\n\t\t{% block vrpayment_order_actions_modal_refund_by_amount %}\n\t\t\t\n\t\t\t\n\t\t{% endblock %}\n\t\t{% block vrpayment_order_actions_modal_completion%}\n\t\t\n\t\t\n\t\t{% endblock %}\n\t\t{% block vrpayment_order_actions_modal_void %}\n\t\t\n\t\t\n\t\t{% endblock %}\n\t
\n\t\n
\n{% endblock %}\n","/* global Shopware */\n\nimport '../../component/vrpayment-order-action-completion';\nimport '../../component/vrpayment-order-action-refund';\nimport '../../component/vrpayment-order-action-refund-partial';\nimport '../../component/vrpayment-order-action-refund-by-amount';\nimport '../../component/vrpayment-order-action-void';\nimport template from './index.html.twig';\nimport './index.scss';\n\nconst {Component, Mixin, Filter, Context, Utils} = Shopware;\nconst Criteria = Shopware.Data.Criteria;\n\nComponent.register('vrpayment-order-detail', {\n\ttemplate,\n\n\tinject: [\n\t\t'VRPaymentTransactionService',\n\t\t'VRPaymentRefundService',\n\t\t'repositoryFactory'\n\t],\n\n\tmixins: [\n\t\tMixin.getByName('notification')\n\t],\n\n\tdata() {\n\t\treturn {\n\t\t\ttransactionData: {\n\t\t\t\ttransactions: [],\n\t\t\t\trefunds: []\n\t\t\t},\n\t\t\ttransaction: {},\n\t\t\tlineItems: [],\n\t\t\trefundableQuantity: 0,\n\t\t\titemRefundableQuantity: 0,\n\t\t\tisLoading: true,\n\t\t\torderId: '',\n\t\t\tcurrency: '',\n\t\t\tmodalType: '',\n\t\t\trefundAmount: 0.00,\n\t\t\trefundableAmount: 0.00,\n\t\t\titemRefundedAmount: 0.00,\n\t\t\titemRefundedQuantity: 0,\n\t\t\titemRefundableAmount: 0.00,\n\t\t\tcurrentLineItem: '',\n\t\t\trefundLineItemQuantity: [],\n\t\t\trefundLineItemAmount: [],\n\t\t\tselectedItems: []\n\t\t};\n\t},\n\n\tmetaInfo() {\n\t\treturn {\n\t\t\ttitle: this.$tc('vrpayment-order.header')\n\t\t};\n\t},\n\n\n\tcomputed: {\n\t\tdateFilter() {\n\t\t\treturn Filter.getByName('date');\n\t\t},\n\n\t\trelatedResourceColumns() {\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\tproperty: 'paymentMethodName',\n\t\t\t\t\tlabel: this.$tc('vrpayment-order.transactionHistory.types.payment_method'),\n\t\t\t\t\trawData: true\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tproperty: 'state',\n\t\t\t\t\tlabel: this.$tc('vrpayment-order.transactionHistory.types.state'),\n\t\t\t\t\trawData: true\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tproperty: 'currency',\n\t\t\t\t\tlabel: this.$tc('vrpayment-order.transactionHistory.types.currency'),\n\t\t\t\t\trawData: true\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tproperty: 'authorized_amount',\n\t\t\t\t\tlabel: this.$tc('vrpayment-order.transactionHistory.types.authorized_amount'),\n\t\t\t\t\trawData: true\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tproperty: 'id',\n\t\t\t\t\tlabel: this.$tc('vrpayment-order.transactionHistory.types.transaction'),\n\t\t\t\t\trawData: true\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tproperty: 'customerId',\n\t\t\t\t\tlabel: this.$tc('vrpayment-order.transactionHistory.types.customer'),\n\t\t\t\t\trawData: true\n\t\t\t\t}\n\t\t\t];\n\t\t},\n\n\t\tlineItemColumns() {\n\t\t\treturn [\n\t\t\t // It must be set in order to have correctly working checkbox mechanism\n\t\t\t\t{\n\t\t\t\t\tproperty: 'id',\n\t\t\t\t\trawData: true,\n\t\t\t\t\tvisible: false,\n\t\t\t\t\tprimary: true\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tproperty: 'uniqueId',\n\t\t\t\t\tlabel: this.$tc('vrpayment-order.lineItem.types.uniqueId'),\n\t\t\t\t\trawData: true,\n\t\t\t\t\tvisible: false,\n\t\t\t\t\tprimary: true\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tproperty: 'name',\n\t\t\t\t\tlabel: this.$tc('vrpayment-order.lineItem.types.name'),\n\t\t\t\t\trawData: true\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tproperty: 'quantity',\n\t\t\t\t\tlabel: this.$tc('vrpayment-order.lineItem.types.quantity'),\n\t\t\t\t\trawData: true\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tproperty: 'amountIncludingTax',\n\t\t\t\t\tlabel: this.$tc('vrpayment-order.lineItem.types.amountIncludingTax'),\n\t\t\t\t\trawData: true\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tproperty: 'type',\n\t\t\t\t\tlabel: this.$tc('vrpayment-order.lineItem.types.type'),\n\t\t\t\t\trawData: true\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tproperty: 'taxAmount',\n\t\t\t\t\tlabel: this.$tc('vrpayment-order.lineItem.types.taxAmount'),\n\t\t\t\t\trawData: true\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tproperty: 'refundableQuantity',\n\t\t\t\t\trawData: true,\n\t\t\t\t\tvisible: false,\n\t\t\t\t},\n\t\t\t];\n\t\t},\n\n\t\trefundColumns() {\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\tproperty: 'id',\n\t\t\t\t\tlabel: this.$tc('vrpayment-order.refund.types.id'),\n\t\t\t\t\trawData: true,\n\t\t\t\t\tvisible: true,\n\t\t\t\t\tprimary: true\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tproperty: 'amount',\n\t\t\t\t\tlabel: this.$tc('vrpayment-order.refund.types.amount'),\n\t\t\t\t\trawData: true\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tproperty: 'state',\n\t\t\t\t\tlabel: this.$tc('vrpayment-order.refund.types.state'),\n\t\t\t\t\trawData: true\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tproperty: 'createdOn',\n\t\t\t\t\tlabel: this.$tc('vrpayment-order.refund.types.createdOn'),\n\t\t\t\t\trawData: true\n\t\t\t\t}\n\t\t\t];\n\t\t}\n\t},\n\n\twatch: {\n\t\t'$route'() {\n\t\t\tthis.resetDataAttributes();\n\t\t\tthis.createdComponent();\n\t\t}\n\t},\n\n\tcreated() {\n\t\tthis.createdComponent();\n\t},\n\n\tmethods: {\n\t\tcreatedComponent() {\n\t\t\tthis.orderId = this.$route.params.id;\n\t\t\tconst orderRepository = this.repositoryFactory.create('order');\n\t\t\tconst orderCriteria = new Criteria(1, 1);\n\t\t\torderCriteria.addAssociation('transactions');\n\t\t\torderCriteria.getAssociation('transactions').addSorting(Criteria.sort('createdAt', 'DESC'));\n\n\t\t\torderRepository.get(this.orderId, Context.api, orderCriteria).then((order) => {\n\t\t\t\tthis.order = order;\n\t\t\t\tthis.isLoading = false;\n\t\t\t\tvar totalAmountTemp = 0;\n\t\t\t\tvar refundsAmountTemp = 0;\n\t\t\t\tconst vrpaymentTransactionId = order.transactions[0].customFields.vrpayment_transaction_id;\n\t\t\t\tthis.VRPaymentTransactionService.getTransactionData(order.salesChannelId, vrpaymentTransactionId)\n\t\t\t\t\t.then((VRPaymentTransaction) => {\n\t\t\t\t\t\tthis.currency = VRPaymentTransaction.transactions[0].currency;\n\n\t\t\t\t\t\tVRPaymentTransaction.transactions[0].authorized_amount = Utils.format.currency(\n\t\t\t\t\t\t\tVRPaymentTransaction.transactions[0].authorizationAmount,\n\t\t\t\t\t\t\tthis.currency\n\t\t\t\t\t\t);\n\n\t\t\t\t\t\tVRPaymentTransaction.refunds.forEach((refund) => {\n\t\t\t\t\t\t\trefundsAmountTemp = parseFloat(parseFloat(refundsAmountTemp) + parseFloat(refund.amount));\n\t\t\t\t\t\t\trefund.amount = Utils.format.currency(\n\t\t\t\t\t\t\t\trefund.amount,\n\t\t\t\t\t\t\t\tthis.currency\n\t\t\t\t\t\t\t);\n\n\t\t\t\t\t\t\trefund.reductions.forEach((reduction) => {\n\t\t\t\t\t\t\t if (reduction.quantityReduction > 0) {\n if (this.refundLineItemQuantity[reduction.lineItemUniqueId] === undefined) {\n this.refundLineItemQuantity[reduction.lineItemUniqueId] = reduction.quantityReduction;\n } else {\n this.refundLineItemQuantity[reduction.lineItemUniqueId] += reduction.quantityReduction;\n }\n\t\t\t\t\t\t\t }\n if (reduction.unitPriceReduction > 0) {\n if (this.refundLineItemAmount[reduction.lineItemUniqueId] === undefined) {\n this.refundLineItemAmount[reduction.lineItemUniqueId] = reduction.unitPriceReduction;\n } else {\n this.refundLineItemAmount[reduction.lineItemUniqueId] += reduction.unitPriceReduction;\n }\n }\n\t\t\t\t\t\t\t});\n\n\t\t\t\t\t\t});\n\n\t\t\t\t\t\tVRPaymentTransaction.transactions[0].lineItems.forEach((lineItem) => {\n\t\t\t\t\t\t\tif (!lineItem.id) {\n\t\t\t\t\t\t\t\tlineItem.id = lineItem.uniqueId;\n }\n\n lineItem.itemRefundedAmount = parseFloat(this.refundLineItemAmount[lineItem.uniqueId] || 0) * parseInt(lineItem.quantity);\n lineItem.amountIncludingTax = parseFloat(lineItem.amountIncludingTax) || 0;\n\n lineItem.itemRefundedQuantity = parseInt(this.refundLineItemQuantity[lineItem.uniqueId]) || 0;\n lineItem.refundableAmount = parseFloat(\n (lineItem.amountIncludingTax - lineItem.itemRefundedAmount).toFixed(2)\n );\n\n\t\t\t\t\t\t\tlineItem.amountIncludingTax = Utils.format.currency(\n\t\t\t\t\t\t\t\tlineItem.amountIncludingTax,\n\t\t\t\t\t\t\t\tthis.currency\n\t\t\t\t\t\t\t);\n\n\t\t\t\t\t\t\tlineItem.taxAmount = Utils.format.currency(\n\t\t\t\t\t\t\t\tlineItem.taxAmount,\n\t\t\t\t\t\t\t\tthis.currency\n\t\t\t\t\t\t\t);\n\n\t\t\t\t\t\t\ttotalAmountTemp = parseFloat(parseFloat(totalAmountTemp) + parseFloat(lineItem.unitPriceIncludingTax * lineItem.quantity));\n\n\t\t\t\t\t\t\tlineItem.refundableQuantity = parseInt(\n\t\t\t\t\t\t\t\tparseInt(lineItem.quantity) - parseInt(this.refundLineItemQuantity[lineItem.uniqueId] || 0)\n\t\t\t\t\t\t\t);\n\n\t\t\t\t\t\t});\n\n\t\t\t\t\t\tthis.lineItems = VRPaymentTransaction.transactions[0].lineItems;\n\t\t\t\t\t\tthis.transactionData = VRPaymentTransaction;\n\t\t\t\t\t\tthis.transaction = this.transactionData.transactions[0];\n\t\t\t\t\t\tthis.refundAmount = Number(this.transactionData.transactions[0].amountIncludingTax);\n\t\t\t\t\t\tthis.refundableAmount = parseFloat(parseFloat(totalAmountTemp) - parseFloat(refundsAmountTemp));\n\n\t\t\t\t\t}).catch((errorResponse) => {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tthis.createNotificationError({\n\t\t\t\t\t\t\ttitle: this.$tc('vrpayment-order.paymentDetails.error.title'),\n\t\t\t\t\t\t\tmessage: errorResponse.message,\n\t\t\t\t\t\t\tautoClose: false\n\t\t\t\t\t\t});\n\t\t\t\t\t} catch (e) {\n\t\t\t\t\t\tthis.createNotificationError({\n\t\t\t\t\t\t\ttitle: this.$tc('vrpayment-order.paymentDetails.error.title'),\n\t\t\t\t\t\t\tmessage: errorResponse.message,\n\t\t\t\t\t\t\tautoClose: false\n\t\t\t\t\t\t});\n\t\t\t\t\t} finally {\n\t\t\t\t\t\tthis.isLoading = false;\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t});\n\t\t},\n\t\tdownloadPackingSlip() {\n\t\t\twindow.open(\n\t\t\t\tthis.VRPaymentTransactionService.getPackingSlip(\n\t\t\t\t\tthis.transaction.metaData.salesChannelId,\n\t\t\t\t\tthis.transaction.id\n\t\t\t\t),\n\t\t\t\t'_blank'\n\t\t\t);\n\t\t},\n\n\t\tdownloadInvoice() {\n\t\t\twindow.open(\n\t\t\t\tthis.VRPaymentTransactionService.getInvoiceDocument(\n\t\t\t\t\tthis.transaction.metaData.salesChannelId,\n\t\t\t\t\tthis.transaction.id\n\t\t\t\t),\n\t\t\t\t'_blank'\n\t\t\t);\n\t\t},\n\n\t\tresetDataAttributes() {\n\t\t\tthis.transactionData = {\n\t\t\t\ttransactions: [],\n\t\t\t\trefunds: []\n\t\t\t};\n\t\t\tthis.lineItems = [];\n\t\t\tthis.refundLineItemQuantity = [];\n\t\t\tthis.refundLineItemAmount = [];\n\t\t\tthis.isLoading = true;\n\t\t},\n\n\t\tspawnModal(modalType, lineItemId, refundableQuantity, itemRefundableAmount) {\n\t\t\tthis.modalType = modalType;\n\t\t\tthis.currentLineItem = lineItemId;\n\t\t\tthis.itemRefundableQuantity = refundableQuantity;\n this.itemRefundableAmount = !isNaN(itemRefundableAmount) ? Math.round(itemRefundableAmount * 100) / 100 : 0;\n\t\t},\n\n\t\tcloseModal() {\n\t\t\tthis.modalType = '';\n\t\t},\n\n\t\tlineItemRefund(lineItemId) {\n\t\t\tthis.isLoading = true;\n\t\t\tthis.VRPaymentRefundService.createRefund(\n\t\t\t\tthis.transactionData.transactions[0].metaData.salesChannelId,\n\t\t\t\tthis.transactionData.transactions[0].id,\n\t\t\t\t0,\n\t\t\t\tlineItemId\n\t\t\t).then(() => {\n\t\t\t\tthis.createNotificationSuccess({\n\t\t\t\t\ttitle: this.$tc('vrpayment-order.refundAction.successTitle'),\n\t\t\t\t\tmessage: this.$tc('vrpayment-order.refundAction.successMessage')\n\t\t\t\t});\n this.isLoading = false;\n this.$emit('modal-close');\n this.$nextTick(() => {\n this.$router.replace(`${this.$route.path}?hash=${Utils.createId()}`);\n });\n\t\t\t}).catch((errorResponse) => {\n\t\t\t\ttry {\n\t\t\t\t\tthis.createNotificationError({\n\t\t\t\t\t\ttitle: errorResponse.response.data.errors[0].title,\n\t\t\t\t\t\tmessage: errorResponse.response.data.errors[0].detail,\n\t\t\t\t\t\tautoClose: false\n\t\t\t\t\t});\n\t\t\t\t} catch (e) {\n\t\t\t\t\tthis.createNotificationError({\n\t\t\t\t\t\ttitle: errorResponse.title,\n\t\t\t\t\t\tmessage: errorResponse.response.data,\n\t\t\t\t\t\tautoClose: false\n\t\t\t\t\t});\n\t\t\t\t} finally {\n this.isLoading = false;\n this.$emit('modal-close');\n this.$nextTick(() => {\n this.$router.replace(`${this.$route.path}?hash=${Utils.createId()}`);\n });\n\t\t\t\t}\n\t\t\t});\n\t\t},\n\t\tisSelectable(item) {\n\t\t\treturn item.refundableQuantity > 0 && item.refundableAmount > 0 && item.itemRefundedAmount == 0 && item.itemRefundedQuantity == 0;\n\t\t},\n\t\tonSelectionChanged(selection) {\n\t\t\tthis.selectedItems = Object.values(selection);\n\t\t},\n onPerformBulkAction() {\n if (this.selectedItems.length) {\n // Set isLoading to true to show the loader\n this.isLoading = true;\n\n // Force the DOM to update before proceeding with the asynchronous operations\n this.$nextTick(() => {\n const refundPromises = this.selectedItems.map((item) => {\n return this.lineItemRefundBulk(item.uniqueId); // Simulated refund action with delay\n });\n\n // Wait for all refund promises to complete\n Promise.all(refundPromises)\n .then(() => {\n // Once all promises are resolved, hide the loader and close the modal\n this.isLoading = false;\n this.$emit('modal-close');\n this.$nextTick(() => {\n this.$router.replace(`${this.$route.path}?hash=${Utils.createId()}`);\n });\n })\n .catch((error) => {\n // Handle any errors during the refund process\n this.createNotificationError({\n title: 'Error',\n message: 'Something went wrong with the refunds',\n autoClose: false\n });\n this.isLoading = false; // Ensure the loader is hidden even on error\n });\n });\n }\n },\n lineItemRefundBulk(lineItemId) {\n return new Promise((resolve, reject) => {\n this.VRPaymentRefundService.createRefund(\n this.transactionData.transactions[0].metaData.salesChannelId,\n this.transactionData.transactions[0].id,\n 0,\n lineItemId\n )\n .then(() => {\n this.createNotificationSuccess({\n title: this.$tc('vrpayment-order.refundAction.successTitle'),\n message: this.$tc('vrpayment-order.refundAction.successMessage')\n });\n resolve();\n })\n .catch((errorResponse) => {\n try {\n this.createNotificationError({\n title: errorResponse.response.data.errors[0].title,\n message: errorResponse.response.data.errors[0].detail,\n autoClose: false\n });\n } catch (e) {\n this.createNotificationError({\n title: errorResponse.title,\n message: errorResponse.response.data,\n autoClose: false\n });\n } finally {\n reject();\n }\n });\n });\n },\n\t}\n});\n","/* global Shopware */\n\nimport './extension/sw-order';\nimport './page/vrpayment-order-detail';\n\nimport deDE from './snippet/de-DE.json';\nimport enGB from './snippet/en-GB.json';\nimport frFR from './snippet/fr-FR.json';\nimport itIT from './snippet/it-IT.json';\n\nconst {Module} = Shopware;\n\nModule.register('vrpayment-order', {\n\ttype: 'plugin',\n\tname: 'VRPayment',\n\ttitle: 'vrpayment-order.general.title',\n\tdescription: 'vrpayment-order.general.descriptionTextModule',\n\tversion: '1.0.1',\n\ttargetVersion: '1.0.1',\n\tcolor: '#2b52ff',\n\n\tsnippets: {\n\t\t'de-DE': deDE,\n\t\t'en-GB': enGB,\n\t\t'fr-FR': frFR,\n\t\t'it-IT': itIT\n\t},\n\n\trouteMiddleware(next, currentRoute) {\n\t\tif (currentRoute.name === 'sw.order.detail') {\n\t\t\tcurrentRoute.children.push({\n\t\t\t\tcomponent: 'vrpayment-order-detail',\n\t\t\t\tname: 'vrpayment.order.detail',\n\t\t\t\tisChildren: true,\n\t\t\t\tpath: '/sw/order/vrpayment/detail/:id'\n\t\t\t});\n\t\t}\n\t\tnext(currentRoute);\n\t}\n});\n","Shopware.Service('privileges').addPrivilegeMappingEntry({\n category: 'permissions',\n parent: 'vrpayment',\n key: 'vrpayment',\n roles: {\n viewer: {\n privileges: [\n 'sales_channel:read',\n 'sales_channel_payment_method:read',\n 'system_config:read'\n ],\n dependencies: []\n },\n editor: {\n privileges: [\n 'sales_channel:update',\n 'sales_channel_payment_method:create',\n 'sales_channel_payment_method:update',\n 'system_config:update',\n 'system_config:create',\n 'system_config:delete'\n ],\n dependencies: [\n 'vrpayment.viewer'\n ]\n }\n }\n});\n\nShopware.Service('privileges').addPrivilegeMappingEntry({\n category: 'permissions',\n parent: null,\n key: 'sales_channel',\n roles: {\n viewer: {\n privileges: [\n 'sales_channel_payment_method:read'\n ]\n },\n editor: {\n privileges: [\n 'payment_method:update'\n ]\n },\n creator: {\n privileges: [\n 'payment_method:create',\n 'shipping_method:create',\n 'delivery_time:create'\n ]\n },\n deleter: {\n privileges: [\n 'payment_method:delete'\n ]\n }\n }\n});\n","{% block vrpayment_settings %}\n\n\n\t{% block vrpayment_settings_header %}\n\t\n\t{% endblock %}\n\n\t{% block vrpayment_settings_actions %}\n\t\n\t{% endblock %}\n\n\t{% block vrpayment_settings_content %}\n\t\n\t{% endblock %}\n\n{% endblock %}\n","export const CONFIG_DOMAIN = 'VRPaymentPayment.config';\nexport const CONFIG_APPLICATION_KEY = CONFIG_DOMAIN + '.' + 'applicationKey';\nexport const CONFIG_EMAIL_ENABLED = CONFIG_DOMAIN + '.' + 'emailEnabled';\nexport const CONFIG_INTEGRATION = CONFIG_DOMAIN + '.' + 'integration';\nexport const CONFIG_LINE_ITEM_CONSISTENCY_ENABLED = CONFIG_DOMAIN + '.' + 'lineItemConsistencyEnabled';\nexport const CONFIG_SPACE_ID = CONFIG_DOMAIN + '.' + 'spaceId';\nexport const CONFIG_SPACE_VIEW_ID = CONFIG_DOMAIN + '.' + 'spaceViewId';\nexport const CONFIG_STOREFRONT_INVOICE_DOWNLOAD_ENABLED = CONFIG_DOMAIN + '.' + 'storefrontInvoiceDownloadEnabled';\nexport const CONFIG_USER_ID = CONFIG_DOMAIN + '.' + 'userId';\nexport const CONFIG_STOREFRONT_WEBHOOKS_UPDATE_ENABLED = CONFIG_DOMAIN + '.' + 'storefrontWebhooksUpdateEnabled';\nexport const CONFIG_STOREFRONT_PAYMENTS_UPDATE_ENABLED = CONFIG_DOMAIN + '.' + 'storefrontPaymentsUpdateEnabled';\n\nexport default {\n CONFIG_DOMAIN,\n CONFIG_APPLICATION_KEY,\n CONFIG_EMAIL_ENABLED,\n CONFIG_INTEGRATION,\n CONFIG_LINE_ITEM_CONSISTENCY_ENABLED,\n CONFIG_SPACE_ID,\n CONFIG_SPACE_VIEW_ID,\n CONFIG_STOREFRONT_INVOICE_DOWNLOAD_ENABLED,\n CONFIG_USER_ID,\n CONFIG_STOREFRONT_WEBHOOKS_UPDATE_ENABLED,\n CONFIG_STOREFRONT_PAYMENTS_UPDATE_ENABLED\n};","/* global Shopware */\n\nimport template from './index.html.twig';\nimport constants from './configuration-constants';\n\nconst {Component, Mixin} = Shopware;\n\nComponent.register('vrpayment-settings', {\n\n template: template,\n\n inject: [\n 'acl',\n 'VRPaymentConfigurationService'\n ],\n\n mixins: [\n Mixin.getByName('notification'),\n Mixin.getByName('sw-inline-snippet')\n ],\n\n data() {\n return {\n\n config: {},\n\n isLoading: false,\n isTesting: false,\n\n isSaveSuccessful: false,\n\n applicationKeyFilled: false,\n applicationKeyErrorState: false,\n\n spaceIdFilled: false,\n spaceIdErrorState: false,\n\n userIdFilled: false,\n userIdErrorState: false,\n\n isSetDefaultPaymentSuccessful: false,\n isSettingDefaultPaymentMethods: false,\n\n configIntegrationDefaultValue: 'payment_page',\n configEmailEnabledDefaultValue: true,\n configLineItemConsistencyEnabledDefaultValue: true,\n configStorefrontInvoiceDownloadEnabledEnabledDefaultValue: true,\n configStorefrontWebhooksUpdateEnabledDefaultValue: true,\n configStorefrontPaymentsUpdateEnabledDefaultValue: true,\n\n ...constants\n };\n },\n\n props: {\n isLoading: {\n type: Boolean,\n required: true\n }\n },\n\n metaInfo() {\n return {\n title: this.$createTitle()\n };\n },\n\n watch: {\n config: {\n handler(configData) {\n const defaultConfig = this.$refs.configComponent.allConfigs.null;\n const salesChannelId = this.$refs.configComponent.selectedSalesChannelId;\n if (salesChannelId === null) {\n\n this.applicationKeyFilled = !!this.config[this.CONFIG_APPLICATION_KEY];\n this.spaceIdFilled = !!this.config[this.CONFIG_SPACE_ID];\n this.userIdFilled = !!this.config[this.CONFIG_USER_ID];\n\n if (!(this.CONFIG_INTEGRATION in this.config)) {\n this.config[this.CONFIG_INTEGRATION] = this.configIntegrationDefaultValue;\n }\n\n if (!(this.CONFIG_EMAIL_ENABLED in this.config)) {\n this.config[this.CONFIG_EMAIL_ENABLED] = this.configEmailEnabledDefaultValue;\n }\n\n if (!(this.CONFIG_LINE_ITEM_CONSISTENCY_ENABLED in this.config)) {\n this.config[this.CONFIG_LINE_ITEM_CONSISTENCY_ENABLED] = this.configLineItemConsistencyEnabledDefaultValue;\n }\n\n if (!(this.CONFIG_STOREFRONT_INVOICE_DOWNLOAD_ENABLED in this.config)) {\n this.config[this.CONFIG_STOREFRONT_INVOICE_DOWNLOAD_ENABLED] = this.configStorefrontInvoiceDownloadEnabledEnabledDefaultValue;\n }\n\n if (!(this.CONFIG_STOREFRONT_WEBHOOKS_UPDATE_ENABLED in this.config)) {\n this.config[this.CONFIG_STOREFRONT_WEBHOOKS_UPDATE_ENABLED] = this.configStorefrontWebhooksUpdateEnabledDefaultValue;\n }\n\n if (!(this.CONFIG_STOREFRONT_PAYMENTS_UPDATE_ENABLED in this.config)) {\n this.config[this.CONFIG_STOREFRONT_PAYMENTS_UPDATE_ENABLED] = this.configStorefrontPaymentsUpdateEnabledDefaultValue;\n }\n\n } else {\n\n this.applicationKeyFilled = !!this.config[this.CONFIG_APPLICATION_KEY] || !!defaultConfig[this.CONFIG_APPLICATION_KEY];\n this.spaceIdFilled = !!this.config[this.CONFIG_SPACE_ID] || !!defaultConfig[this.CONFIG_SPACE_ID];\n this.userIdFilled = !!this.config[this.CONFIG_USER_ID] || !!defaultConfig[this.CONFIG_USER_ID];\n\n\n if (!(this.CONFIG_INTEGRATION in this.config) || !(this.CONFIG_INTEGRATION in defaultConfig)) {\n this.config[this.CONFIG_INTEGRATION] = this.configIntegrationDefaultValue;\n }\n\n if (!(this.CONFIG_EMAIL_ENABLED in this.config) || !(this.CONFIG_EMAIL_ENABLED in defaultConfig)) {\n this.config[this.CONFIG_EMAIL_ENABLED] = this.configEmailEnabledDefaultValue;\n }\n\n if (!(this.CONFIG_LINE_ITEM_CONSISTENCY_ENABLED in this.config) || !(this.CONFIG_LINE_ITEM_CONSISTENCY_ENABLED in defaultConfig)) {\n this.config[this.CONFIG_LINE_ITEM_CONSISTENCY_ENABLED] = this.configLineItemConsistencyEnabledDefaultValue;\n }\n\n if (!(this.CONFIG_STOREFRONT_INVOICE_DOWNLOAD_ENABLED in this.config) || !(this.CONFIG_STOREFRONT_INVOICE_DOWNLOAD_ENABLED in defaultConfig)) {\n this.config[this.CONFIG_STOREFRONT_INVOICE_DOWNLOAD_ENABLED] = this.configStorefrontInvoiceDownloadEnabledEnabledDefaultValue;\n }\n\n if (!(this.CONFIG_STOREFRONT_WEBHOOKS_UPDATE_ENABLED in this.config) || !(this.CONFIG_STOREFRONT_WEBHOOKS_UPDATE_ENABLED in defaultConfig)) {\n this.config[this.CONFIG_STOREFRONT_WEBHOOKS_UPDATE_ENABLED] = this.configStorefrontWebhooksUpdateEnabledDefaultValue;\n }\n\n if (!(this.CONFIG_STOREFRONT_PAYMENTS_UPDATE_ENABLED in this.config) || !(this.CONFIG_STOREFRONT_PAYMENTS_UPDATE_ENABLED in defaultConfig)) {\n this.config[this.CONFIG_STOREFRONT_PAYMENTS_UPDATE_ENABLED] = this.configStorefrontPaymentsUpdateEnabledDefaultValue;\n }\n }\n\n this.$emit('salesChannelChanged');\n this.$emit('update:value', configData);\n },\n deep: true\n }\n },\n\n methods: {\n checkTextFieldInheritance(value) {\n if (typeof value !== 'string') {\n return true;\n }\n\n return value.length <= 0;\n },\n\n checkNumberFieldInheritance(value) {\n if (typeof value !== 'number') {\n return true;\n }\n\n return value.length <= 0;\n },\n\n checkBoolFieldInheritance(value) {\n return typeof value !== 'boolean';\n },\n\n getInheritValue(key) {\n if (this.selectedSalesChannelId == null ) {\n return this.actualConfigData[key];\n } else {\n return this.allConfigs['null'][key];\n }\n },\n\n onSave() {\n if (!(this.spaceIdFilled && this.userIdFilled && this.applicationKeyFilled)) {\n this.setErrorStates();\n return;\n }\n this.save();\n },\n\n save() {\n this.isLoading = true;\n\n this.$refs.configComponent.save().then((res) => {\n if (res) {\n this.config = res;\n }\n this.registerWebHooks();\n this.synchronizePaymentMethodConfiguration();\n this.installOrderDeliveryStates();\n }).catch((e) => {\n console.error('Error:', e);\n this.isLoading = false;\n });\n },\n\n registerWebHooks() {\n if (this.config[this.CONFIG_STOREFRONT_WEBHOOKS_UPDATE_ENABLED] === false) {\n return false;\n }\n\n this.VRPaymentConfigurationService.registerWebHooks(this.$refs.configComponent.selectedSalesChannelId)\n .then(() => {\n this.createNotificationSuccess({\n title: this.$tc('vrpayment-settings.settingForm.titleSuccess'),\n message: this.$tc('vrpayment-settings.settingForm.messageWebHookUpdated')\n });\n }).catch((e) => {\n this.createNotificationError({\n title: this.$tc('vrpayment-settings.settingForm.titleError'),\n message: this.$tc('vrpayment-settings.settingForm.messageWebHookError')\n });\n this.isLoading = false;\n console.error('Error:', e);\n });\n },\n\n synchronizePaymentMethodConfiguration() {\n if (this.config[this.CONFIG_STOREFRONT_PAYMENTS_UPDATE_ENABLED] === false) {\n return false;\n }\n\n this.VRPaymentConfigurationService.synchronizePaymentMethodConfiguration(this.$refs.configComponent.selectedSalesChannelId)\n .then(() => {\n this.createNotificationSuccess({\n title: this.$tc('vrpayment-settings.settingForm.titleSuccess'),\n message: this.$tc('vrpayment-settings.settingForm.messagePaymentMethodConfigurationUpdated')\n });\n this.isLoading = false;\n }).catch((e) => {\n this.createNotificationError({\n title: this.$tc('vrpayment-settings.settingForm.titleError'),\n message: this.$tc('vrpayment-settings.settingForm.messagePaymentMethodConfigurationError')\n });\n this.isLoading = false;\n console.error('Error:', e);\n });\n },\n\n installOrderDeliveryStates(){\n this.VRPaymentConfigurationService.installOrderDeliveryStates()\n .then(() => {\n this.createNotificationSuccess({\n title: this.$tc('vrpayment-settings.settingForm.titleSuccess'),\n message: this.$tc('vrpayment-settings.settingForm.messageOrderDeliveryStateUpdated')\n });\n this.isLoading = false;\n }).catch(() => {\n this.createNotificationError({\n title: this.$tc('vrpayment-settings.settingForm.titleError'),\n message: this.$tc('vrpayment-settings.settingForm.messageOrderDeliveryStateError')\n });\n this.isLoading = false;\n });\n },\n\n onSetPaymentMethodDefault() {\n this.isSettingDefaultPaymentMethods = true;\n this.VRPaymentConfigurationService.setVRPaymentAsSalesChannelPaymentDefault(\n this.$refs.configComponent.selectedSalesChannelId\n ).then(() => {\n this.isSettingDefaultPaymentMethods = false;\n this.isSetDefaultPaymentSuccessful = true;\n this.createNotificationSuccess({\n title: this.$tc('vrpayment-settings.settingForm.titleSuccess'),\n message: this.$tc('vrpayment-settings.salesChannelCard.messageDefaultPaymentUpdated')\n });\n });\n },\n\n setErrorStates() {\n const messageNotBlankErrorState = {\n code: 1,\n detail: this.$tc('vrpayment-settings.messageNotBlank')\n };\n\n if (!this.spaceIdFilled) {\n this.spaceIdErrorState = messageNotBlankErrorState;\n }\n\n if (!this.userIdFilled) {\n this.userIdErrorState = messageNotBlankErrorState;\n }\n\n if (!this.applicationKeyFilled) {\n this.applicationKeyErrorState = messageNotBlankErrorState;\n }\n },\n\n // Handles the 'check-api-connection-event'.\n // Uses the provided apiConnectionData to perform API connection checks.\n onCheckApiConnection(apiConnectionData) {\n const { spaceId, userId, applicationKey } = apiConnectionData;\n this.isTesting = true;\n\n this.VRPaymentConfigurationService.checkApiConnection(spaceId, userId, applicationKey)\n .then((res) => {\n if (res.result === 200) {\n this.createNotificationSuccess({\n title: this.$tc('vrpayment-settings.settingForm.credentials.alert.title'),\n message: this.$tc('vrpayment-settings.settingForm.credentials.alert.successMessage')\n });\n } else {\n this.createNotificationError({\n title: this.$tc('vrpayment-settings.settingForm.credentials.alert.title'),\n message: this.$tc('vrpayment-settings.settingForm.credentials.alert.errorMessage')\n });\n }\n this.isTesting = false;\n }).catch(() => {\n this.createNotificationError({\n title: this.$tc('vrpayment-settings.settingForm.credentials.alert.title'),\n message: this.$tc('vrpayment-settings.settingForm.credentials.alert.errorMessage')\n });\n this.isTesting = false;\n });\n }\n }\n});\n","{% block vrpayment_settings_content_card_channel_config_credentials %}\n\t\n\n\t\t{% block vrpayment_settings_content_card_channel_config_credentials_card_container %}\n\t\t\t\n\n\t\t\t\t{% block vrpayment_settings_content_card_channel_config_credentials_card_container_settings %}\n\t\t\t\t\t
\n\n\t\t\t\t\t\t{% block vrpayment_settings_content_card_channel_config_credentials_card_container_settings_space_id %}\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t{% endblock %}\n\n\t\t\t\t\t\t{% block vrpayment_settings_content_card_channel_config_credentials_card_container_settings_user_id %}\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t{% endblock %}\n\n\t\t\t\t\t\t{% block vrpayment_settings_content_card_channel_config_credentials_card_container_settings_application_key %}\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t{% endblock %}\n\t\t\t\t\t
\n\t\t\t\t{% endblock %}\n\n\t\t\t\t{% verbatim %}\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\t{{ $tc('vrpayment-settings.settingForm.credentials.button.label') }}\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t\t{% endverbatim %}\n\n\t\t\t
\n\t\t{% endblock %}\n\t\n\n{% endblock %}\n","/* global Shopware */\n\nimport template from './index.html.twig';\nimport constants from '../../page/vrpayment-settings/configuration-constants'\n\nconst {Component, Mixin} = Shopware;\n\nComponent.register('sw-vrpayment-credentials', {\n template: template,\n\n name: 'VRPaymentCredentials',\n\n inject: [\n 'acl'\n ],\n\n mixins: [\n Mixin.getByName('notification')\n ],\n\n props: {\n actualConfigData: {\n type: Object,\n required: true\n },\n allConfigs: {\n type: Object,\n required: true\n },\n\n selectedSalesChannelId: {\n required: true\n },\n spaceIdFilled: {\n type: Boolean,\n required: true\n },\n spaceIdErrorState: {\n required: true\n },\n userIdFilled: {\n type: Boolean,\n required: true\n },\n userIdErrorState: {\n required: true\n },\n applicationKeyFilled: {\n type: Boolean,\n required: true\n },\n applicationKeyErrorState: {\n required: true\n },\n isLoading: {\n type: Boolean,\n required: true\n },\n isTesting: {\n type: Boolean,\n required: false\n }\n },\n\n data() {\n return {\n ...constants\n };\n },\n\n methods: {\n\n checkTextFieldInheritance(value) {\n if (typeof value !== 'string') {\n return true;\n }\n\n return value.length <= 0;\n },\n\n checkNumberFieldInheritance(value) {\n if (typeof value !== 'number') {\n return true;\n }\n\n return value.length <= 0;\n },\n\n checkBoolFieldInheritance(value) {\n return typeof value !== 'boolean';\n },\n\n // Emits the 'check-api-connection-event' with the current API connection parameters.\n // Used to trigger API connection testing from this component.\n emitCheckApiConnectionEvent() {\n const apiConnectionParams = {\n spaceId: this.actualConfigData[constants.CONFIG_SPACE_ID],\n userId: this.actualConfigData[constants.CONFIG_USER_ID],\n applicationKey: this.actualConfigData[constants.CONFIG_APPLICATION_KEY]\n };\n\n this.$emit('check-api-connection-event', apiConnectionParams);\n }\n }\n});\n","{% block vrpayment_settings_content_card_channel_config_options %}\n\t\n\n\t\t{% block vrpayment_settings_content_card_channel_config_credentials_card_container %}\n\t\t\t\n\n\t\t\t\t{% block vrpayment_settings_content_card_channel_config_credentials_card_container_settings %}\n\t\t\t\t\t
\n\n\t\t\t\t\t\t{% block vrpayment_settings_content_card_channel_config_credentials_card_container_settings_space_view_id %}\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t{% endblock %}\n\n\t\t\t\t\t\t{% block vrpayment_settings_content_card_channel_config_credentials_card_container_settings_integration %}\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t{% endblock %}\n\n\t\t\t\t\t\t{% block vrpayment_settings_content_card_channel_config_credentials_card_container_settings_line_item_consistency_enabled %}\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t{% endblock %}\n\n\t\t\t\t\t\t{% block vrpayment_settings_content_card_channel_config_credentials_card_container_settings_email_enabled %}\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t{% endblock %}\n\t\t\t\t\t
\n\t\t\t\t{% endblock %}\n\t\t\t
\n\t\t{% endblock %}\n\t
\n\n{% endblock %}\n","/* global Shopware */\n\nimport template from './index.html.twig';\nimport constants from '../../page/vrpayment-settings/configuration-constants'\n\nconst {Component, Mixin} = Shopware;\n\nComponent.register('sw-vrpayment-options', {\n\ttemplate: template,\n\n\tname: 'VRPaymentOptions',\n\n\tmixins: [\n\t\tMixin.getByName('notification')\n\t],\n\n\tprops: {\n\t\tactualConfigData: {\n\t\t\ttype: Object,\n\t\t\trequired: true\n\t\t},\n\t\tallConfigs: {\n\t\t\ttype: Object,\n\t\t\trequired: true\n\t\t},\n\t\tselectedSalesChannelId: {\n\t\t\trequired: true\n\t\t},\n\t\tisLoading: {\n\t\t\ttype: Boolean,\n\t\t\trequired: true\n\t\t}\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\t...constants\n\t\t};\n\t},\n\n\tcomputed: {\n\t\tintegrationOptions() {\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\tid: 'payment_page',\n\t\t\t\t\tname: this.$tc('vrpayment-settings.settingForm.options.integration.options.payment_page')\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tid: 'iframe',\n\t\t\t\t\tname: this.$tc('vrpayment-settings.settingForm.options.integration.options.iframe')\n\t\t\t\t}\n\t\t\t];\n\t\t}\n\t},\n\n\tmethods: {\n\t\tcheckTextFieldInheritance(value) {\n\t\t\tif (typeof value !== 'string') {\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\treturn value.length <= 0;\n\t\t},\n\n\t\tcheckNumberFieldInheritance(value) {\n\t\t\tif (typeof value !== 'number') {\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\treturn value.length <= 0;\n\t\t},\n\n\t\tcheckBoolFieldInheritance(value) {\n\t\t\treturn typeof value !== 'boolean';\n\t\t}\n\t}\n});\n","{% block vrpayment_settings_icon %}\n \n \n\n\n\t\n\n\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\n\n\n \n{% endblock %}\n","import template from './index.html.twig';\n\nconst { Component } = Shopware;\n\nComponent.register('sw-vrpayment-settings-icon', {\n template\n});\n","\n\t\n\t\t
\n\t\t\t\n\t\t\t\t\n\t\t\t\n\t\t
\n\t
\n
\n\n","/* global Shopware */\n\nimport template from './index.html.twig';\nimport constants from '../../page/vrpayment-settings/configuration-constants'\n\nconst {Component, Mixin} = Shopware;\n\nComponent.register('sw-vrpayment-storefront-options', {\n\ttemplate: template,\n\n\tname: 'VRPaymentStorefrontOptions',\n\n\tmixins: [\n\t\tMixin.getByName('notification')\n\t],\n\n\tprops: {\n\t\tactualConfigData: {\n\t\t\ttype: Object,\n\t\t\trequired: true\n\t\t},\n\t\tallConfigs: {\n\t\t\ttype: Object,\n\t\t\trequired: true\n\t\t},\n\t\tselectedSalesChannelId: {\n\t\t\trequired: true\n\t\t},\n\t\tisLoading: {\n\t\t\ttype: Boolean,\n\t\t\trequired: true\n\t\t}\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\t...constants\n\t\t};\n\t},\n\n\tmethods: {\n\t\tcheckTextFieldInheritance(value) {\n\t\t\tif (typeof value !== 'string') {\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\treturn value.length <= 0;\n\t\t},\n\n\t\tcheckNumberFieldInheritance(value) {\n\t\t\tif (typeof value !== 'number') {\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\treturn value.length <= 0;\n\t\t},\n\n\t\tcheckBoolFieldInheritance(value) {\n\t\t\treturn typeof value !== 'boolean';\n\t\t}\n\t}\n});\n","\n\t\n\t\t
\n\t\t\t\n\t\t\t\t\n\t\t\t\n\n\t\t\t\n\t\t\t\t\n\t\t\t\n\t\t
\n\t
\n
\n\n","/* global Shopware */\n\nimport template from './index.html.twig';\nimport constants from '../../page/vrpayment-settings/configuration-constants'\n\nconst {Component, Mixin} = Shopware;\n\nComponent.register('sw-vrpayment-advanced-options', {\n\ttemplate: template,\n\n\tname: 'VRPaymentAdvancedOptions',\n\n\tinject: [\n\t\t'acl'\n\t],\n\n\tmixins: [\n\t\tMixin.getByName('notification')\n\t],\n\n\tprops: {\n\t\tactualConfigData: {\n\t\t\ttype: Object,\n\t\t\trequired: true\n\t\t},\n\t\tallConfigs: {\n\t\t\ttype: Object,\n\t\t\trequired: true\n\t\t},\n\t\tselectedSalesChannelId: {\n\t\t\trequired: true\n\t\t},\n\t\tisLoading: {\n\t\t\ttype: Boolean,\n\t\t\trequired: true\n\t\t}\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\t...constants\n\t\t};\n\t},\n\n\tmethods: {\n\t\tcheckTextFieldInheritance(value) {\n\t\t\tif (typeof value !== 'string') {\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\treturn value.length <= 0;\n\t\t},\n\n\t\tcheckNumberFieldInheritance(value) {\n\t\t\tif (typeof value !== 'number') {\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\treturn value.length <= 0;\n\t\t},\n\n\t\tcheckBoolFieldInheritance(value) {\n\t\t\treturn typeof value !== 'boolean';\n\t\t}\n\t}\n});\n","/* global Shopware */\n\nimport './acl';\nimport './page/vrpayment-settings';\nimport './component/sw-vrpayment-credentials';\nimport './component/sw-vrpayment-options';\nimport './component/sw-vrpayment-settings-icon';\nimport './component/sw-vrpayment-storefront-options';\nimport './component/sw-vrpayment-advanced-options';\n\nimport deDE from './snippet/de-DE.json';\nimport enGB from './snippet/en-GB.json';\nimport frFR from './snippet/fr-FR.json';\nimport itIT from './snippet/it-IT.json';\n\nconst {Module} = Shopware;\n\nModule.register('vrpayment-settings', {\n\ttype: 'plugin',\n\tname: 'VRPayment',\n\ttitle: 'vrpayment-settings.general.descriptionTextModule',\n\tdescription: 'vrpayment-settings.general.descriptionTextModule',\n\tcolor: '#28d8ff',\n\ticon: 'default-action-settings',\n\tversion: '1.0.1',\n\ttargetVersion: '1.0.1',\n\n snippets: {\n 'de-DE': deDE,\n 'en-GB': enGB,\n 'fr-FR': frFR,\n 'it-IT': itIT,\n },\n\n\troutes: {\n\t\tindex: {\n\t\t\tcomponent: 'vrpayment-settings',\n\t\t\tpath: 'index',\n\t\t\tmeta: {\n\t\t\t\tparentPath: 'sw.settings.index',\n\t\t\t\tprivilege: 'vrpayment.viewer'\n\t\t\t},\n\t\t\tprops: {\n default: (route) => {\n return {\n hash: route.params.hash,\n };\n },\n },\n\t\t}\n\t},\n\n\tsettingsItem: {\n\t\tgroup: 'plugins',\n\t\tto: 'vrpayment.settings.index',\n\t\ticonComponent: 'sw-vrpayment-settings-icon',\n\t\tbackgroundEnabled: true,\n\t\tprivilege: 'vrpayment.viewer'\n\t}\n\n});\n","/* global Shopware */\n\nconst ApiService = Shopware.Classes.ApiService;\n\n/**\n * @class VRPaymentPayment\\Core\\Api\\Config\\Controller\\ConfigurationController\n */\nclass VRPaymentConfigurationService extends ApiService {\n\n\t/**\n\t * VRPaymentConfigurationService constructor\n\t *\n\t * @param httpClient\n\t * @param loginService\n\t * @param apiEndpoint\n\t */\n\tconstructor(httpClient, loginService, apiEndpoint = 'vrpayment') {\n\t\tsuper(httpClient, loginService, apiEndpoint);\n\t}\n\n\t/**\n\t * Register web hooks\n\t *\n\t * @param {String|null} salesChannelId\n\t * @return {*}\n\t */\n\tregisterWebHooks(salesChannelId = null) {\n\n\t\tconst headers = this.getBasicHeaders();\n\t\tconst apiRoute = `${Shopware.Context.api.apiPath}/_action/${this.getApiBasePath()}/configuration/register-web-hooks`;\n\n\t\treturn this.httpClient.post(\n\t\t\tapiRoute,\n\t\t\t{\n\t\t\t\tsalesChannelId: salesChannelId\n\t\t\t},\n\t\t\t{\n\t\t\t\theaders: headers\n\t\t\t}\n\t\t).then((response) => {\n\t\t\treturn ApiService.handleResponse(response);\n\t\t});\n\t}\n\n\t/**\n\t * Test API connection\n\t *\n\t * @param {int|null} spaceId\n\t * @param {int|null} userId\n\t * @param {String|null} applicationId\n\t * @return {*}\n\t */\n\tcheckApiConnection(spaceId = null, userId = null, applicationId = null) {\n\n\t\tconst headers = this.getBasicHeaders();\n\t\tconst apiRoute = `${Shopware.Context.api.apiPath}/_action/${this.getApiBasePath()}/configuration/check-api-connection`;\n\n\t\treturn this.httpClient.post(\n\t\t\tapiRoute,\n\t\t\t{\n\t\t\t\tspaceId: spaceId,\n\t\t\t\tuserId: userId,\n\t\t\t\tapplicationId: applicationId\n\t\t\t},\n\t\t\t{\n\t\t\t\theaders: headers\n\t\t\t}\n\t\t).then((response) => {\n\t\t\treturn ApiService.handleResponse(response);\n\t\t});\n\t}\n\n\t/**\n\t * Set's the default payment method to VRPayment for the given salesChannel id.\n\t *\n\t * @param {String|null} salesChannelId\n\t *\n\t * @returns {Promise}\n\t */\n\tsetVRPaymentAsSalesChannelPaymentDefault(salesChannelId = null) {\n\n\t\tconst headers = this.getBasicHeaders();\n\t\tconst apiRoute = `${Shopware.Context.api.apiPath}/_action/${this.getApiBasePath()}/configuration/set-vrpayment-as-sales-channel-payment-default`;\n\n\t\treturn this.httpClient.post(\n\t\t\tapiRoute,\n\t\t\t{\n\t\t\t\tsalesChannelId: salesChannelId\n\t\t\t},\n\t\t\t{\n\t\t\t\theaders: headers\n\t\t\t}\n\t\t).then((response) => {\n\t\t\treturn ApiService.handleResponse(response);\n\t\t});\n\t}\n\n\t/**\n\t *\n\t * @param salesChannelId\n\t * @return {Promise}\n\t */\n\tsynchronizePaymentMethodConfiguration(salesChannelId = null) {\n\t\tconst headers = this.getBasicHeaders();\n\t\tconst apiRoute = `${Shopware.Context.api.apiPath}/_action/${this.getApiBasePath()}/configuration/synchronize-payment-method-configuration`;\n\n\t\treturn this.httpClient.post(\n\t\t\tapiRoute,\n\t\t\t{\n\t\t\t\tsalesChannelId: salesChannelId\n\t\t\t},\n\t\t\t{\n\t\t\t\theaders: headers\n\t\t\t}\n\t\t).then((response) => {\n\t\t\treturn ApiService.handleResponse(response);\n\t\t});\n\t}\n\n\t/**\n\t *\n\t * @return {*}\n\t */\n\tinstallOrderDeliveryStates() {\n\t\tconst headers = this.getBasicHeaders();\n\t\tconst apiRoute = `${Shopware.Context.api.apiPath}/_action/${this.getApiBasePath()}/configuration/install-order-delivery-states`;\n\n\t\treturn this.httpClient.post(\n\t\t\tapiRoute,\n\t\t\t{\n\t\t\t},\n\t\t\t{\n\t\t\t\theaders: headers\n\t\t\t}\n\t\t).then((response) => {\n\t\t\treturn ApiService.handleResponse(response);\n\t\t});\n\t}\n}\n\nexport default VRPaymentConfigurationService;\n","/* global Shopware */\n\nconst ApiService = Shopware.Classes.ApiService;\n\n/**\n * @class VRPaymentPayment\\Core\\Api\\Transaction\\Controller\\RefundController\n */\nclass VRPaymentRefundService extends ApiService {\n\n\t/**\n\t * VRPaymentRefundService constructor\n\t *\n\t * @param httpClient\n\t * @param loginService\n\t * @param apiEndpoint\n\t */\n\tconstructor(httpClient, loginService, apiEndpoint = 'vrpayment') {\n\t\tsuper(httpClient, loginService, apiEndpoint);\n\t}\n\n\t/**\n\t * Refund a transaction\n\t *\n\t * @param {String} salesChannelId\n\t * @param {int} transactionId\n\t * @param {int} quantity\n\t * @param {int} lineItemId\n\t * @return {*}\n\t */\n\tcreateRefund(salesChannelId, transactionId, quantity, lineItemId) {\n\n\t\tconst headers = this.getBasicHeaders();\n\t\tconst apiRoute = `${Shopware.Context.api.apiPath}/_action/${this.getApiBasePath()}/refund/create-refund/`;\n\n\t\treturn this.httpClient.post(\n\t\t\tapiRoute,\n\t\t\t{\n\t\t\t\tsalesChannelId: salesChannelId,\n\t\t\t\ttransactionId: transactionId,\n\t\t\t\tquantity: quantity,\n\t\t\t\tlineItemId: lineItemId\n\t\t\t},\n\t\t\t{\n\t\t\t\theaders: headers\n\t\t\t}\n\t\t).then((response) => {\n\t\t\treturn ApiService.handleResponse(response);\n\t\t});\n\t}\n\n\t/**\n\t * Refund a transaction\n\t *\n\t * @param {String} salesChannelId\n\t * @param {int} transactionId\n\t * @param {float} refundableAmount\n\t * @return {*}\n\t */\n\tcreateRefundByAmount(salesChannelId, transactionId, refundableAmount) {\n\n\t\tconst headers = this.getBasicHeaders();\n\t\tconst apiRoute = `${Shopware.Context.api.apiPath}/_action/${this.getApiBasePath()}/refund/create-refund-by-amount/`;\n\n\t\treturn this.httpClient.post(\n\t\t\tapiRoute,\n\t\t\t{\n\t\t\t\tsalesChannelId: salesChannelId,\n\t\t\t\ttransactionId: transactionId,\n\t\t\t\trefundableAmount: refundableAmount\n\t\t\t},\n\t\t\t{\n\t\t\t\theaders: headers\n\t\t\t}\n\t\t).then((response) => {\n\t\t\treturn ApiService.handleResponse(response);\n\t\t});\n\t}\n\n\t/**\n\t * Refund a transaction\n\t *\n\t * @param {String} salesChannelId\n\t * @param {int} transactionId\n\t * @param {float} refundableAmount\n\t * @param {String} lineItemId\n\t * @return {*}\n\t */\n\tcreatePartialRefund(salesChannelId, transactionId, refundableAmount, lineItemId) {\n\n\t\tconst headers = this.getBasicHeaders();\n\t\tconst apiRoute = `${Shopware.Context.api.apiPath}/_action/${this.getApiBasePath()}/refund/create-partial-refund/`;\n\n\t\treturn this.httpClient.post(\n\t\t\tapiRoute,\n\t\t\t{\n\t\t\t\tsalesChannelId: salesChannelId,\n\t\t\t\ttransactionId: transactionId,\n\t\t\t\trefundableAmount: refundableAmount,\n\t\t\t\tlineItemId: lineItemId\n\t\t\t},\n\t\t\t{\n\t\t\t\theaders: headers\n\t\t\t}\n\t\t).then((response) => {\n\t\t\treturn ApiService.handleResponse(response);\n\t\t});\n\t}\n}\n\nexport default VRPaymentRefundService;\n","/* global Shopware */\n\nconst ApiService = Shopware.Classes.ApiService;\n\n/**\n * @class VRPaymentPayment\\Core\\Api\\Transaction\\Controller\\TransactionController\n */\nclass VRPaymentTransactionService extends ApiService {\n\n\t/**\n\t * VRPaymentTransactionService constructor\n\t *\n\t * @param httpClient\n\t * @param loginService\n\t * @param apiEndpoint\n\t */\n\tconstructor(httpClient, loginService, apiEndpoint = 'vrpayment') {\n\t\tsuper(httpClient, loginService, apiEndpoint);\n\t}\n\n\t/**\n\t * Get transaction data\n\t *\n\t * @param {String} salesChannelId\n\t * @param {int} transactionId\n\t * @return {*}\n\t */\n\tgetTransactionData(salesChannelId, transactionId) {\n\n\t\tconst headers = this.getBasicHeaders();\n\t\tconst apiRoute = `${Shopware.Context.api.apiPath}/_action/${this.getApiBasePath()}/transaction/get-transaction-data/`;\n\n\t\treturn this.httpClient.post(\n\t\t\tapiRoute,\n\t\t\t{\n\t\t\t\tsalesChannelId: salesChannelId,\n\t\t\t\ttransactionId: transactionId\n\t\t\t},\n\t\t\t{\n\t\t\t\theaders: headers\n\t\t\t}\n\t\t).then((response) => {\n\t\t\treturn ApiService.handleResponse(response);\n\t\t});\n\t}\n\n\t/**\n\t * Download Invoice Document\n\t *\n\t * @param context\n\t * @param salesChannelId\n\t * @param transactionId\n\t * @return {string}\n\t */\n\tgetInvoiceDocument(salesChannelId, transactionId) {\n\t\treturn `${Shopware.Context.api.apiPath}/_action/${this.getApiBasePath()}/transaction/get-invoice-document/${salesChannelId}/${transactionId}`;\n\t}\n\n\t/**\n\t * Download Packing slip\n\t *\n\t * @param salesChannelId\n\t * @param transactionId\n\t * @return {string}\n\t */\n\tgetPackingSlip(salesChannelId, transactionId) {\n\t\treturn `${Shopware.Context.api.apiPath}/_action/${this.getApiBasePath()}/transaction/get-packing-slip/${salesChannelId}/${transactionId}`;\n\t}\n}\n\nexport default VRPaymentTransactionService;","/* global Shopware */\n\nconst ApiService = Shopware.Classes.ApiService;\n\n/**\n * @class VRPaymentPayment\\Core\\Api\\Transaction\\Controller\\TransactionCompletionController\n */\nclass VRPaymentTransactionCompletionService extends ApiService {\n\n\t/**\n\t * VRPaymentTransactionCompletionService constructor\n\t *\n\t * @param httpClient\n\t * @param loginService\n\t * @param apiEndpoint\n\t */\n\tconstructor(httpClient, loginService, apiEndpoint = 'vrpayment') {\n\t\tsuper(httpClient, loginService, apiEndpoint);\n\t}\n\n\t/**\n\t * Complete a transaction\n\t *\n\t * @param {String} salesChannelId\n\t * @param {int} transactionId\n\t * @return {*}\n\t */\n\tcreateTransactionCompletion(salesChannelId, transactionId) {\n\n\t\tconst headers = this.getBasicHeaders();\n\t\tconst apiRoute = `${Shopware.Context.api.apiPath}/_action/${this.getApiBasePath()}/transaction-completion/create-transaction-completion/`;\n\n\t\treturn this.httpClient.post(\n\t\t\tapiRoute,\n\t\t\t{\n\t\t\t\tsalesChannelId: salesChannelId,\n\t\t\t\ttransactionId: transactionId\n\t\t\t},\n\t\t\t{\n\t\t\t\theaders: headers\n\t\t\t}\n\t\t).then((response) => {\n\t\t\treturn ApiService.handleResponse(response);\n\t\t});\n\t}\n}\n\nexport default VRPaymentTransactionCompletionService;","/* global Shopware */\n\nconst ApiService = Shopware.Classes.ApiService;\n\n/**\n * @class VRPaymentPayment\\Core\\Api\\Transaction\\Controller\\TransactionVoidController\n */\nclass VRPaymentTransactionVoidService extends ApiService {\n\n\t/**\n\t * VRPaymentTransactionVoidService constructor\n\t *\n\t * @param httpClient\n\t * @param loginService\n\t * @param apiEndpoint\n\t */\n\tconstructor(httpClient, loginService, apiEndpoint = 'vrpayment') {\n\t\tsuper(httpClient, loginService, apiEndpoint);\n\t}\n\n\t/**\n\t * Void a transaction\n\t *\n\t * @param {String} salesChannelId\n\t * @param {int} transactionId\n\t * @return {*}\n\t */\n\tcreateTransactionVoid(salesChannelId, transactionId) {\n\n\t\tconst headers = this.getBasicHeaders();\n\t\tconst apiRoute = `${Shopware.Context.api.apiPath}/_action/${this.getApiBasePath()}/transaction-void/create-transaction-void/`;\n\n\t\treturn this.httpClient.post(\n\t\t\tapiRoute,\n\t\t\t{\n\t\t\t\tsalesChannelId: salesChannelId,\n\t\t\t\ttransactionId: transactionId\n\t\t\t},\n\t\t\t{\n\t\t\t\theaders: headers\n\t\t\t}\n\t\t).then((response) => {\n\t\t\treturn ApiService.handleResponse(response);\n\t\t});\n\t}\n}\n\nexport default VRPaymentTransactionVoidService;","/* global Shopware */\n\nimport VRPaymentConfigurationService from '../core/service/api/vrpayment-configuration.service';\nimport VRPaymentRefundService from '../core/service/api/vrpayment-refund.service';\nimport VRPaymentTransactionService from '../core/service/api/vrpayment-transaction.service';\nimport VRPaymentTransactionCompletionService\n\tfrom '../core/service/api/vrpayment-transaction-completion.service';\nimport VRPaymentTransactionVoidService\n\tfrom '../core/service/api/vrpayment-transaction-void.service';\n\n\nconst {Application} = Shopware;\n\n// noinspection JSUnresolvedFunction\nApplication.addServiceProvider('VRPaymentConfigurationService', (container) => {\n\tconst initContainer = Application.getContainer('init');\n\treturn new VRPaymentConfigurationService(initContainer.httpClient, container.loginService);\n});\n\n// noinspection JSUnresolvedFunction\nApplication.addServiceProvider('VRPaymentRefundService', (container) => {\n\tconst initContainer = Application.getContainer('init');\n\treturn new VRPaymentRefundService(initContainer.httpClient, container.loginService);\n});\n\n// noinspection JSUnresolvedFunction\nApplication.addServiceProvider('VRPaymentTransactionService', (container) => {\n\tconst initContainer = Application.getContainer('init');\n\treturn new VRPaymentTransactionService(initContainer.httpClient, container.loginService);\n});\n\n// noinspection JSUnresolvedFunction\nApplication.addServiceProvider('VRPaymentTransactionCompletionService', (container) => {\n\tconst initContainer = Application.getContainer('init');\n\treturn new VRPaymentTransactionCompletionService(initContainer.httpClient, container.loginService);\n});\n\n// noinspection JSUnresolvedFunction\nApplication.addServiceProvider('VRPaymentTransactionVoidService', (container) => {\n\tconst initContainer = Application.getContainer('init');\n\treturn new VRPaymentTransactionVoidService(initContainer.httpClient, container.loginService);\n});"],"names":["template$c","Component","Context","Criteria","vrpaymentFormattedHandlerIdentifier","template","orderRepository","orderCriteria","order","paymentMethodId","paymentMethod","template$b","Mixin","Filter","Utils","errorResponse","template$a","template$9","itemUniqueId","newValue","template$8","errorTitle","errorMessage","template$7","template$6","totalAmountTemp","refundsAmountTemp","vrpaymentTransactionId","VRPaymentTransaction","refund","reduction","lineItem","modalType","lineItemId","refundableQuantity","itemRefundableAmount","item","selection","refundPromises","error","resolve","reject","Module","deDE","enGB","frFR","itIT","next","currentRoute","template$5","CONFIG_DOMAIN","CONFIG_APPLICATION_KEY","CONFIG_EMAIL_ENABLED","CONFIG_INTEGRATION","CONFIG_LINE_ITEM_CONSISTENCY_ENABLED","CONFIG_SPACE_ID","CONFIG_SPACE_VIEW_ID","CONFIG_STOREFRONT_INVOICE_DOWNLOAD_ENABLED","CONFIG_USER_ID","CONFIG_STOREFRONT_WEBHOOKS_UPDATE_ENABLED","CONFIG_STOREFRONT_PAYMENTS_UPDATE_ENABLED","constants","configData","defaultConfig","value","key","res","messageNotBlankErrorState","apiConnectionData","spaceId","userId","applicationKey","template$4","apiConnectionParams","template$3","template$2","template$1","route","ApiService","VRPaymentConfigurationService","httpClient","loginService","apiEndpoint","salesChannelId","headers","apiRoute","response","applicationId","VRPaymentRefundService","transactionId","quantity","refundableAmount","VRPaymentTransactionService","VRPaymentTransactionCompletionService","VRPaymentTransactionVoidService","Application","container","initContainer"],"mappings":"AAAA,MAAeA,EAAA,4hBCKT,WAACC,EAAS,QAAEC,CAAO,EAAI,SACvBC,EAAW,SAAS,KAAK,SAEzBC,EAAsC,mDAE5CH,EAAU,SAAS,kBAAmB,CACtC,SAACI,EAEA,MAAO,CACN,MAAO,CACN,mBAAoB,EACpB,CACD,EAED,SAAU,CACT,YAAa,CACZ,MAAO,CAAC,KAAK,oBAAsB,KAAK,OAAO,OAAS,wBACxD,EACD,UAAW,CACV,MAAO,EACV,CACE,EAED,MAAO,CACN,QAAS,CACR,KAAM,GACN,SAAU,CACT,GAAI,CAAC,KAAK,QAAS,CAClB,KAAK,sBAAsB,IAAI,EAC/B,MACL,CAEI,MAAMC,EAAkB,KAAK,kBAAkB,OAAO,OAAO,EACvDC,EAAgB,IAAIJ,EAAS,EAAG,CAAC,EACvCI,EAAc,eAAe,cAAc,EAE3CD,EAAgB,IAAI,KAAK,QAASJ,EAAQ,IAAKK,CAAa,EAAE,KAAMC,GAAU,CAC7E,GACEA,EAAM,aAAe,GACrBA,EAAM,aAAa,QAAU,GAC9B,CAACA,EAAM,aAAa,CAAC,EAAE,gBACtB,CACD,KAAK,sBAAsB,IAAI,EAC/B,MACN,CAEK,MAAMC,EAAkBD,EAAM,aAAa,CAAC,EAAE,gBACTC,GAAoB,MACxD,KAAK,sBAAsBA,CAAe,CAEhD,CAAK,CACD,EACD,UAAW,EACd,CACE,EAED,QAAS,CACR,sBAAsBA,EAAiB,CACtC,GAAI,CAACA,EACJ,OAE+B,KAAK,kBAAkB,OAAO,gBAAgB,EACtD,IAAIA,EAAiBP,EAAQ,GAAG,EAAE,KACxDQ,GAAkB,CAClB,KAAK,mBAAsBA,EAAc,6BAA+BN,CAC7E,CACI,CACJ,CACA,CACA,CAAC,EC1ED,MAAeO,EAAA,gpBCIT,CAAA,UAACV,EAAS,MAAEW,EAAK,OAAEC,EAAQC,MAAAA,CAAK,EAAI,SAE1Cb,EAAU,SAAS,oCAAqC,CAEvD,SAAUI,EAEV,OAAQ,CAAC,uCAAuC,EAEhD,OAAQ,CACPO,EAAM,UAAU,cAAc,CAC9B,EAED,MAAO,CACN,gBAAiB,CAChB,KAAM,OACN,SAAU,EACb,CACE,EAED,MAAO,CACN,MAAO,CACN,UAAW,GACX,aAAc,EACd,CACD,EAED,SAAU,CACT,YAAa,CACZ,OAAOC,EAAO,UAAU,MAAM,CACjC,CACE,EAED,SAAU,CACT,KAAK,iBAAkB,CACvB,EAED,QAAS,CACR,kBAAmB,CAClB,KAAK,UAAY,EACjB,EAED,YAAa,CACR,KAAK,eACR,KAAK,UAAY,GACjB,KAAK,sCAAsC,4BAC1C,KAAK,gBAAgB,aAAa,CAAC,EAAE,SAAS,eAC9C,KAAK,gBAAgB,aAAa,CAAC,EAAE,EACrC,EAAC,KAAK,IAAM,CACZ,KAAK,0BAA0B,CAC9B,MAAO,KAAK,IAAI,4CAA4C,EAC5D,QAAS,KAAK,IAAI,8CAA8C,CACtE,CAAM,EACD,KAAK,UAAY,GACjB,KAAK,MAAM,aAAa,EACxB,KAAK,UAAU,IAAM,CACpB,KAAK,QAAQ,QAAQ,GAAG,KAAK,OAAO,IAAI,SAASC,EAAM,SAAQ,CAAE,EAAE,CACzE,CAAM,CACN,CAAK,EAAE,MAAOC,GAAkB,CAC3B,GAAI,CACH,KAAK,wBAAwB,CAC5B,MAAOA,EAAc,SAAS,KAAK,OAAO,CAAC,EAAE,MAC7C,QAASA,EAAc,SAAS,KAAK,OAAO,CAAC,EAAE,OAC/C,UAAW,EAClB,CAAO,CACD,MAAW,CACX,KAAK,wBAAwB,CAC5B,MAAOA,EAAc,MACrB,QAASA,EAAc,QACvB,UAAW,EAClB,CAAO,CACP,QAAe,CACT,KAAK,UAAY,GACjB,KAAK,MAAM,aAAa,EACxB,KAAK,UAAU,IAAM,CACpB,KAAK,QAAQ,QAAQ,GAAG,KAAK,OAAO,IAAI,SAASD,EAAM,SAAQ,CAAE,EAAE,CAC1E,CAAO,CACP,CACA,CAAK,EAEL,CACA,CACA,CAAC,ECrFD,MAAeE,EAAA,40BCIT,CAAA,UAACf,EAAS,MAAEW,EAAK,OAAEC,EAAQC,MAAAA,CAAK,EAAI,SAE1Cb,EAAU,SAAS,gCAAiC,CACpD,SAACI,EAEA,OAAQ,CAAC,wBAAwB,EAEjC,OAAQ,CACPO,EAAM,UAAU,cAAc,CAC9B,EAED,MAAO,CACN,gBAAiB,CAChB,KAAM,OACN,SAAU,EACV,EAED,QAAS,CACR,KAAM,OACN,SAAU,EACb,CACE,EAED,MAAO,CACN,MAAO,CACN,eAAgB,EAChB,UAAW,GACX,gBAAiB,EACjB,CACD,EAED,SAAU,CACT,YAAa,CACZ,OAAOC,EAAO,UAAU,MAAM,CACjC,CACE,EAED,SAAU,CACT,KAAK,iBAAkB,CACvB,EAED,QAAS,CACR,kBAAmB,CAClB,KAAK,UAAY,GACjB,KAAK,eAAiB,CACtB,EAED,QAAS,CACR,KAAK,UAAY,GACjB,KAAK,uBAAuB,aAC3B,KAAK,gBAAgB,aAAa,CAAC,EAAE,SAAS,eAC9C,KAAK,gBAAgB,aAAa,CAAC,EAAE,GACrC,KAAK,eACL,KAAK,QAAQ,QAAQ,eACrB,EAAC,KAAK,IAAM,CACZ,KAAK,0BAA0B,CAC9B,MAAO,KAAK,IAAI,2CAA2C,EAC3D,QAAS,KAAK,IAAI,6CAA6C,CACpE,CAAK,EACD,KAAK,UAAY,GACjB,KAAK,MAAM,aAAa,EACxB,KAAK,UAAU,IAAM,CACpB,KAAK,QAAQ,QAAQ,GAAG,KAAK,OAAO,IAAI,SAASC,EAAM,SAAQ,CAAE,EAAE,CACxE,CAAK,CACL,CAAI,EAAE,MAAOC,GAAkB,CAC3B,GAAI,CACH,KAAK,wBAAwB,CAC5B,MAAOA,EAAc,SAAS,KAAK,OAAO,CAAC,EAAE,MAC7C,QAASA,EAAc,SAAS,KAAK,OAAO,CAAC,EAAE,OAC/C,UAAW,EACjB,CAAM,CACD,MAAW,CACX,KAAK,wBAAwB,CAC5B,MAAOA,EAAc,MACrB,QAASA,EAAc,QACvB,UAAW,EACjB,CAAM,CACN,QAAc,CACT,KAAK,UAAY,GACjB,KAAK,MAAM,aAAa,EACxB,KAAK,UAAU,IAAM,CACpB,KAAK,QAAQ,QAAQ,GAAG,KAAK,OAAO,IAAI,SAASD,EAAM,SAAQ,CAAE,EAAE,CACzE,CAAM,CACN,CACA,CAAI,CACJ,CACA,CACA,CAAC,EC3FD,MAAeG,EAAA,o6BCIT,CAAA,UAAChB,EAAS,MAAEW,EAAK,OAAEC,EAAQC,MAAAA,CAAK,EAAI,SAE1Cb,EAAU,SAAS,wCAAyC,CAC5D,SAACI,EAEA,OAAQ,CAAC,wBAAwB,EAEjC,OAAQ,CACPO,EAAM,UAAU,cAAc,CAC9B,EAED,MAAO,CACN,gBAAiB,CAChB,KAAM,OACN,SAAU,EACV,EAED,QAAS,CACR,KAAM,OACN,SAAU,EACb,CACE,EAED,MAAO,CACN,MAAO,CACN,UAAW,GACX,SAAU,KAAK,gBAAgB,aAAa,CAAC,EAAE,SAC/C,aAAc,CACd,CACD,EAED,SAAU,CACT,YAAa,CACZ,OAAOC,EAAO,UAAU,MAAM,CACjC,CACE,EAED,SAAU,CACT,KAAK,iBAAkB,CACvB,EAED,QAAS,CACF,kBAAmB,CACf,KAAK,UAAY,GACjB,KAAK,SAAW,KAAK,gBAAgB,aAAa,CAAC,EAAE,SAChD,KAAK,eACN,KAAK,aAAe,KAAK,QAAQ,QAAQ,qBAEhD,EAEP,oBAAoBK,EAAc,CACjC,KAAK,UAAY,GACjB,KAAK,uBAAuB,oBAC3B,KAAK,gBAAgB,aAAa,CAAC,EAAE,SAAS,eAC9C,KAAK,gBAAgB,aAAa,CAAC,EAAE,GACrC,KAAK,aACLA,CACA,EAAC,KAAK,IAAM,CACZ,KAAK,0BAA0B,CAC9B,MAAO,KAAK,IAAI,2CAA2C,EAC3D,QAAS,KAAK,IAAI,6CAA6C,CACpE,CAAK,EACD,KAAK,UAAY,GACjB,KAAK,MAAM,aAAa,EACxB,KAAK,UAAU,IAAM,CACpB,KAAK,QAAQ,QAAQ,GAAG,KAAK,OAAO,IAAI,SAASJ,EAAM,SAAQ,CAAE,EAAE,CACxE,CAAK,CACL,CAAI,EAAE,MAAOC,GAAkB,CAC3B,GAAI,CACH,KAAK,wBAAwB,CAC5B,MAAOA,EAAc,SAAS,KAAK,OAAO,CAAC,EAAE,MAC7C,QAASA,EAAc,SAAS,KAAK,OAAO,CAAC,EAAE,OAC/C,UAAW,EACjB,CAAM,CACD,MAAW,CACX,KAAK,wBAAwB,CAC5B,MAAOA,EAAc,MACrB,QAASA,EAAc,QACvB,UAAW,EACjB,CAAM,CACN,QAAc,CACT,KAAK,UAAY,GACjB,KAAK,MAAM,aAAa,EACxB,KAAK,UAAU,IAAM,CACpB,KAAK,QAAQ,QAAQ,GAAG,KAAK,OAAO,IAAI,SAASD,EAAM,SAAQ,CAAE,EAAE,CACzE,CAAM,CACN,CACA,CAAI,CACJ,CACE,EAEE,MAAO,CACH,aAAaK,EAAU,CACfA,IAAa,OACb,KAAK,aAAe,KAAK,MAAMA,EAAW,GAAG,EAAI,IAEjE,CACA,CACA,CAAC,ECtGD,MAAeC,EAAA,2tBCIT,CAAA,UAACnB,EAAS,MAAEW,EAAK,OAAEC,EAAQC,MAAAA,CAAK,EAAI,SAE1Cb,EAAU,SAAS,0CAA2C,CAC9D,SAACI,EAEA,OAAQ,CAAC,wBAAwB,EAEjC,OAAQ,CACPO,EAAM,UAAU,cAAc,CAC9B,EAED,MAAO,CACN,gBAAiB,CAChB,KAAM,OACN,SAAU,EACV,EAED,QAAS,CACR,KAAM,OACN,SAAU,EACb,CACE,EAED,MAAO,CACN,MAAO,CACN,UAAW,GACX,SAAU,KAAK,gBAAgB,aAAa,CAAC,EAAE,SAC/C,aAAc,EACd,iBAAkB,CAClB,CACD,EAED,SAAU,CACT,YAAa,CACZ,OAAOC,EAAO,UAAU,MAAM,CACjC,CACE,EAED,SAAU,CACT,KAAK,iBAAkB,CACvB,EAED,QAAS,CACR,kBAAmB,CAClB,KAAK,UAAY,GACjB,KAAK,SAAW,KAAK,gBAAgB,aAAa,CAAC,EAAE,SACrD,KAAK,aAAe,OAAO,KAAK,gBAAgB,aAAa,CAAC,EAAE,kBAAkB,EAClF,KAAK,iBAAmB,OAAO,KAAK,gBAAgB,aAAa,CAAC,EAAE,kBAAkB,CACtF,EAED,gBAAiB,CAChB,KAAK,UAAY,GACjB,KAAK,uBAAuB,qBAC3B,KAAK,gBAAgB,aAAa,CAAC,EAAE,SAAS,eAC9C,KAAK,gBAAgB,aAAa,CAAC,EAAE,GACrC,KAAK,YACL,EAAC,KAAK,IAAM,CACZ,KAAK,0BAA0B,CAC9B,MAAO,KAAK,IAAI,2CAA2C,EAC3D,QAAS,KAAK,IAAI,6CAA6C,CACpE,CAAK,EACD,KAAK,UAAY,GACjB,KAAK,MAAM,aAAa,EACxB,KAAK,UAAU,IAAM,CACpB,KAAK,QAAQ,QAAQ,GAAG,KAAK,OAAO,IAAI,SAASC,EAAM,SAAQ,CAAE,EAAE,CACxE,CAAK,CACL,CAAI,EAAE,MAAOC,GAAkB,CAC3B,GAAI,CACH,IAAIM,EACAC,EACAP,EAAc,SAAS,MAAQ,uBAClCM,EAAa,KAAK,IAAI,4DAA4D,EAClFC,EAAe,KAAK,IAAI,iGAAiG,IAEzHD,EAAaN,EAAc,SAAS,KAAK,OAAO,CAAC,EAAE,MACnDO,EAAeP,EAAc,SAAS,KAAK,OAAO,CAAC,EAAE,QAEtD,KAAK,wBAAwB,CAC5B,MAAOM,EACP,QAASC,EACT,UAAW,EACjB,CAAM,CACD,MAAW,CACX,KAAK,wBAAwB,CAC5B,MAAOP,EAAc,MACrB,QAASA,EAAc,QACvB,UAAW,EACjB,CAAM,CACN,QAAc,CACT,KAAK,UAAY,GACjB,KAAK,MAAM,aAAa,EACxB,KAAK,UAAU,IAAM,CACpB,KAAK,QAAQ,QAAQ,GAAG,KAAK,OAAO,IAAI,SAASD,EAAM,SAAQ,CAAE,EAAE,CACzE,CAAM,CACN,CACA,CAAI,CACJ,CACA,CACA,CAAC,ECtGD,MAAeS,EAAA,gsBCIT,CAAA,UAACtB,EAAS,MAAEW,EAAK,OAAEC,EAAQC,MAAAA,CAAK,EAAI,SAE1Cb,EAAU,SAAS,8BAA+B,CAClD,SAACI,EAEA,OAAQ,CAAC,iCAAiC,EAE1C,OAAQ,CACPO,EAAM,UAAU,cAAc,CAC9B,EAED,MAAO,CACN,gBAAiB,CAChB,KAAM,OACN,SAAU,EACb,CACE,EAED,MAAO,CACN,MAAO,CACN,UAAW,GACX,OAAQ,EACR,CACD,EAED,SAAU,CACT,YAAa,CACZ,OAAOC,EAAO,UAAU,MAAM,CAC9B,EACD,iBAAkB,CACjB,MAAO,CACN,CACC,SAAU,WACV,MAAO,KAAK,IAAI,uCAAuC,EACvD,QAAS,GACT,YAAa,GACb,QAAS,GACT,MAAO,MACP,EACD,CACC,SAAU,OACV,MAAO,KAAK,IAAI,mCAAmC,EACnD,QAAS,GACT,YAAa,GACb,SAAU,GACV,MAAO,MACP,EACD,CACC,SAAU,WACV,MAAO,KAAK,IAAI,uCAAuC,EACvD,QAAS,GACT,YAAa,GACb,MAAO,MACP,EACD,CACC,SAAU,qBACV,MAAO,KAAK,IAAI,iDAAiD,EACjE,QAAS,GACT,YAAa,GACb,WAAY,SACZ,MAAO,MACP,EACD,CACC,SAAU,OACV,MAAO,KAAK,IAAI,mCAAmC,EACnD,QAAS,GACT,YAAa,GACb,SAAU,GACV,MAAO,MACP,EACD,CACC,SAAU,YACV,MAAO,KAAK,IAAI,wCAAwC,EACxD,QAAS,GACT,YAAa,GACb,MAAO,MACZ,CACI,CACJ,CACE,EAED,SAAU,CACT,KAAK,iBAAkB,CACvB,EAED,QAAS,CACR,kBAAmB,CAClB,KAAK,UAAY,GACjB,KAAK,SAAW,KAAK,gBAAgB,aAAa,CAAC,EAAE,SACrD,KAAK,iBAAmB,KAAK,gBAAgB,aAAa,CAAC,EAAE,mBAC7D,KAAK,aAAe,KAAK,gBAAgB,aAAa,CAAC,EAAE,kBACzD,EAED,aAAc,CACT,KAAK,SACR,KAAK,UAAY,GACjB,KAAK,gCAAgC,sBACpC,KAAK,gBAAgB,aAAa,CAAC,EAAE,SAAS,eAC9C,KAAK,gBAAgB,aAAa,CAAC,EAAE,EACrC,EAAC,KAAK,IAAM,CACZ,KAAK,0BAA0B,CAC9B,MAAO,KAAK,IAAI,yCAAyC,EACzD,QAAS,KAAK,IAAI,2CAA2C,CACnE,CAAM,EACD,KAAK,UAAY,GACjB,KAAK,MAAM,aAAa,EACxB,KAAK,UAAU,IAAM,CACpB,KAAK,QAAQ,QAAQ,GAAG,KAAK,OAAO,IAAI,SAASC,EAAM,SAAQ,CAAE,EAAE,CACzE,CAAM,CACN,CAAK,EAAE,MAAOC,GAAkB,CAC3B,GAAI,CACH,KAAK,wBAAwB,CAC5B,MAAOA,EAAc,SAAS,KAAK,OAAO,CAAC,EAAE,MAC7C,QAASA,EAAc,SAAS,KAAK,OAAO,CAAC,EAAE,OAC/C,UAAW,EAClB,CAAO,CACD,MAAW,CACX,KAAK,wBAAwB,CAC5B,MAAOA,EAAc,MACrB,QAASA,EAAc,QACvB,UAAW,EAClB,CAAO,CACP,QAAe,CACT,KAAK,UAAY,GACjB,KAAK,MAAM,aAAa,EACxB,KAAK,UAAU,IAAM,CACpB,KAAK,QAAQ,QAAQ,GAAG,KAAK,OAAO,IAAI,SAASD,EAAM,SAAQ,CAAE,EAAE,CAC1E,CAAO,CACP,CACA,CAAK,EAEL,CACA,CACA,CAAC,ECzID,MAAeU,EAAA,2jOCUT,CAACvB,UAAAA,EAAWW,MAAAA,EAAO,OAAAC,EAAQ,QAAAX,GAAS,MAAAY,CAAK,EAAI,SAC7CX,EAAW,SAAS,KAAK,SAE/BF,EAAU,SAAS,yBAA0B,CAC7C,SAACI,EAEA,OAAQ,CACP,8BACA,yBACA,mBACA,EAED,OAAQ,CACPO,EAAM,UAAU,cAAc,CAC9B,EAED,MAAO,CACN,MAAO,CACN,gBAAiB,CAChB,aAAc,CAAE,EAChB,QAAS,CAAA,CACT,EACD,YAAa,CAAE,EACf,UAAW,CAAE,EACb,mBAAoB,EACpB,uBAAwB,EACxB,UAAW,GACX,QAAS,GACT,SAAU,GACV,UAAW,GACX,aAAc,EACd,iBAAkB,EAClB,mBAAoB,EACpB,qBAAsB,EACtB,qBAAsB,EACtB,gBAAiB,GACjB,uBAAwB,CAAE,EAC1B,qBAAsB,CAAE,EACxB,cAAe,CAAA,CACf,CACD,EAED,UAAW,CACV,MAAO,CACN,MAAO,KAAK,IAAI,wBAAwB,CACxC,CACD,EAGD,SAAU,CACT,YAAa,CACZ,OAAOC,EAAO,UAAU,MAAM,CAC9B,EAED,wBAAyB,CACxB,MAAO,CACN,CACC,SAAU,oBACV,MAAO,KAAK,IAAI,yDAAyD,EACzE,QAAS,EACT,EACD,CACC,SAAU,QACV,MAAO,KAAK,IAAI,gDAAgD,EAChE,QAAS,EACT,EACD,CACC,SAAU,WACV,MAAO,KAAK,IAAI,mDAAmD,EACnE,QAAS,EACT,EACD,CACC,SAAU,oBACV,MAAO,KAAK,IAAI,4DAA4D,EAC5E,QAAS,EACT,EACD,CACC,SAAU,KACV,MAAO,KAAK,IAAI,sDAAsD,EACtE,QAAS,EACT,EACD,CACC,SAAU,aACV,MAAO,KAAK,IAAI,mDAAmD,EACnE,QAAS,EACd,CACI,CACD,EAED,iBAAkB,CACjB,MAAO,CAEN,CACC,SAAU,KACV,QAAS,GACT,QAAS,GACT,QAAS,EACT,EACD,CACC,SAAU,WACV,MAAO,KAAK,IAAI,yCAAyC,EACzD,QAAS,GACT,QAAS,GACT,QAAS,EACT,EACD,CACC,SAAU,OACV,MAAO,KAAK,IAAI,qCAAqC,EACrD,QAAS,EACT,EACD,CACC,SAAU,WACV,MAAO,KAAK,IAAI,yCAAyC,EACzD,QAAS,EACT,EACD,CACC,SAAU,qBACV,MAAO,KAAK,IAAI,mDAAmD,EACnE,QAAS,EACT,EACD,CACC,SAAU,OACV,MAAO,KAAK,IAAI,qCAAqC,EACrD,QAAS,EACT,EACD,CACC,SAAU,YACV,MAAO,KAAK,IAAI,0CAA0C,EAC1D,QAAS,EACT,EACD,CACC,SAAU,qBACV,QAAS,GACT,QAAS,EACT,CACD,CACD,EAED,eAAgB,CACf,MAAO,CACN,CACC,SAAU,KACV,MAAO,KAAK,IAAI,iCAAiC,EACjD,QAAS,GACT,QAAS,GACT,QAAS,EACT,EACD,CACC,SAAU,SACV,MAAO,KAAK,IAAI,qCAAqC,EACrD,QAAS,EACT,EACD,CACC,SAAU,QACV,MAAO,KAAK,IAAI,oCAAoC,EACpD,QAAS,EACT,EACD,CACC,SAAU,YACV,MAAO,KAAK,IAAI,wCAAwC,EACxD,QAAS,EACd,CACI,CACJ,CACE,EAED,MAAO,CACN,QAAW,CACV,KAAK,oBAAqB,EAC1B,KAAK,iBAAkB,CAC1B,CACE,EAED,SAAU,CACT,KAAK,iBAAkB,CACvB,EAED,QAAS,CACR,kBAAmB,CAClB,KAAK,QAAU,KAAK,OAAO,OAAO,GAClC,MAAMP,EAAkB,KAAK,kBAAkB,OAAO,OAAO,EACvDC,EAAgB,IAAIJ,EAAS,EAAG,CAAC,EACvCI,EAAc,eAAe,cAAc,EAC3CA,EAAc,eAAe,cAAc,EAAE,WAAWJ,EAAS,KAAK,YAAa,MAAM,CAAC,EAE1FG,EAAgB,IAAI,KAAK,QAASJ,GAAQ,IAAKK,CAAa,EAAE,KAAMC,GAAU,CAC7E,KAAK,MAAQA,EACb,KAAK,UAAY,GACjB,IAAIiB,EAAkB,EAClBC,EAAoB,EACxB,MAAMC,EAAyBnB,EAAM,aAAa,CAAC,EAAE,aAAa,yBAClE,KAAK,4BAA4B,mBAAmBA,EAAM,eAAgBmB,CAAsB,EAC9F,KAAMC,GAAyB,CAC/B,KAAK,SAAWA,EAAqB,aAAa,CAAC,EAAE,SAErDA,EAAqB,aAAa,CAAC,EAAE,kBAAoBd,EAAM,OAAO,SACrEc,EAAqB,aAAa,CAAC,EAAE,oBACrC,KAAK,QACL,EAEDA,EAAqB,QAAQ,QAASC,GAAW,CAChDH,EAAoB,WAAW,WAAWA,CAAiB,EAAI,WAAWG,EAAO,MAAM,CAAC,EACxFA,EAAO,OAASf,EAAM,OAAO,SAC5Be,EAAO,OACP,KAAK,QACL,EAEDA,EAAO,WAAW,QAASC,GAAc,CACjCA,EAAU,kBAAoB,IACL,KAAK,uBAAuBA,EAAU,gBAAgB,IAAM,OAC5D,KAAK,uBAAuBA,EAAU,gBAAgB,EAAIA,EAAU,kBAEpE,KAAK,uBAAuBA,EAAU,gBAAgB,GAAKA,EAAU,mBAGzEA,EAAU,mBAAqB,IAC3B,KAAK,qBAAqBA,EAAU,gBAAgB,IAAM,OAC1D,KAAK,qBAAqBA,EAAU,gBAAgB,EAAIA,EAAU,mBAElE,KAAK,qBAAqBA,EAAU,gBAAgB,GAAKA,EAAU,mBAG3G,CAAQ,CAER,CAAO,EAEDF,EAAqB,aAAa,CAAC,EAAE,UAAU,QAASG,GAAa,CAC/DA,EAAS,KACbA,EAAS,GAAKA,EAAS,UAGHA,EAAS,mBAAqB,WAAW,KAAK,qBAAqBA,EAAS,QAAQ,GAAK,CAAC,EAAI,SAASA,EAAS,QAAQ,EACxHA,EAAS,mBAAqB,WAAWA,EAAS,kBAAkB,GAAK,EAEzEA,EAAS,qBAAuB,SAAS,KAAK,uBAAuBA,EAAS,QAAQ,CAAC,GAAK,EAC5FA,EAAS,iBAAmB,YACzBA,EAAS,mBAAqBA,EAAS,oBAAoB,QAAQ,CAAC,CACtE,EAEtBA,EAAS,mBAAqBjB,EAAM,OAAO,SAC1CiB,EAAS,mBACT,KAAK,QACL,EAEDA,EAAS,UAAYjB,EAAM,OAAO,SACjCiB,EAAS,UACT,KAAK,QACL,EAEDN,EAAkB,WAAW,WAAWA,CAAe,EAAI,WAAWM,EAAS,sBAAwBA,EAAS,QAAQ,CAAC,EAEzHA,EAAS,mBAAqB,SAC7B,SAASA,EAAS,QAAQ,EAAI,SAAS,KAAK,uBAAuBA,EAAS,QAAQ,GAAK,CAAC,CAC1F,CAER,CAAO,EAED,KAAK,UAAYH,EAAqB,aAAa,CAAC,EAAE,UACtD,KAAK,gBAAkBA,EACvB,KAAK,YAAc,KAAK,gBAAgB,aAAa,CAAC,EACtD,KAAK,aAAe,OAAO,KAAK,gBAAgB,aAAa,CAAC,EAAE,kBAAkB,EAClF,KAAK,iBAAmB,WAAW,WAAWH,CAAe,EAAI,WAAWC,CAAiB,CAAC,CAEpG,CAAM,EAAE,MAAOX,GAAkB,CAC5B,GAAI,CACH,KAAK,wBAAwB,CAC5B,MAAO,KAAK,IAAI,4CAA4C,EAC5D,QAASA,EAAc,QACvB,UAAW,EAClB,CAAO,CACD,MAAW,CACX,KAAK,wBAAwB,CAC5B,MAAO,KAAK,IAAI,4CAA4C,EAC5D,QAASA,EAAc,QACvB,UAAW,EAClB,CAAO,CACP,QAAe,CACT,KAAK,UAAY,EACvB,CACA,CAAK,CACL,CAAI,CACD,EACD,qBAAsB,CACrB,OAAO,KACN,KAAK,4BAA4B,eAChC,KAAK,YAAY,SAAS,eAC1B,KAAK,YAAY,EACjB,EACD,QACA,CACD,EAED,iBAAkB,CACjB,OAAO,KACN,KAAK,4BAA4B,mBAChC,KAAK,YAAY,SAAS,eAC1B,KAAK,YAAY,EACjB,EACD,QACA,CACD,EAED,qBAAsB,CACrB,KAAK,gBAAkB,CACtB,aAAc,CAAE,EAChB,QAAS,CAAA,CACT,EACD,KAAK,UAAY,CAAE,EACnB,KAAK,uBAAyB,CAAE,EAChC,KAAK,qBAAuB,CAAE,EAC9B,KAAK,UAAY,EACjB,EAED,WAAWiB,EAAWC,EAAYC,EAAoBC,EAAsB,CAC3E,KAAK,UAAYH,EACjB,KAAK,gBAAkBC,EACvB,KAAK,uBAAyBC,EACrB,KAAK,qBAAwB,MAAMC,CAAoB,EAAmD,EAA/C,KAAK,MAAMA,EAAuB,GAAG,EAAI,GAC7G,EAED,YAAa,CACZ,KAAK,UAAY,EACjB,EAED,eAAeF,EAAY,CAC1B,KAAK,UAAY,GACjB,KAAK,uBAAuB,aAC3B,KAAK,gBAAgB,aAAa,CAAC,EAAE,SAAS,eAC9C,KAAK,gBAAgB,aAAa,CAAC,EAAE,GACrC,EACAA,CACA,EAAC,KAAK,IAAM,CACZ,KAAK,0BAA0B,CAC9B,MAAO,KAAK,IAAI,2CAA2C,EAC3D,QAAS,KAAK,IAAI,6CAA6C,CACpE,CAAK,EACW,KAAK,UAAY,GACjB,KAAK,MAAM,aAAa,EACxB,KAAK,UAAU,IAAM,CACjB,KAAK,QAAQ,QAAQ,GAAG,KAAK,OAAO,IAAI,SAASnB,EAAM,SAAQ,CAAE,EAAE,CACvF,CAAiB,CACjB,CAAI,EAAE,MAAOC,GAAkB,CAC3B,GAAI,CACH,KAAK,wBAAwB,CAC5B,MAAOA,EAAc,SAAS,KAAK,OAAO,CAAC,EAAE,MAC7C,QAASA,EAAc,SAAS,KAAK,OAAO,CAAC,EAAE,OAC/C,UAAW,EACjB,CAAM,CACD,MAAW,CACX,KAAK,wBAAwB,CAC5B,MAAOA,EAAc,MACrB,QAASA,EAAc,SAAS,KAChC,UAAW,EACjB,CAAM,CACN,QAAc,CACM,KAAK,UAAY,GACjB,KAAK,MAAM,aAAa,EACxB,KAAK,UAAU,IAAM,CACjB,KAAK,QAAQ,QAAQ,GAAG,KAAK,OAAO,IAAI,SAASD,EAAM,SAAQ,CAAE,EAAE,CAC3F,CAAqB,CACrB,CACA,CAAI,CACD,EACD,aAAasB,EAAM,CAClB,OAAOA,EAAK,mBAAqB,GAAKA,EAAK,iBAAmB,GAAKA,EAAK,oBAAsB,GAAKA,EAAK,sBAAwB,CAChI,EACD,mBAAmBC,EAAW,CAC7B,KAAK,cAAgB,OAAO,OAAOA,CAAS,CAC5C,EACK,qBAAsB,CACd,KAAK,cAAc,SAEnB,KAAK,UAAY,GAGjB,KAAK,UAAU,IAAM,CACjB,MAAMC,EAAiB,KAAK,cAAc,IAAKF,GACpC,KAAK,mBAAmBA,EAAK,QAAQ,CAC/C,EAGD,QAAQ,IAAIE,CAAc,EACrB,KAAK,IAAM,CAER,KAAK,UAAY,GACjB,KAAK,MAAM,aAAa,EACxB,KAAK,UAAU,IAAM,CACjB,KAAK,QAAQ,QAAQ,GAAG,KAAK,OAAO,IAAI,SAASxB,EAAM,SAAQ,CAAE,EAAE,CACnG,CAA6B,CACJ,CAAA,EACA,MAAOyB,GAAU,CAEd,KAAK,wBAAwB,CACzB,MAAO,QACP,QAAS,wCACT,UAAW,EAC3C,CAA6B,EACD,KAAK,UAAY,EAC7C,CAAyB,CACzB,CAAiB,EAER,EACD,mBAAmBN,EAAY,CAC3B,OAAO,IAAI,QAAQ,CAACO,EAASC,IAAW,CACpC,KAAK,uBAAuB,aACxB,KAAK,gBAAgB,aAAa,CAAC,EAAE,SAAS,eAC9C,KAAK,gBAAgB,aAAa,CAAC,EAAE,GACrC,EACAR,CACpB,EACiB,KAAK,IAAM,CACR,KAAK,0BAA0B,CAC3B,MAAO,KAAK,IAAI,2CAA2C,EAC3D,QAAS,KAAK,IAAI,6CAA6C,CACvF,CAAqB,EACDO,EAAS,CACZ,CAAA,EACA,MAAOzB,GAAkB,CACtB,GAAI,CACA,KAAK,wBAAwB,CACzB,MAAOA,EAAc,SAAS,KAAK,OAAO,CAAC,EAAE,MAC7C,QAASA,EAAc,SAAS,KAAK,OAAO,CAAC,EAAE,OAC/C,UAAW,EACvC,CAAyB,CACJ,MAAW,CACR,KAAK,wBAAwB,CACzB,MAAOA,EAAc,MACrB,QAASA,EAAc,SAAS,KAChC,UAAW,EACvC,CAAyB,CACzB,QAA8B,CACN0B,EAAQ,CAChC,CACA,CAAiB,CACjB,CAAa,CACJ,CACT,CACA,CAAC,o+SCrbK,CAACC,OAAAA,EAAM,EAAI,SAEjBA,GAAO,SAAS,kBAAmB,CAClC,KAAM,SACN,KAAM,YACN,MAAO,gCACP,YAAa,gDACb,QAAS,QACT,cAAe,QACf,MAAO,UAEP,SAAU,CACT,QAASC,GACT,QAASC,GACT,QAASC,GACT,QAASC,EACT,EAED,gBAAgBC,EAAMC,EAAc,CAC/BA,EAAa,OAAS,mBACzBA,EAAa,SAAS,KAAK,CAC1B,UAAW,yBACX,KAAM,yBACN,WAAY,GACZ,KAAM,gCACV,CAAI,EAEFD,EAAKC,CAAY,CACnB,CACA,CAAC,ECvCD,SAAS,QAAQ,YAAY,EAAE,yBAAyB,CACpD,SAAU,cACV,OAAQ,YACR,IAAK,YACL,MAAO,CACH,OAAQ,CACJ,WAAY,CACR,qBACA,oCACA,oBACH,EACD,aAAc,CAAA,CACjB,EACD,OAAQ,CACJ,WAAY,CACR,uBACA,sCACA,sCACA,uBACA,uBACA,sBACH,EACD,aAAc,CACV,kBAChB,CACA,CACA,CACA,CAAC,EAED,SAAS,QAAQ,YAAY,EAAE,yBAAyB,CACpD,SAAU,cACV,OAAQ,KACR,IAAK,gBACL,MAAO,CACH,OAAQ,CACJ,WAAY,CACR,mCAChB,CACS,EACD,OAAQ,CACJ,WAAY,CACR,uBAChB,CACS,EACD,QAAS,CACL,WAAY,CACR,wBACA,yBACA,sBAChB,CACS,EACD,QAAS,CACL,WAAY,CACR,uBAChB,CACA,CACA,CACA,CAAC,ECzDD,MAAeC,GAAA,65HCAFC,EAAgB,0BAChBC,GAAyBD,EAAgB,kBACzCE,GAAuBF,EAAgB,gBACvCG,GAAqBH,EAAgB,eACrCI,GAAuCJ,EAAgB,8BACvDK,GAAkBL,EAAgB,WAClCM,GAAuBN,EAAgB,eACvCO,GAA6CP,EAAgB,oCAC7DQ,GAAiBR,EAAgB,UACjCS,GAA4CT,EAAgB,mCAC5DU,GAA4CV,EAAgB,mCAE1DW,EAAA,CACX,cAAAX,EACA,uBAAAC,GACA,qBAAAC,GACA,mBAAAC,GACA,qCAAAC,GACA,gBAAAC,GACA,qBAAAC,GACA,2CAAAC,GACA,eAAAC,GACA,0CAAAC,GACA,0CAAAC,EACJ,ECnBM,WAAC3D,GAAS,MAAEW,CAAK,EAAI,SAE3BX,GAAU,SAAS,qBAAsB,CAErC,SAAUI,GAEV,OAAQ,CACJ,MACA,+BACH,EAED,OAAQ,CACJO,EAAM,UAAU,cAAc,EAC9BA,EAAM,UAAU,mBAAmB,CACtC,EAED,MAAO,CACH,MAAO,CAEH,OAAQ,CAAE,EAEV,UAAW,GACX,UAAW,GAEX,iBAAkB,GAElB,qBAAsB,GACtB,yBAA0B,GAE1B,cAAe,GACf,kBAAmB,GAEnB,aAAc,GACd,iBAAkB,GAElB,8BAA+B,GAC/B,+BAAgC,GAEhC,8BAA+B,eAC/B,+BAAgC,GAChC,6CAA8C,GAC9C,0DAA2D,GAC3D,kDAAmD,GACnD,kDAAmD,GAEnD,GAAGiD,CACN,CACJ,EAED,MAAO,CACH,UAAW,CACP,KAAM,QACN,SAAU,EACtB,CACK,EAED,UAAW,CACP,MAAO,CACH,MAAO,KAAK,aAAY,CAC3B,CACJ,EAED,MAAO,CACH,OAAQ,CACJ,QAAQC,EAAY,CAChB,MAAMC,EAAgB,KAAK,MAAM,gBAAgB,WAAW,KACrC,KAAK,MAAM,gBAAgB,yBAC3B,MAEnB,KAAK,qBAAuB,CAAC,CAAC,KAAK,OAAO,KAAK,sBAAsB,EACrE,KAAK,cAAgB,CAAC,CAAC,KAAK,OAAO,KAAK,eAAe,EACvD,KAAK,aAAe,CAAC,CAAC,KAAK,OAAO,KAAK,cAAc,EAE/C,KAAK,sBAAsB,KAAK,SAClC,KAAK,OAAO,KAAK,kBAAkB,EAAI,KAAK,+BAG1C,KAAK,wBAAwB,KAAK,SACpC,KAAK,OAAO,KAAK,oBAAoB,EAAI,KAAK,gCAG5C,KAAK,wCAAwC,KAAK,SACpD,KAAK,OAAO,KAAK,oCAAoC,EAAI,KAAK,8CAG5D,KAAK,8CAA8C,KAAK,SAC1D,KAAK,OAAO,KAAK,0CAA0C,EAAI,KAAK,2DAGlE,KAAK,6CAA6C,KAAK,SACzD,KAAK,OAAO,KAAK,yCAAyC,EAAI,KAAK,mDAGjE,KAAK,6CAA6C,KAAK,SACzD,KAAK,OAAO,KAAK,yCAAyC,EAAI,KAAK,qDAKvE,KAAK,qBAAuB,CAAC,CAAC,KAAK,OAAO,KAAK,sBAAsB,GAAK,CAAC,CAACA,EAAc,KAAK,sBAAsB,EACrH,KAAK,cAAgB,CAAC,CAAC,KAAK,OAAO,KAAK,eAAe,GAAK,CAAC,CAACA,EAAc,KAAK,eAAe,EAChG,KAAK,aAAe,CAAC,CAAC,KAAK,OAAO,KAAK,cAAc,GAAK,CAAC,CAACA,EAAc,KAAK,cAAc,GAGzF,EAAE,KAAK,sBAAsB,KAAK,SAAW,EAAE,KAAK,sBAAsBA,MAC1E,KAAK,OAAO,KAAK,kBAAkB,EAAI,KAAK,gCAG5C,EAAE,KAAK,wBAAwB,KAAK,SAAW,EAAE,KAAK,wBAAwBA,MAC9E,KAAK,OAAO,KAAK,oBAAoB,EAAI,KAAK,iCAG9C,EAAE,KAAK,wCAAwC,KAAK,SAAW,EAAE,KAAK,wCAAwCA,MAC9G,KAAK,OAAO,KAAK,oCAAoC,EAAI,KAAK,+CAG9D,EAAE,KAAK,8CAA8C,KAAK,SAAW,EAAE,KAAK,8CAA8CA,MAC1H,KAAK,OAAO,KAAK,0CAA0C,EAAI,KAAK,4DAGpE,EAAE,KAAK,6CAA6C,KAAK,SAAW,EAAE,KAAK,6CAA6CA,MACxH,KAAK,OAAO,KAAK,yCAAyC,EAAI,KAAK,oDAGnE,EAAE,KAAK,6CAA6C,KAAK,SAAW,EAAE,KAAK,6CAA6CA,MACxH,KAAK,OAAO,KAAK,yCAAyC,EAAI,KAAK,oDAI3E,KAAK,MAAM,qBAAqB,EAChC,KAAK,MAAM,eAAgBD,CAAU,CACxC,EACD,KAAM,EAClB,CACK,EAED,QAAS,CACL,0BAA0BE,EAAO,CAC7B,OAAI,OAAOA,GAAU,SACV,GAGJA,EAAM,QAAU,CAC1B,EAED,4BAA4BA,EAAO,CAC/B,OAAI,OAAOA,GAAU,SACV,GAGJA,EAAM,QAAU,CAC1B,EAED,0BAA0BA,EAAO,CAC7B,OAAO,OAAOA,GAAU,SAC3B,EAED,gBAAgBC,EAAK,CACjB,OAAI,KAAK,wBAA0B,KACxB,KAAK,iBAAiBA,CAAG,EAEzB,KAAK,WAAW,KAAQA,CAAG,CAEzC,EAED,QAAS,CACL,GAAI,EAAE,KAAK,eAAiB,KAAK,cAAgB,KAAK,sBAAuB,CACzE,KAAK,eAAgB,EACrB,MAChB,CACY,KAAK,KAAM,CACd,EAED,MAAO,CACH,KAAK,UAAY,GAEjB,KAAK,MAAM,gBAAgB,KAAI,EAAG,KAAMC,GAAQ,CACxCA,IACA,KAAK,OAASA,GAElB,KAAK,iBAAkB,EACvB,KAAK,sCAAuC,EAC5C,KAAK,2BAA4B,CACjD,CAAa,EAAE,MAAO,GAAM,CACZ,QAAQ,MAAM,SAAU,CAAC,EACzB,KAAK,UAAY,EACjC,CAAa,CACJ,EAED,kBAAmB,CACf,GAAI,KAAK,OAAO,KAAK,yCAAyC,IAAM,GAChE,MAAO,GAGX,KAAK,8BAA8B,iBAAiB,KAAK,MAAM,gBAAgB,sBAAsB,EAChG,KAAK,IAAM,CACR,KAAK,0BAA0B,CAC3B,MAAO,KAAK,IAAI,6CAA6C,EAC7D,QAAS,KAAK,IAAI,sDAAsD,CAChG,CAAqB,CACrB,CAAiB,EAAE,MAAO,GAAM,CACZ,KAAK,wBAAwB,CACzB,MAAO,KAAK,IAAI,2CAA2C,EAC3D,QAAS,KAAK,IAAI,oDAAoD,CAC9F,CAAqB,EACD,KAAK,UAAY,GACjB,QAAQ,MAAM,SAAU,CAAC,CAC7C,CAAa,CACJ,EAED,uCAAwC,CACpC,GAAI,KAAK,OAAO,KAAK,yCAAyC,IAAM,GAChE,MAAO,GAGX,KAAK,8BAA8B,sCAAsC,KAAK,MAAM,gBAAgB,sBAAsB,EACrH,KAAK,IAAM,CACR,KAAK,0BAA0B,CAC3B,MAAO,KAAK,IAAI,6CAA6C,EAC7D,QAAS,KAAK,IAAI,yEAAyE,CACnH,CAAqB,EACD,KAAK,UAAY,EACrC,CAAiB,EAAE,MAAO,GAAM,CACZ,KAAK,wBAAwB,CACzB,MAAO,KAAK,IAAI,2CAA2C,EAC3D,QAAS,KAAK,IAAI,uEAAuE,CACjH,CAAqB,EACD,KAAK,UAAY,GACjB,QAAQ,MAAM,SAAU,CAAC,CAC7C,CAAa,CACJ,EAED,4BAA4B,CACxB,KAAK,8BAA8B,2BAA0B,EACxD,KAAK,IAAM,CACR,KAAK,0BAA0B,CAC3B,MAAO,KAAK,IAAI,6CAA6C,EAC7D,QAAS,KAAK,IAAI,iEAAiE,CAC3G,CAAqB,EACD,KAAK,UAAY,EACrC,CAAiB,EAAE,MAAM,IAAM,CACX,KAAK,wBAAwB,CACzB,MAAO,KAAK,IAAI,2CAA2C,EAC3D,QAAS,KAAK,IAAI,+DAA+D,CACzG,CAAqB,EACD,KAAK,UAAY,EACrC,CAAa,CACJ,EAED,2BAA4B,CACxB,KAAK,+BAAiC,GACtC,KAAK,8BAA8B,yCAC/B,KAAK,MAAM,gBAAgB,sBAC9B,EAAC,KAAK,IAAM,CACT,KAAK,+BAAiC,GACtC,KAAK,8BAAgC,GACrC,KAAK,0BAA0B,CAC3B,MAAO,KAAK,IAAI,6CAA6C,EAC7D,QAAS,KAAK,IAAI,kEAAkE,CACxG,CAAiB,CACjB,CAAa,CACJ,EAED,gBAAiB,CACb,MAAMC,EAA4B,CAC9B,KAAM,EACN,OAAQ,KAAK,IAAI,oCAAoC,CACxD,EAEI,KAAK,gBACN,KAAK,kBAAoBA,GAGxB,KAAK,eACN,KAAK,iBAAmBA,GAGvB,KAAK,uBACN,KAAK,yBAA2BA,EAEvC,EAID,qBAAqBC,EAAmB,CACpC,KAAM,CAAE,QAAAC,EAAS,OAAAC,EAAQ,eAAAC,CAAgB,EAAGH,EAC5C,KAAK,UAAY,GAEjB,KAAK,8BAA8B,mBAAmBC,EAASC,EAAQC,CAAc,EAChF,KAAML,GAAQ,CACPA,EAAI,SAAW,IACf,KAAK,0BAA0B,CAC3B,MAAO,KAAK,IAAI,wDAAwD,EACxE,QAAS,KAAK,IAAI,iEAAiE,CAC/G,CAAyB,EAED,KAAK,wBAAwB,CACzB,MAAO,KAAK,IAAI,wDAAwD,EACxE,QAAS,KAAK,IAAI,+DAA+D,CAC7G,CAAyB,EAEL,KAAK,UAAY,EACrC,CAAiB,EAAE,MAAM,IAAM,CACX,KAAK,wBAAwB,CACzB,MAAO,KAAK,IAAI,wDAAwD,EACxE,QAAS,KAAK,IAAI,+DAA+D,CACzG,CAAqB,EACD,KAAK,UAAY,EACrC,CAAa,CACb,CACA,CACA,CAAC,EC5TD,MAAeM,GAAA,m0GCKT,WAACvE,GAAS,MAAEW,EAAK,EAAI,SAE3BX,GAAU,SAAS,2BAA4B,CAC3C,SAAUI,GAEV,KAAM,uBAEN,OAAQ,CACJ,KACH,EAED,OAAQ,CACJO,GAAM,UAAU,cAAc,CACjC,EAED,MAAO,CACH,iBAAkB,CACd,KAAM,OACN,SAAU,EACb,EACD,WAAY,CACR,KAAM,OACN,SAAU,EACb,EAED,uBAAwB,CACpB,SAAU,EACb,EACD,cAAe,CACX,KAAM,QACN,SAAU,EACb,EACD,kBAAmB,CACf,SAAU,EACb,EACD,aAAc,CACV,KAAM,QACN,SAAU,EACb,EACD,iBAAkB,CACd,SAAU,EACb,EACD,qBAAsB,CAClB,KAAM,QACN,SAAU,EACb,EACD,yBAA0B,CACtB,SAAU,EACb,EACD,UAAW,CACP,KAAM,QACN,SAAU,EACb,EACD,UAAW,CACP,KAAM,QACN,SAAU,EACtB,CACK,EAED,MAAO,CACH,MAAO,CACH,GAAGiD,CACN,CACJ,EAED,QAAS,CAEL,0BAA0BG,EAAO,CAC7B,OAAI,OAAOA,GAAU,SACV,GAGJA,EAAM,QAAU,CAC1B,EAED,4BAA4BA,EAAO,CAC/B,OAAI,OAAOA,GAAU,SACV,GAGJA,EAAM,QAAU,CAC1B,EAED,0BAA0BA,EAAO,CAC7B,OAAO,OAAOA,GAAU,SAC3B,EAID,6BAA8B,CAC1B,MAAMS,EAAsB,CACxB,QAAS,KAAK,iBAAiBZ,EAAU,eAAe,EACxD,OAAQ,KAAK,iBAAiBA,EAAU,cAAc,EACtD,eAAgB,KAAK,iBAAiBA,EAAU,sBAAsB,CACzE,EAED,KAAK,MAAM,6BAA8BY,CAAmB,CACxE,CACA,CACA,CAAC,ECxGD,MAAeC,GAAA,qqHCKT,WAACzE,GAAS,MAAEW,EAAK,EAAI,SAE3BX,GAAU,SAAS,uBAAwB,CAC1C,SAAUI,GAEV,KAAM,mBAEN,OAAQ,CACPO,GAAM,UAAU,cAAc,CAC9B,EAED,MAAO,CACN,iBAAkB,CACjB,KAAM,OACN,SAAU,EACV,EACD,WAAY,CACX,KAAM,OACN,SAAU,EACV,EACD,uBAAwB,CACvB,SAAU,EACV,EACD,UAAW,CACV,KAAM,QACN,SAAU,EACb,CACE,EAED,MAAO,CACN,MAAO,CACN,GAAGiD,CACH,CACD,EAED,SAAU,CACT,oBAAqB,CACpB,MAAO,CACN,CACC,GAAI,eACJ,KAAM,KAAK,IAAI,yEAAyE,CACxF,EACD,CACC,GAAI,SACJ,KAAM,KAAK,IAAI,mEAAmE,CACvF,CACI,CACJ,CACE,EAED,QAAS,CACR,0BAA0BG,EAAO,CAChC,OAAI,OAAOA,GAAU,SACb,GAGDA,EAAM,QAAU,CACvB,EAED,4BAA4BA,EAAO,CAClC,OAAI,OAAOA,GAAU,SACb,GAGDA,EAAM,QAAU,CACvB,EAED,0BAA0BA,EAAO,CAChC,OAAO,OAAOA,GAAU,SAC3B,CACA,CACA,CAAC,EC5ED,MAAeW,GAAA,0qYCET,CAAE1E,UAAAA,EAAW,EAAG,SAEtBA,GAAU,SAAS,6BAA8B,CAC7CI,SAAAA,EACJ,CAAC,ECND,MAAeuE,GAAA,09BCKT,WAAC3E,GAAS,MAAEW,EAAK,EAAI,SAE3BX,GAAU,SAAS,kCAAmC,CACrD,SAAUI,GAEV,KAAM,6BAEN,OAAQ,CACPO,GAAM,UAAU,cAAc,CAC9B,EAED,MAAO,CACN,iBAAkB,CACjB,KAAM,OACN,SAAU,EACV,EACD,WAAY,CACX,KAAM,OACN,SAAU,EACV,EACD,uBAAwB,CACvB,SAAU,EACV,EACD,UAAW,CACV,KAAM,QACN,SAAU,EACb,CACE,EAED,MAAO,CACN,MAAO,CACN,GAAGiD,CACH,CACD,EAED,QAAS,CACR,0BAA0BG,EAAO,CAChC,OAAI,OAAOA,GAAU,SACb,GAGDA,EAAM,QAAU,CACvB,EAED,4BAA4BA,EAAO,CAClC,OAAI,OAAOA,GAAU,SACb,GAGDA,EAAM,QAAU,CACvB,EAED,0BAA0BA,EAAO,CAChC,OAAO,OAAOA,GAAU,SAC3B,CACA,CACA,CAAC,EC7DD,MAAe3D,GAAA,mrDCKT,CAAC,UAAAJ,GAAW,MAAAW,EAAK,EAAI,SAE3BX,GAAU,SAAS,gCAAiC,CACnD,SAAUI,GAEV,KAAM,2BAEN,OAAQ,CACP,KACA,EAED,OAAQ,CACPO,GAAM,UAAU,cAAc,CAC9B,EAED,MAAO,CACN,iBAAkB,CACjB,KAAM,OACN,SAAU,EACV,EACD,WAAY,CACX,KAAM,OACN,SAAU,EACV,EACD,uBAAwB,CACvB,SAAU,EACV,EACD,UAAW,CACV,KAAM,QACN,SAAU,EACb,CACE,EAED,MAAO,CACN,MAAO,CACN,GAAGiD,CACH,CACD,EAED,QAAS,CACR,0BAA0BG,EAAO,CAChC,OAAI,OAAOA,GAAU,SACb,GAGDA,EAAM,QAAU,CACvB,EAED,4BAA4BA,EAAO,CAClC,OAAI,OAAOA,GAAU,SACb,GAGDA,EAAM,QAAU,CACvB,EAED,0BAA0BA,EAAO,CAChC,OAAO,OAAOA,GAAU,SAC3B,CACA,CACA,CAAC,wjaClDK,CAAC,OAAAtB,EAAM,EAAI,SAEjBA,GAAO,SAAS,qBAAsB,CACrC,KAAM,SACN,KAAM,YACN,MAAO,mDACP,YAAa,mDACb,MAAO,UACP,KAAM,0BACN,QAAS,QACT,cAAe,QAEZ,SAAU,CACN,QAASC,GACT,QAASC,GACT,QAASC,GACT,QAASC,EACZ,EAEJ,OAAQ,CACP,MAAO,CACN,UAAW,qBACX,KAAM,QACN,KAAM,CACL,WAAY,oBACZ,UAAW,kBACX,EACD,MAAO,CACM,QAAU+B,IACC,CACH,KAAMA,EAAM,OAAO,IACtB,EAER,CACb,CACE,EAED,aAAc,CACb,MAAO,UACP,GAAI,2BACJ,cAAe,6BACf,kBAAmB,GACnB,UAAW,kBACb,CAEA,CAAC,EC1DD,MAAMC,EAAa,SAAS,QAAQ,WAKpC,MAAMC,WAAsCD,CAAW,CAStD,YAAYE,EAAYC,EAAcC,EAAc,YAAa,CAChE,MAAMF,EAAYC,EAAcC,CAAW,CAC7C,CAQC,iBAAiBC,EAAiB,KAAM,CAEvC,MAAMC,EAAU,KAAK,gBAAiB,EAChCC,EAAW,GAAG,SAAS,QAAQ,IAAI,OAAO,YAAY,KAAK,eAAc,CAAE,oCAEjF,OAAO,KAAK,WAAW,KACtBA,EACA,CACC,eAAgBF,CAChB,EACD,CACC,QAASC,CACb,CACA,EAAI,KAAME,GACAR,EAAW,eAAeQ,CAAQ,CACzC,CACH,CAUC,mBAAmBjB,EAAU,KAAMC,EAAS,KAAMiB,EAAgB,KAAM,CAEvE,MAAMH,EAAU,KAAK,gBAAiB,EAChCC,EAAW,GAAG,SAAS,QAAQ,IAAI,OAAO,YAAY,KAAK,eAAc,CAAE,sCAEjF,OAAO,KAAK,WAAW,KACtBA,EACA,CACC,QAAShB,EACT,OAAQC,EACR,cAAeiB,CACf,EACD,CACC,QAASH,CACb,CACA,EAAI,KAAME,GACAR,EAAW,eAAeQ,CAAQ,CACzC,CACH,CASC,yCAAyCH,EAAiB,KAAM,CAE/D,MAAMC,EAAU,KAAK,gBAAiB,EAChCC,EAAW,GAAG,SAAS,QAAQ,IAAI,OAAO,YAAY,KAAK,eAAc,CAAE,gEAEjF,OAAO,KAAK,WAAW,KACtBA,EACA,CACC,eAAgBF,CAChB,EACD,CACC,QAASC,CACb,CACA,EAAI,KAAME,GACAR,EAAW,eAAeQ,CAAQ,CACzC,CACH,CAOC,sCAAsCH,EAAiB,KAAM,CAC5D,MAAMC,EAAU,KAAK,gBAAiB,EAChCC,EAAW,GAAG,SAAS,QAAQ,IAAI,OAAO,YAAY,KAAK,eAAc,CAAE,0DAEjF,OAAO,KAAK,WAAW,KACtBA,EACA,CACC,eAAgBF,CAChB,EACD,CACC,QAASC,CACb,CACA,EAAI,KAAME,GACAR,EAAW,eAAeQ,CAAQ,CACzC,CACH,CAMC,4BAA6B,CAC5B,MAAMF,EAAU,KAAK,gBAAiB,EAChCC,EAAW,GAAG,SAAS,QAAQ,IAAI,OAAO,YAAY,KAAK,eAAc,CAAE,+CAEjF,OAAO,KAAK,WAAW,KACtBA,EACA,CACC,EACD,CACC,QAASD,CACb,CACA,EAAI,KAAME,GACAR,EAAW,eAAeQ,CAAQ,CACzC,CACH,CACA,CCxIA,MAAMR,EAAa,SAAS,QAAQ,WAKpC,MAAMU,WAA+BV,CAAW,CAS/C,YAAYE,EAAYC,EAAcC,EAAc,YAAa,CAChE,MAAMF,EAAYC,EAAcC,CAAW,CAC7C,CAWC,aAAaC,EAAgBM,EAAeC,EAAUzD,EAAY,CAEjE,MAAMmD,EAAU,KAAK,gBAAiB,EAChCC,EAAW,GAAG,SAAS,QAAQ,IAAI,OAAO,YAAY,KAAK,eAAc,CAAE,yBAEjF,OAAO,KAAK,WAAW,KACtBA,EACA,CACC,eAAgBF,EAChB,cAAeM,EACf,SAAUC,EACV,WAAYzD,CACZ,EACD,CACC,QAASmD,CACb,CACA,EAAI,KAAME,GACAR,EAAW,eAAeQ,CAAQ,CACzC,CACH,CAUC,qBAAqBH,EAAgBM,EAAeE,EAAkB,CAErE,MAAMP,EAAU,KAAK,gBAAiB,EAChCC,EAAW,GAAG,SAAS,QAAQ,IAAI,OAAO,YAAY,KAAK,eAAc,CAAE,mCAEjF,OAAO,KAAK,WAAW,KACtBA,EACA,CACC,eAAgBF,EAChB,cAAeM,EACf,iBAAkBE,CAClB,EACD,CACC,QAASP,CACb,CACA,EAAI,KAAME,GACAR,EAAW,eAAeQ,CAAQ,CACzC,CACH,CAWC,oBAAoBH,EAAgBM,EAAeE,EAAkB1D,EAAY,CAEhF,MAAMmD,EAAU,KAAK,gBAAiB,EAChCC,EAAW,GAAG,SAAS,QAAQ,IAAI,OAAO,YAAY,KAAK,eAAc,CAAE,iCAEjF,OAAO,KAAK,WAAW,KACtBA,EACA,CACC,eAAgBF,EAChB,cAAeM,EACf,iBAAkBE,EAClB,WAAY1D,CACZ,EACD,CACC,QAASmD,CACb,CACA,EAAI,KAAME,GACAR,EAAW,eAAeQ,CAAQ,CACzC,CACH,CACA,CCzGA,MAAMR,EAAa,SAAS,QAAQ,WAKpC,MAAMc,WAAoCd,CAAW,CASpD,YAAYE,EAAYC,EAAcC,EAAc,YAAa,CAChE,MAAMF,EAAYC,EAAcC,CAAW,CAC7C,CASC,mBAAmBC,EAAgBM,EAAe,CAEjD,MAAML,EAAU,KAAK,gBAAiB,EAChCC,EAAW,GAAG,SAAS,QAAQ,IAAI,OAAO,YAAY,KAAK,eAAc,CAAE,qCAEjF,OAAO,KAAK,WAAW,KACtBA,EACA,CACC,eAAgBF,EAChB,cAAeM,CACf,EACD,CACC,QAASL,CACb,CACA,EAAI,KAAME,GACAR,EAAW,eAAeQ,CAAQ,CACzC,CACH,CAUC,mBAAmBH,EAAgBM,EAAe,CACjD,MAAO,GAAG,SAAS,QAAQ,IAAI,OAAO,YAAY,KAAK,eAAgB,CAAA,qCAAqCN,CAAc,IAAIM,CAAa,EAC7I,CASC,eAAeN,EAAgBM,EAAe,CAC7C,MAAO,GAAG,SAAS,QAAQ,IAAI,OAAO,YAAY,KAAK,eAAgB,CAAA,iCAAiCN,CAAc,IAAIM,CAAa,EACzI,CACA,CClEA,MAAMX,EAAa,SAAS,QAAQ,WAKpC,MAAMe,WAA8Cf,CAAW,CAS9D,YAAYE,EAAYC,EAAcC,EAAc,YAAa,CAChE,MAAMF,EAAYC,EAAcC,CAAW,CAC7C,CASC,4BAA4BC,EAAgBM,EAAe,CAE1D,MAAML,EAAU,KAAK,gBAAiB,EAChCC,EAAW,GAAG,SAAS,QAAQ,IAAI,OAAO,YAAY,KAAK,eAAc,CAAE,yDAEjF,OAAO,KAAK,WAAW,KACtBA,EACA,CACC,eAAgBF,EAChB,cAAeM,CACf,EACD,CACC,QAASL,CACb,CACA,EAAI,KAAME,GACAR,EAAW,eAAeQ,CAAQ,CACzC,CACH,CACA,CC3CA,MAAMR,EAAa,SAAS,QAAQ,WAKpC,MAAMgB,WAAwChB,CAAW,CASxD,YAAYE,EAAYC,EAAcC,EAAc,YAAa,CAChE,MAAMF,EAAYC,EAAcC,CAAW,CAC7C,CASC,sBAAsBC,EAAgBM,EAAe,CAEpD,MAAML,EAAU,KAAK,gBAAiB,EAChCC,EAAW,GAAG,SAAS,QAAQ,IAAI,OAAO,YAAY,KAAK,eAAc,CAAE,6CAEjF,OAAO,KAAK,WAAW,KACtBA,EACA,CACC,eAAgBF,EAChB,cAAeM,CACf,EACD,CACC,QAASL,CACb,CACA,EAAI,KAAME,GACAR,EAAW,eAAeQ,CAAQ,CACzC,CACH,CACA,CClCA,KAAM,CAAC,YAAAS,CAAW,EAAI,SAGtBA,EAAY,mBAAmB,gCAAkCC,GAAc,CAC9E,MAAMC,EAAgBF,EAAY,aAAa,MAAM,EACrD,OAAO,IAAIhB,GAA8BkB,EAAc,WAAYD,EAAU,YAAY,CAC1F,CAAC,EAGDD,EAAY,mBAAmB,yBAA2BC,GAAc,CACvE,MAAMC,EAAgBF,EAAY,aAAa,MAAM,EACrD,OAAO,IAAIP,GAAuBS,EAAc,WAAYD,EAAU,YAAY,CACnF,CAAC,EAGDD,EAAY,mBAAmB,8BAAgCC,GAAc,CAC5E,MAAMC,EAAgBF,EAAY,aAAa,MAAM,EACrD,OAAO,IAAIH,GAA4BK,EAAc,WAAYD,EAAU,YAAY,CACxF,CAAC,EAGDD,EAAY,mBAAmB,wCAA0CC,GAAc,CACtF,MAAMC,EAAgBF,EAAY,aAAa,MAAM,EACrD,OAAO,IAAIF,GAAsCI,EAAc,WAAYD,EAAU,YAAY,CAClG,CAAC,EAGDD,EAAY,mBAAmB,kCAAoCC,GAAc,CAChF,MAAMC,EAAgBF,EAAY,aAAa,MAAM,EACrD,OAAO,IAAID,GAAgCG,EAAc,WAAYD,EAAU,YAAY,CAC5F,CAAC"} \ No newline at end of file diff --git a/src/Resources/public/administration/assets/v-r-payment-payment-D4AH6HY2.css b/src/Resources/public/administration/assets/v-r-payment-payment-D4AH6HY2.css new file mode 100644 index 0000000..f68a9a8 --- /dev/null +++ b/src/Resources/public/administration/assets/v-r-payment-payment-D4AH6HY2.css @@ -0,0 +1 @@ +.sw-order-detail .sw-tabs{margin-top:40px}.sw-order-detail .sw-order-detail-base .mt-card-view__content{overflow-x:visible;overflow-y:visible}.vrpayment-order-detail__data{display:grid}.vrpayment-order-detail__heading{padding-top:15px} diff --git a/src/Resources/public/storefront/js/app.js b/src/Resources/public/storefront/js/app.js new file mode 100644 index 0000000..773dfe8 --- /dev/null +++ b/src/Resources/public/storefront/js/app.js @@ -0,0 +1,201 @@ +/* global window */ +// noinspection ThisExpressionReferencesGlobalObjectJS +(function (window) { + /** + * VRPaymentCheckout + * @type { + * { + * payment_method_handler_name: string, + * payment_method_iframe_class: string, + * init: init, + * validationCallBack: validationCallBack, + * payment_method_handler_status: string, + * submitPayment: (function(*): boolean), + * payment_method_iframe_prefix: string, + * payment_form_id: string, + * payment_method_handler_prefix: string, + * payment_method_tabs: string, + * getIframe: (function(): boolean + * } + * } + */ + const VRPaymentCheckout = { + /** + * Variables + */ + payment_panel_id: 'vrpayment-payment-panel', + payment_method_iframe_id: 'vrpayment-payment-iframe', + payment_method_handler_name: 'vrpayment_payment_handler', + payment_method_handler_status: 'input[name="vrpayment_payment_handler_validation_status"]', + payment_form_id: 'confirmOrderForm', + button_cancel_id: 'vrpaymentOrderCancel', + // button_home_override: 'vrpaymentHomeLink', + loader_id: 'vrpaymentLoader', + checkout_url: null, + checkout_url_id: 'checkoutUrl', + cart_recreate_url: null, + cart_recreate_url_id: 'cartRecreateUrl', + handler: null, + + /** + * Initialize plugin + */ + init: function () { + VRPaymentCheckout.activateLoader(true); + this.checkout_url = document.getElementById(this.checkout_url_id).value; + this.cart_recreate_url = document.getElementById(this.cart_recreate_url_id).value; + + document.getElementById(this.button_cancel_id).addEventListener('click', this.recreateCart, false); + // document.getElementById(this.button_home_override).addEventListener('click', this.recreateCart, false); + document.getElementById(this.payment_form_id).addEventListener('submit', this.submitPayment, false); + + VRPaymentCheckout.getIframe(); + }, + + activateLoader: function (activate) { + const buttons = document.querySelectorAll('button'); + if (activate) { + for (let i = 0; i < buttons.length; i++) { + buttons[i].disabled = true; + } + } else { + for (let i = 0; i < buttons.length; i++) { + buttons[i].disabled = false; + } + } + }, + + hideLoader: function () { + const loader = document.getElementById(VRPaymentCheckout.loader_id); + if (loader !== null && loader.parentNode !== null) { + loader.parentNode.removeChild(loader); + } + VRPaymentCheckout.activateLoader(false); + }, + + recreateCart: function (e) { + window.location.href = VRPaymentCheckout.cart_recreate_url; + e.preventDefault(); + }, + + /** + * Submit form + * + * @param event + * @return {boolean} + */ + submitPayment: function (event) { + VRPaymentCheckout.activateLoader(true); + VRPaymentCheckout.handler.validate(); + event.preventDefault(); + return false; + }, + + /** + * Get iframe + */ + getIframe: function () { + const paymentPanel = document.getElementById(VRPaymentCheckout.payment_panel_id); + const paymentMethodConfigurationId = paymentPanel.dataset.id; + const iframeContainer = document.getElementById(VRPaymentCheckout.payment_method_iframe_id); + + if (!VRPaymentCheckout.handler) { // iframe has not been loaded yet + // noinspection JSUnresolvedFunction + VRPaymentCheckout.handler = window.IframeCheckoutHandler(paymentMethodConfigurationId); + // noinspection JSUnresolvedFunction + VRPaymentCheckout.handler.setValidationCallback(function(validationResult){ + VRPaymentCheckout.hideErrors(); + VRPaymentCheckout.validationCallBack(validationResult); + }); + VRPaymentCheckout.handler.setInitializeCallback(VRPaymentCheckout.hideLoader()); + VRPaymentCheckout.handler.setHeightChangeCallback(function(height){ + if(height < 1){ // iframe has no fields + VRPaymentCheckout.handler.submit(); + } + }); + VRPaymentCheckout.handler.create(iframeContainer); + setTimeout(VRPaymentCheckout.hideLoader(), 10000); + + } + }, + + /** + * validation callback + * @param validationResult + */ + validationCallBack: function (validationResult) { + if (validationResult.success) { + document.querySelector(this.payment_method_handler_status).value = true; + VRPaymentCheckout.handler.submit(); + } else { + document.body.scrollTop = 0; + document.documentElement.scrollTop = 0; + + if (validationResult.errors) { + VRPaymentCheckout.showErrors(validationResult.errors); + } + document.querySelector(this.payment_method_handler_status).value = false; + VRPaymentCheckout.activateLoader(false); + } + }, + + showErrors: function(errors) { + let alert = document.createElement('div'); + alert.setAttribute('class', 'alert alert-danger'); + alert.setAttribute('role', 'alert'); + alert.setAttribute('id', 'vrpayment-errors'); + document.getElementsByClassName('flashbags')[0].appendChild(alert); + + let alertContentContainer = document.createElement('div'); + alertContentContainer.setAttribute('class', 'alert-content-container'); + alert.appendChild(alertContentContainer); + + let alertContent = document.createElement('div'); + alertContent.setAttribute('class', 'alert-content'); + alertContentContainer.appendChild(alertContent); + + if (errors.length > 1) { + let alertList = document.createElement('ul'); + alertList.setAttribute('class', 'alert-list'); + alertContent.appendChild(alertList); + for (let index = 0; index < errors.length; index++) { + let alertListItem = document.createElement('li'); + alertListItem.innerHTML = errors[index]; + alertList.appendChild(alertListItem); + } + } else { + alertContent.innerHTML = errors[0]; + } + }, + + hideErrors: function() { + let errorElement = document.getElementById('vrpayment-errors'); + if (errorElement) { + errorElement.parentNode.removeChild(errorElement); + } + } + }; + + window.VRPaymentCheckout = VRPaymentCheckout; + +}(typeof window !== "undefined" ? window : this)); + +/** + * Vanilla JS over JQuery + */ +window.addEventListener('load', function (e) { + VRPaymentCheckout.init(); + window.history.pushState({}, document.title, VRPaymentCheckout.cart_recreate_url); + window.history.pushState({}, document.title, VRPaymentCheckout.checkout_url); +}, false); + +/** + * This only works if the user has interacted with the page + * @link https://stackoverflow.com/questions/57339098/chrome-popstate-not-firing-on-back-button-if-no-user-interaction + */ +window.addEventListener('popstate', function (e) { + if (window.history.state == null) { // This means it's page load + return; + } + window.location.href = VRPaymentCheckout.cart_recreate_url; +}, false); diff --git a/src/Resources/snippet/storefront/vrpayment.de-DE.json b/src/Resources/snippet/storefront/vrpayment.de-DE.json new file mode 100644 index 0000000..07308e5 --- /dev/null +++ b/src/Resources/snippet/storefront/vrpayment.de-DE.json @@ -0,0 +1,27 @@ +{ + "vrpayment": { + "account": { + "downloadInvoice": "Rechnung herunterladen" + }, + "cookie": { + "name": "VRPayment-Zahlungen" + }, + "deliveryState": { + "hold": "Halten", + "unhold": "Aufheben" + }, + "payButton": "Zahlen", + "payHeader": "Bestellung bezahlen", + "payload": { + "adjustmentLineItem": "Anpassung Einzelposten", + "shipping": { + "lineItem": "Versand", + "name": "Versand" + }, + "taxes": "Steuern" + }, + "paymentMethod": { + "notAvailable": "Die Zahlungsmethode ist derzeit nicht verfügbar. Bitte wählen Sie eine andere Zahlungsmethode." + } + } +} \ No newline at end of file diff --git a/src/Resources/snippet/storefront/vrpayment.en-GB.json b/src/Resources/snippet/storefront/vrpayment.en-GB.json new file mode 100644 index 0000000..fbbe547 --- /dev/null +++ b/src/Resources/snippet/storefront/vrpayment.en-GB.json @@ -0,0 +1,27 @@ +{ + "vrpayment": { + "account": { + "downloadInvoice": "Download Invoice" + }, + "cookie": { + "name": "VRPayment Payment" + }, + "deliveryState": { + "hold": "Hold", + "unhold": "Unhold" + }, + "payButton": "Pay", + "payHeader": "Pay order", + "payload": { + "adjustmentLineItem": "Adjustment Line Item", + "shipping": { + "lineItem": "Shipping", + "name": "Shipping" + }, + "taxes": "Taxes" + }, + "paymentMethod": { + "notAvailable": "Payment method is not currently available. Please choose another payment method." + } + } +} \ No newline at end of file diff --git a/src/Resources/snippet/storefront/vrpayment.fr-FR.json b/src/Resources/snippet/storefront/vrpayment.fr-FR.json new file mode 100644 index 0000000..6840863 --- /dev/null +++ b/src/Resources/snippet/storefront/vrpayment.fr-FR.json @@ -0,0 +1,27 @@ +{ + "vrpayment": { + "account": { + "downloadInvoice": "Télécharger la facture" + }, + "cookie": { + "name": "VRPayment Paiement" + }, + "deliveryState": { + "hold": "Tenir", + "unhold": "Détacher" + }, + "payButton": "Payez", + "payHeader": "Commande de paiement", + "payload": { + "adjustmentLineItem": "Poste d'ajustement", + "shipping": { + "lineItem": "Expédition", + "name": "Expédition" + }, + "taxes": "Taxes" + }, + "paymentMethod": { + "notAvailable": "Le mode de paiement n'est pas disponible actuellement. Veuillez choisir un autre mode de paiement." + } + } +} \ No newline at end of file diff --git a/src/Resources/snippet/storefront/vrpayment.it-IT.json b/src/Resources/snippet/storefront/vrpayment.it-IT.json new file mode 100644 index 0000000..d214335 --- /dev/null +++ b/src/Resources/snippet/storefront/vrpayment.it-IT.json @@ -0,0 +1,27 @@ +{ + "vrpayment": { + "account": { + "downloadInvoice": "Scaricare la fattura" + }, + "cookie": { + "name": "Pagamento VRPayment" + }, + "deliveryState": { + "hold": "Tenere", + "unhold": "Aprite" + }, + "payButton": "Paga", + "payHeader": "Ordine di pagamento", + "payload": { + "adjustmentLineItem": "Voce di aggiustamento", + "shipping": { + "lineItem": "Spedizione", + "name": "Spedizione" + }, + "taxes": "Tasse" + }, + "paymentMethod": { + "notAvailable": "Il metodo di pagamento non è attualmente disponibile. Si prega di scegliere un altro metodo di pagamento." + } + } +} diff --git a/src/Resources/views/storefront/page/account/order-history/order-item.html.twig b/src/Resources/views/storefront/page/account/order-history/order-item.html.twig new file mode 100644 index 0000000..d77c25d --- /dev/null +++ b/src/Resources/views/storefront/page/account/order-history/order-item.html.twig @@ -0,0 +1,18 @@ +{% sw_extends '@Storefront/storefront/page/account/order-history/order-item.html.twig' %} +{% block page_account_order_item_context_menu_content %} + {{ parent() }} + {% block vrpayment_page_account_order_item_context_menu_content %} + {% if page.extensions.vrpaymentSettings and page.extensions.vrpaymentSettings.storefrontInvoiceDownloadEnabled %} + {% set vrpaymentFormattedHandlerIdentifier = 'handler_vrpaymentpayment_vrpaymentpaymenthandler' %} + {% set orderPaymentState = order.transactions.last.stateMachineState.technicalName %} + {% set orderPaymentMethodFormattedHandlerIdentifier = order.transactions.last.paymentMethod.formattedHandlerIdentifier %} + {% if (vrpaymentFormattedHandlerIdentifier == orderPaymentMethodFormattedHandlerIdentifier) and (orderPaymentState in ['paid', 'refunded']) %} + + {{ "vrpayment.account.downloadInvoice"|trans|sw_sanitize }} + + {% endif %} + {% endif %} + {% endblock %} +{% endblock %} \ No newline at end of file diff --git a/src/Resources/views/storefront/page/checkout/order/vrpayment.html.twig b/src/Resources/views/storefront/page/checkout/order/vrpayment.html.twig new file mode 100644 index 0000000..9aa434c --- /dev/null +++ b/src/Resources/views/storefront/page/checkout/order/vrpayment.html.twig @@ -0,0 +1,154 @@ +{% sw_extends '@Storefront/storefront/page/checkout/_page.html.twig' %} + +{% block base_body_classes %}vrpayment-payment is-act-confirmpage{% endblock %} + +{% block page_checkout_main_content %} +
+ {% block page_checkout_pay %} + {% block page_checkout_confirm_header %} +

+ {{ "vrpayment.payHeader"|trans|sw_sanitize }} +

+ {% endblock %} + + {# TODO: move this into a separate file #} + {% block page_checkout_confirm_address %} +
+ {% block page_checkout_confirm_address_shipping %} + {% if page.cart is defined %} + {% set lineItems = page.cart.lineItems %} + {% endif %} + {% if page.order is defined %} + {% set lineItems = page.order.lineItems %} + {% endif %} + {% if not page.isHideShippingAddress() %} +
+
+
+ {% block page_checkout_confirm_address_shipping_title %} +
+ {{ "checkout.shippingAddressHeader"|trans|sw_sanitize }} +
+ {% endblock %} + + {% block page_checkout_confirm_address_shipping_data %} +
+ {% sw_include '@Storefront/storefront/component/address/address.html.twig' with { + 'address': context.customer.defaultShippingAddress + } %} +
+ {% endblock %} + + {% block page_checkout_confirm_address_shipping_actions %} +
+ {% set addressEditorOptions = { + changeShipping: true, + addressId: context.customer.defaultShippingAddressId, + } %} +
+ {% endblock %} +
+
+
+ {% endif %} + {% endblock %} + + {% block page_checkout_confirm_address_billing %} +
+
+
+ {% block page_checkout_confirm_address_billing_title %} +
+ {{ "checkout.billingAddressHeader"|trans|sw_sanitize }} +
+ {% endblock %} + + {% block page_checkout_confirm_address_billing_data %} +
+ {% set shippingAddress = context.customer.activeShippingAddress %} + {% set billingAddress = context.customer.activeBillingAddress %} + {% if shippingAddress.id is defined and shippingAddress.id is same as(billingAddress.id) %} + {% block page_checkout_confirm_address_billing_data_equal %} +

+ {{ "checkout.addressEqualText"|trans|sw_sanitize }} +

+ {% endblock %} + {% else %} + {% sw_include '@Storefront/storefront/component/address/address.html.twig' with { + 'address': context.customer.defaultBillingAddress + } %} + {% endif %} +
+ {% endblock %} + + {% block page_checkout_confirm_address_billing_actions %} +
+ {% set addressEditorOptions = { + changeBilling: true, + addressId: context.customer.defaultBillingAddressId, + } %} +
+ {% endblock %} +
+
+
+ {% endblock %} +
+ {% endblock %} + + {% block page_checkout_pay_order_form %} +
+
+ {% sw_include '@VRPaymentPayment/storefront/page/checkout/order/vrpayment_payment.html.twig' %} +
+
+ {% endblock %} + + {% block page_checkout_pay_product_table %} +
+
+ {% block page_checkout_pay_table_header %} + {% sw_include '@Storefront/storefront/component/checkout/cart-header.html.twig' %} + {% endblock %} + + {% block page_checkout_pay_items %} + {% for lineItem in page.order.nestedLineItems %} + {% block page_checkout_pay_item %} + {% sw_include '@Storefront/storefront/component/line-item/line-item.html.twig' %} + {% endblock %} + {% endfor %} + {% endblock %} +
+
+ {% endblock %} + {% endblock %} +{% endblock %} + +{% block page_checkout_aside_actions %} +
+
+ + + +
+
+{% endblock %} + +{% block base_body_script %} + {{ parent() }} + {% if page.extensions.vRPaymentData %} + {% if page.extensions.vRPaymentData.deviceJavascriptUrl %} + + {% endif %} + {% if page.extensions.vRPaymentData.javascriptUrl %} + + {% endif %} + + {% endif %} +{% endblock %} diff --git a/src/Resources/views/storefront/page/checkout/order/vrpayment_payment.html.twig b/src/Resources/views/storefront/page/checkout/order/vrpayment_payment.html.twig new file mode 100644 index 0000000..820ae9f --- /dev/null +++ b/src/Resources/views/storefront/page/checkout/order/vrpayment_payment.html.twig @@ -0,0 +1,24 @@ +{% set vRPaymentData = page.extensions.vRPaymentData %} +{% if vRPaymentData and vRPaymentData.integration %} + {% if vRPaymentData.integration in ['iframe'] %} + {% for transactionPossiblePaymentMethod in vRPaymentData.transactionPossiblePaymentMethods %} +
+
+
+ {{ transactionPossiblePaymentMethod.getName() }} +
+
+
+ +
+
+
+
+ {% endfor %} + {% endif %} +{% endif %} \ No newline at end of file diff --git a/src/VRPaymentPayment.php b/src/VRPaymentPayment.php new file mode 100644 index 0000000..a6650cb --- /dev/null +++ b/src/VRPaymentPayment.php @@ -0,0 +1,140 @@ +disablePaymentMethods($uninstallContext->getContext()); + $this->removeConfiguration($uninstallContext->getContext()); + $this->deleteUserData($uninstallContext); + } + + /** + * @param \Shopware\Core\Framework\Plugin\Context\ActivateContext $activateContext + * @return void + */ + public function activate(ActivateContext $activateContext): void + { + parent::activate($activateContext); + $this->enablePaymentMethods($activateContext->getContext()); + } + + /** + * @param \Shopware\Core\Framework\Plugin\Context\DeactivateContext $deactivateContext + * @return void + */ + public function deactivate(DeactivateContext $deactivateContext): void + { + parent::deactivate($deactivateContext); + $this->disablePaymentMethods($deactivateContext->getContext()); + } + + public function build(ContainerBuilder $container): void + { + parent::build($container); + + $confDir = \rtrim($this->getPath(), '/') . '/Resources/config'; + $locator = new FileLocator($confDir); + + $resolver = new LoaderResolver([ + new YamlFileLoader($container, $locator), + new XmlFileLoader($container, $locator), + new GlobFileLoader($container, $locator), + new DirectoryLoader($container, $locator), + ]); + + $configLoader = new DelegatingLoader($resolver); + + $configLoader->load($confDir . '/{packages}/*.yaml', 'glob'); + + $configLoader->load('services/core/checkout.xml'); + } + + public function enrichPrivileges(): array + { + return [ + 'sales_channel.viewer' => [ + self::VRPAYMENT_SALES_CHANNEL_PRIVILEGE_READ, + self::VRPAYMENT_SALES_CHANNEL_PRIVILEGE_RUN_READ, + self::VRPAYMENT_SALES_CHANNEL_PRIVILEGE_RUN_UPDATE, + self::VRPAYMENT_SALES_CHANNEL_PRIVILEGE_RUN_CREATE, + self::VRPAYMENT_SALES_CHANNEL_PRIVILEGE_RUN_LOG_READ, + 'sales_channel_payment_method:read', + ], + 'sales_channel.editor' => [ + self::VRPAYMENT_SALES_CHANNEL_PRIVILEGE_UPDATE, + self::VRPAYMENT_SALES_CHANNEL_PRIVILEGE_RUN_DELETE, + 'payment_method:update', + ], + 'sales_channel.creator' => [ + self::VRPAYMENT_SALES_CHANNEL_PRIVILEGE_CREATE, + 'payment_method:create', + 'shipping_method:create', + 'delivery_time:create', + ], + 'sales_channel.deleter' => [ + self::VRPAYMENT_SALES_CHANNEL_PRIVILEGE_DELETE, + ], + ]; + } + + /** + * {@inheritdoc} + */ + public function executeComposerCommands(): bool + { + // The plugin needs the SDK to be installed via composer. + return true; + } +}