From a285fa5b2c098e4f306397be0c710c70846963b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Ka=C5=82u=C5=BCny?= <85225665+bartoszkaluzny-solteq@users.noreply.github.com> Date: Mon, 27 Jan 2025 13:10:57 +0100 Subject: [PATCH 001/136] Initial commit --- LICENSE | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..299e946c --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 Solteq + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. From f960173ebc218d9fcd3bc2ed43f853e4a49691d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Ka=C5=82u=C5=BCny?= Date: Mon, 27 Jan 2025 13:18:47 +0100 Subject: [PATCH 002/136] SQNETS-32: create Adobe Commerce module --- README.md | 2 ++ composer.json | 65 ++++++++++++++++++++++++++++++++++++++++ etc/adminhtml/system.xml | 41 +++++++++++++++++++++++++ etc/module.xml | 14 +++++++++ registration.php | 6 ++++ 5 files changed, 128 insertions(+) create mode 100644 README.md create mode 100644 composer.json create mode 100644 etc/adminhtml/system.xml create mode 100755 etc/module.xml create mode 100755 registration.php diff --git a/README.md b/README.md new file mode 100644 index 00000000..69476724 --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +# magento-module-nexi-checkout +module draft diff --git a/composer.json b/composer.json new file mode 100644 index 00000000..b01a91a1 --- /dev/null +++ b/composer.json @@ -0,0 +1,65 @@ +{ + "name": "nexi-checkout/magento-nexi-checkout", + "description": "Nets Easy Checkout", + "authors": [ + { + "name": "Solteq Oyj", + "email": "magentoservices@solteq.com", + "homepage": "https://github.com/Solteq", + "role": "Developer" + }, + { + "name": "Konrad Konieczny", + "email": "konrad.konieczny@solteq.com", + "homepage": "https://github.com/bartoszkaluzny-solteq", + "role": "Lead Developer" + }, + { + "name": "Bartosz Kałużny", + "email": "bartosz.kaluzny@solteq.com", + "homepage": "https://github.com/bartoszkaluzny-solteq", + "role": "Developer" + } + ], + "require": { + "php": "^8.0", + "nexi-checkout/php-payment-sdk": "*", + "magento/framework": ">=102.0.0", + "ext-curl": "*", + "nesbot/carbon": "^2.57.0" + }, + "require-dev": { + "magento/magento-coding-standard": "*" + }, + "type": "magento2-module", + "version": "0.0.1", + "license": "MIT", + "autoload": { + "files": [ + "registration.php" + ], + "psr-4": { + "NexiCheckout\\MagentoNexiCheckout\\": "" + } + }, + "repositories": [ + { + "type": "composer", + "url": "https://repo.magento.com/" + } + ], + "keywords": [ + "magento 2", + "adobe commerce", + "payment gateway", + "nexi" + ], + "config": { + "allow-plugins": { + "magento/composer-dependency-version-audit-plugin": false, + "magento/magento-composer-installer": false, + "magento/inventory-composer-installer": false, + "dealerdirect/phpcodesniffer-composer-installer": true + } + } +} diff --git a/etc/adminhtml/system.xml b/etc/adminhtml/system.xml new file mode 100644 index 00000000..c56b8fd8 --- /dev/null +++ b/etc/adminhtml/system.xml @@ -0,0 +1,41 @@ + + + +
+ + + + + Magento\Config\Model\Config\Source\Yesno + + + + + + + + + + Magento\Config\Model\Config\Backend\Encrypted + + + + Activating subscriptions feature + Magento\Config\Model\Config\Source\Yesno + + + + + +
+
+
diff --git a/etc/module.xml b/etc/module.xml new file mode 100755 index 00000000..f310dbdc --- /dev/null +++ b/etc/module.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + diff --git a/registration.php b/registration.php new file mode 100755 index 00000000..5a81717b --- /dev/null +++ b/registration.php @@ -0,0 +1,6 @@ + Date: Mon, 27 Jan 2025 13:36:52 +0100 Subject: [PATCH 003/136] SQNETS-32: update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 69476724..bfaf0f76 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,2 @@ -# magento-module-nexi-checkout -module draft +# magento-nexi-checkout +Nexi payment module for Adobe Commerce (Magento 2) From 577589dc6770d6245850db0067e98f9c76e6ea39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Ka=C5=82u=C5=BCny?= Date: Mon, 27 Jan 2025 13:59:43 +0100 Subject: [PATCH 004/136] SQNETS-32: initial module commit --- Gateway/Config/Config.php | 27 +++++++ Gateway/Http/TransferFactory.php | 35 ++++++++ .../Request/CreatePaymentRequestBuilder.php | 13 +++ Gateway/Response/Handler.php | 14 ++++ Model/Config/Source/Environment.php | 21 +++++ Model/Ui/ConfigProvider.php | 36 +++++++++ composer.json | 2 +- etc/adminhtml/system.xml | 37 +++------ etc/config.xml | 38 +++++++++ etc/di.xml | 80 +++++++++++++++++++ etc/frontend/di.xml | 14 ++++ etc/module.xml | 5 +- etc/payment.xml | 13 +++ registration.php | 6 -- view/frontend/layout/checkout_index_index.xml | 43 ++++++++++ .../payment/method-renderer/nexi-method.js | 53 ++++++++++++ .../web/js/view/payment/nexi-payment.js | 21 +++++ view/frontend/web/template/payment/nexi.html | 34 ++++++++ 18 files changed, 455 insertions(+), 37 deletions(-) create mode 100644 Gateway/Config/Config.php create mode 100644 Gateway/Http/TransferFactory.php create mode 100644 Gateway/Request/CreatePaymentRequestBuilder.php create mode 100644 Gateway/Response/Handler.php create mode 100644 Model/Config/Source/Environment.php create mode 100644 Model/Ui/ConfigProvider.php create mode 100644 etc/config.xml create mode 100644 etc/di.xml create mode 100644 etc/frontend/di.xml mode change 100755 => 100644 etc/module.xml create mode 100755 etc/payment.xml delete mode 100755 registration.php create mode 100644 view/frontend/layout/checkout_index_index.xml create mode 100755 view/frontend/web/js/view/payment/method-renderer/nexi-method.js create mode 100755 view/frontend/web/js/view/payment/nexi-payment.js create mode 100644 view/frontend/web/template/payment/nexi.html diff --git a/Gateway/Config/Config.php b/Gateway/Config/Config.php new file mode 100644 index 00000000..3b4bfb06 --- /dev/null +++ b/Gateway/Config/Config.php @@ -0,0 +1,27 @@ +getValue(self::KEY_ENVIRONMENT); + } + + public function isActive(): bool + { + return (bool) $this->getValue(self::KEY_ACTIVE); + } + + public function getClientToken() + { + return $this->getValue('client_token'); + } +} diff --git a/Gateway/Http/TransferFactory.php b/Gateway/Http/TransferFactory.php new file mode 100644 index 00000000..705ae8b4 --- /dev/null +++ b/Gateway/Http/TransferFactory.php @@ -0,0 +1,35 @@ +transferBuilder + ->setBody($request) + ->build(); + } +} diff --git a/Gateway/Request/CreatePaymentRequestBuilder.php b/Gateway/Request/CreatePaymentRequestBuilder.php new file mode 100644 index 00000000..236d08c7 --- /dev/null +++ b/Gateway/Request/CreatePaymentRequestBuilder.php @@ -0,0 +1,13 @@ + 'test', 'label' => __('Test')], + ['value' => 'live', 'label' => __('Live')] + ]; + } +} diff --git a/Model/Ui/ConfigProvider.php b/Model/Ui/ConfigProvider.php new file mode 100644 index 00000000..df192b61 --- /dev/null +++ b/Model/Ui/ConfigProvider.php @@ -0,0 +1,36 @@ +config->isActive()) { + return []; + } + + return [ + 'payment' => [ + self::CODE => [ + 'isActive' => $this->config->isActive(), + 'clientToken' => $this->config->getClientToken(), + 'environment' => $this->config->getEnvironment(), + 'label' => $this->paymentHelper->getMethodInstance(self::CODE)->getTitle(), + ] + ] + ]; + } +} diff --git a/composer.json b/composer.json index b01a91a1..d16846ec 100644 --- a/composer.json +++ b/composer.json @@ -1,5 +1,5 @@ { - "name": "nexi-checkout/magento-nexi-checkout", + "name": "nexi/module-checkout", "description": "Nets Easy Checkout", "authors": [ { diff --git a/etc/adminhtml/system.xml b/etc/adminhtml/system.xml index c56b8fd8..dc47dcee 100644 --- a/etc/adminhtml/system.xml +++ b/etc/adminhtml/system.xml @@ -2,38 +2,23 @@ -
- - - +
+ + + Magento\Config\Model\Config\Source\Yesno - + - - + + + Magento\Sales\Model\Config\Source\Order\Status\NewStatus - - - Magento\Config\Model\Config\Backend\Encrypted - - - - Activating subscriptions feature - Magento\Config\Model\Config\Source\Yesno - - - + + + Nexi\Checkout\Model\Config\Source\Environment
diff --git a/etc/config.xml b/etc/config.xml new file mode 100644 index 00000000..244a95b2 --- /dev/null +++ b/etc/config.xml @@ -0,0 +1,38 @@ + + + + + NexiFacade + Nexi Payments + authorize_capture + 0 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + processing + test + 0 + + + + + cvv,number + avsPostalCodeResponseCode,avsStreetAddressResponseCode,cvvResponseCode,processorAuthorizationCode,processorResponseCode,processorResponseText,liabilityShifted,liabilityShiftPossible,riskDataId,riskDataDecision + cc_type,cc_number,avsPostalCodeResponseCode,avsStreetAddressResponseCode,cvvResponseCode,processorAuthorizationCode,processorResponseCode,processorResponseText,liabilityShifted,liabilityShiftPossible + nexi_group + + + + diff --git a/etc/di.xml b/etc/di.xml new file mode 100644 index 00000000..3231e734 --- /dev/null +++ b/etc/di.xml @@ -0,0 +1,80 @@ + + + + + + Nexi\Checkout\Model\Ui\ConfigProvider::CODE + Magento\Payment\Block\Form + Magento\Payment\Block\Form + NexiValueHandlerPool + Magento\Payment\Gateway\Validator\ValidatorPool + NexiCommandPool + + + + + + + NexiConfigValueHandler + + + + + + + NexiConfig + + + + + + Nexi\Checkout\Model\Ui\ConfigProvider::CODE + + + + + + NexiConfig + + + + + + + NexiCommandCreatePayment + + + + + + + + + + + + + + + + + NexiRequestBuilder + Nexi\Checkout\Gateway\Http\TransferFactory + Magento\Payment\Gateway\Http\ClientInterface + + + + + + NexiConfig + + + + + + NexiConfig + + + + diff --git a/etc/frontend/di.xml b/etc/frontend/di.xml new file mode 100644 index 00000000..c1d044c3 --- /dev/null +++ b/etc/frontend/di.xml @@ -0,0 +1,14 @@ + + + + + + + Nexi\Checkout\Model\Ui\ConfigProvider + + + + + diff --git a/etc/module.xml b/etc/module.xml old mode 100755 new mode 100644 index f310dbdc..26c312d5 --- a/etc/module.xml +++ b/etc/module.xml @@ -1,14 +1,11 @@ - + - - - diff --git a/etc/payment.xml b/etc/payment.xml new file mode 100755 index 00000000..f56d404b --- /dev/null +++ b/etc/payment.xml @@ -0,0 +1,13 @@ + + + + + + + + + + 1 + + + diff --git a/registration.php b/registration.php deleted file mode 100755 index 5a81717b..00000000 --- a/registration.php +++ /dev/null @@ -1,6 +0,0 @@ - + + + + + + + + + + + + + + + + + + + + Nexi_Checkout/js/view/payment/nexi-payment + + + false + + + + + + + + + + + + + + + + + + + diff --git a/view/frontend/web/js/view/payment/method-renderer/nexi-method.js b/view/frontend/web/js/view/payment/method-renderer/nexi-method.js new file mode 100755 index 00000000..fcfc5071 --- /dev/null +++ b/view/frontend/web/js/view/payment/method-renderer/nexi-method.js @@ -0,0 +1,53 @@ +/*browser:true*/ +/*global define*/ +define( + [ + 'ko', + 'jquery', + 'underscore', + 'mage/storage', + 'Magento_Checkout/js/view/payment/default', + 'Magento_Checkout/js/action/place-order', + 'Magento_Checkout/js/action/select-payment-method', + 'Magento_Checkout/js/model/payment/additional-validators', + 'Magento_Checkout/js/model/quote', + 'Magento_Checkout/js/action/get-totals', + 'Magento_Checkout/js/model/url-builder', + 'mage/url', + 'Magento_Checkout/js/model/full-screen-loader', + 'Magento_Customer/js/model/customer', + 'Magento_Checkout/js/checkout-data', + 'Magento_Checkout/js/model/totals', + 'Magento_Ui/js/model/messageList', + 'mage/translate', + 'Magento_Ui/js/modal/modal' + ], + function (ko, $, _, storage, Component) { + 'use strict'; + + return Component.extend({ + defaults: { + template: 'Nexi_Checkout/payment/nexi' + }, + /** + * Returns send check to info. + * + * @return {*} + */ + getMailingAddress: function () { + return window.checkoutConfig.payment.checkmo.mailingAddress; + }, + + /** + * Returns payable to info. + * + * @return {*} + */ + getPayableTo: function () { + return window.checkoutConfig.payment.checkmo.payableTo; + } + }); + + + } +); diff --git a/view/frontend/web/js/view/payment/nexi-payment.js b/view/frontend/web/js/view/payment/nexi-payment.js new file mode 100755 index 00000000..a462d1b0 --- /dev/null +++ b/view/frontend/web/js/view/payment/nexi-payment.js @@ -0,0 +1,21 @@ +/*browser:true*/ +/*global define*/ +define( + [ + 'uiComponent', + 'Magento_Checkout/js/model/payment/renderer-list' + ], + function ( + Component, + rendererList + ) { + 'use strict'; + rendererList.push( + { + type: 'nexi', + component: 'Nexi_Checkout/js/view/payment/method-renderer/nexi-method' + } + ); + return Component.extend({}); + } +); diff --git a/view/frontend/web/template/payment/nexi.html b/view/frontend/web/template/payment/nexi.html new file mode 100644 index 00000000..b20743a3 --- /dev/null +++ b/view/frontend/web/template/payment/nexi.html @@ -0,0 +1,34 @@ +
+
+ + +
+
+ + + +
+ + + +
+
+
+ +
+
+
+
From b5db912816c3ce458dd69304c176c4049094220a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Ka=C5=82u=C5=BCny?= Date: Mon, 27 Jan 2025 14:05:58 +0100 Subject: [PATCH 005/136] SQNETS-32: add registration.php --- registration.php | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 registration.php diff --git a/registration.php b/registration.php new file mode 100644 index 00000000..83008037 --- /dev/null +++ b/registration.php @@ -0,0 +1,5 @@ + Date: Mon, 27 Jan 2025 18:47:36 +0100 Subject: [PATCH 006/136] SQNETS-40: Fix config virtual type --- etc/di.xml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/etc/di.xml b/etc/di.xml index 3231e734..ce2b76b6 100644 --- a/etc/di.xml +++ b/etc/di.xml @@ -27,7 +27,7 @@ - + Nexi\Checkout\Model\Ui\ConfigProvider::CODE @@ -76,5 +76,4 @@ NexiConfig - From 7894b269f2c5b57260ae489fa94f18a09e891426 Mon Sep 17 00:00:00 2001 From: "konrad.konieczny" Date: Thu, 30 Jan 2025 10:55:30 +0100 Subject: [PATCH 007/136] SQNETS-40: remove unused methods --- .../payment/method-renderer/nexi-method.js | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/view/frontend/web/js/view/payment/method-renderer/nexi-method.js b/view/frontend/web/js/view/payment/method-renderer/nexi-method.js index fcfc5071..8043d702 100755 --- a/view/frontend/web/js/view/payment/method-renderer/nexi-method.js +++ b/view/frontend/web/js/view/payment/method-renderer/nexi-method.js @@ -28,26 +28,7 @@ define( return Component.extend({ defaults: { template: 'Nexi_Checkout/payment/nexi' - }, - /** - * Returns send check to info. - * - * @return {*} - */ - getMailingAddress: function () { - return window.checkoutConfig.payment.checkmo.mailingAddress; - }, - - /** - * Returns payable to info. - * - * @return {*} - */ - getPayableTo: function () { - return window.checkoutConfig.payment.checkmo.payableTo; } }); - - } ); From 8dbc2df1decdbbe04c59ce8ec429bc930bed6d7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Ka=C5=82u=C5=BCny?= Date: Thu, 30 Jan 2025 17:17:05 +0100 Subject: [PATCH 008/136] SQNETS-32: improve admin configuration --- .../System/Config/TestConnection.php | 64 +++++++++++++++++++ Gateway/Config/Config.php | 23 ++++--- etc/adminhtml/system.xml | 52 ++++++++++++++- .../system/config/testconnection.phtml | 14 ++++ 4 files changed, 142 insertions(+), 11 deletions(-) create mode 100644 Block/Adminhtml/System/Config/TestConnection.php create mode 100644 view/adminhtml/templates/system/config/testconnection.phtml diff --git a/Block/Adminhtml/System/Config/TestConnection.php b/Block/Adminhtml/System/Config/TestConnection.php new file mode 100644 index 00000000..9c18cb21 --- /dev/null +++ b/Block/Adminhtml/System/Config/TestConnection.php @@ -0,0 +1,64 @@ +setTemplate('Nexi_Checkout::system/config/testconnection.phtml'); + return $this; + } + + protected function _getElementHtml(\Magento\Framework\Data\Form\Element\AbstractElement $element) + { + // TODO: add Nexi URL + $originalData = $element->getOriginalData(); + $this->addData( + [ + 'button_label' => __($originalData['button_label']), + 'html_id' => $element->getHtmlId(), + 'ajax_url' => $this->_urlBuilder->getUrl('nexi-api-url'), + 'field_mapping' => str_replace('"', '\\"', json_encode($this->_getFieldMapping())) + ] + ); + + return $this->_toHtml(); + } + + /** + * @return string[] + */ + protected function _getFieldMapping(): array + { + return $fields = [ + 'api_key' => $this->gatewayConfig->getApiKey(), + 'api_identifier' => $this->gatewayConfig->getApiIdentifier(), + ]; + } +} diff --git a/Gateway/Config/Config.php b/Gateway/Config/Config.php index 3b4bfb06..e80f88af 100644 --- a/Gateway/Config/Config.php +++ b/Gateway/Config/Config.php @@ -5,23 +5,28 @@ class Config extends \Magento\Payment\Gateway\Config\Config { public const CODE = 'nexi'; - - public const KEY_ENVIRONMENT = 'environment'; public const KEY_ACTIVE = 'active'; - public const KEY_CLIENT_TOKEN = 'client_token'; + public const API_KEY = 'api_key'; + public const API_IDENTIFIER = 'api_identifier'; + public const KEY_ENVIRONMENT = 'environment'; - public function getEnvironment(): string + public function isActive(): bool { - return $this->getValue(self::KEY_ENVIRONMENT); + return (bool) $this->getValue(self::KEY_ACTIVE); } - public function isActive(): bool + public function getApiKey() { - return (bool) $this->getValue(self::KEY_ACTIVE); + return $this->getValue(self::API_KEY); } - public function getClientToken() + public function getApiIdentifier() { - return $this->getValue('client_token'); + return $this->getValue(self::API_IDENTIFIER); + } + + public function getEnvironment(): string + { + return $this->getValue(self::KEY_ENVIRONMENT); } } diff --git a/etc/adminhtml/system.xml b/etc/adminhtml/system.xml index dc47dcee..664b418a 100644 --- a/etc/adminhtml/system.xml +++ b/etc/adminhtml/system.xml @@ -12,14 +12,62 @@ - + + + Magento\Config\Model\Config\Backend\Encrypted + + 1 + + + + + Magento\Config\Model\Config\Backend\Encrypted + + 1 + + + + + + + add URL to your Webshop Terms and Conditions site. + + + + add URL to your payment Terms and Conditions site. + + + + Magento\Config\Model\Config\Source\Yesno + + Magento\Sales\Model\Config\Source\Order\Status\NewStatus - + Nexi\Checkout\Model\Config\Source\Environment + + + Log file: var/log/nexi_checkout_request.log + Magento\Config\Model\Config\Source\Yesno + + + + Log file: var/log/nexi_checkout_response.log + Magento\Config\Model\Config\Source\Yesno +
diff --git a/view/adminhtml/templates/system/config/testconnection.phtml b/view/adminhtml/templates/system/config/testconnection.phtml new file mode 100644 index 00000000..a583b2d5 --- /dev/null +++ b/view/adminhtml/templates/system/config/testconnection.phtml @@ -0,0 +1,14 @@ + + From 033ab52614c0dacf61bb9795b67c7e9e800bf297 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Ka=C5=82u=C5=BCny?= Date: Thu, 30 Jan 2025 17:34:29 +0100 Subject: [PATCH 009/136] SQNETS-32: improve constructor --- Block/Adminhtml/System/Config/TestConnection.php | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/Block/Adminhtml/System/Config/TestConnection.php b/Block/Adminhtml/System/Config/TestConnection.php index 9c18cb21..6eae6c87 100644 --- a/Block/Adminhtml/System/Config/TestConnection.php +++ b/Block/Adminhtml/System/Config/TestConnection.php @@ -7,6 +7,8 @@ namespace Nexi\Checkout\Block\Adminhtml\System\Config; +use Magento\Backend\Block\Template\Context; +use Magento\Framework\View\Helper\SecureHtmlRenderer; use Nexi\Checkout\Gateway\Config\Config; /** @@ -17,12 +19,18 @@ class TestConnection extends \Magento\Config\Block\System\Config\Form\Field /** * TestConnection constructor. * + * @param Context $context + * @param array $data + * @param SecureHtmlRenderer|null $secureRenderer * @param Config $gatewayConfig */ public function __construct( - private Config $gatewayConfig - ) - { + Context $context, + array $data = [], + ?SecureHtmlRenderer $secureRenderer = null, + private Config $gatewayConfig + ) { + parent::__construct($context, $data, $secureRenderer); } /** From d789bd75b43366c755b6095fcb9d8f9a4e4af536 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Ka=C5=82u=C5=BCny?= Date: Thu, 30 Jan 2025 17:43:17 +0100 Subject: [PATCH 010/136] SQNETS: add code-validation.yml --- .github/workflows/code-validation.yml | 84 +++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 .github/workflows/code-validation.yml diff --git a/.github/workflows/code-validation.yml b/.github/workflows/code-validation.yml new file mode 100644 index 00000000..5e77567e --- /dev/null +++ b/.github/workflows/code-validation.yml @@ -0,0 +1,84 @@ +name: Run code validation checks +on: + pull_request: +jobs: + changed-files: + runs-on: ubuntu-latest + name: Gather Changelist + strategy: + matrix: + fetch-depth: + - 2 + base-branch: + - "master" + outputs: + all: ${{ steps.changes.outputs.all }} + php: ${{ steps.changes.outputs.php }} + steps: + - name: Check out code + uses: actions/checkout@v4 + with: + fetch-depth: ${{ matrix.fetch-depth }} + - name: Get changed files + id: changes + run: | + BASE_SHA="${{ github.event.before }}" + if [ "$BASE_SHA" = "0000000000000000000000000000000000000000" ]; then + BASE_SHA="${{ matrix.base-branch }}" + fi + if [[ ! -z "${{ github.event.pull_request.base.sha }}" ]]; then + BASE_SHA="${{ github.event.pull_request.base.sha }}" + fi + echo "all=$(git diff --name-only --diff-filter=ACMRT $BASE_SHA ${{ github.sha }} | xargs)" >> $GITHUB_OUTPUT + echo "php=$(git diff --name-only --diff-filter=ACMRT $BASE_SHA ${{ github.sha }} | grep -E '.ph(p|tml)$' | xargs)" >> $GITHUB_OUTPUT + validate-php: + runs-on: ubuntu-latest + name: Run php code validation and Unit tests + needs: changed-files + if: ${{needs.changed-files.outputs.php}} + strategy: + matrix: + node-version: + - 20 + php-version: ["8.1", "8.2", "8.3"] + dependencies: + - "highest" + composer-options: + - "--no-plugins --no-progress" + steps: + - name: Setup node + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node_version }} + - name: Check out code + uses: actions/checkout@v4 + - name: Setup Php + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php-version }} + ini-file: development + tools: composer:2.2, cs2pr + env: + COMPOSER_AUTH_JSON: | + { + "http-basic": { + "repo.magento.com": { + "username": "${{ secrets.REPO_MAGENTO_USER }}", + "password": "${{ secrets.REPO_MAGENTO_PASSWORD }}" + } + } + } + - name: Validate Composer Files + run: composer validate + - name: Run Composer install + uses: "ramsey/composer-install@v1" + with: + dependency-versions: "${{ matrix.dependencies }}" + composer-options: "${{ matrix.composer-options }}" + - name: Detect PhpCs violations + run: | + vendor/bin/phpcs --config-set installed_paths ../../magento/magento-coding-standard/,../../phpcompatibility/php-compatibility,../../magento/php-compatibility-fork + vendor/bin/phpcs --standard=Magento2 -q --report=checkstyle ${{needs.changed-files.outputs.php}} | cs2pr --graceful-warnings + - name: Run Unit test + run: | + vendor/bin/phpunit Test/Unit/Model/ From 311b960fede7f0674988f0337a5ada5069fe8e26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Ka=C5=82u=C5=BCny?= Date: Thu, 30 Jan 2025 17:44:23 +0100 Subject: [PATCH 011/136] SQNETS-32: simplify FQN --- Block/Adminhtml/System/Config/TestConnection.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Block/Adminhtml/System/Config/TestConnection.php b/Block/Adminhtml/System/Config/TestConnection.php index 6eae6c87..07f6e48c 100644 --- a/Block/Adminhtml/System/Config/TestConnection.php +++ b/Block/Adminhtml/System/Config/TestConnection.php @@ -8,13 +8,15 @@ namespace Nexi\Checkout\Block\Adminhtml\System\Config; use Magento\Backend\Block\Template\Context; +use Magento\Config\Block\System\Config\Form\Field; +use Magento\Framework\Data\Form\Element\AbstractElement; use Magento\Framework\View\Helper\SecureHtmlRenderer; use Nexi\Checkout\Gateway\Config\Config; /** * Nexi API test connection block */ -class TestConnection extends \Magento\Config\Block\System\Config\Form\Field +class TestConnection extends Field { /** * TestConnection constructor. @@ -43,7 +45,7 @@ protected function _prepareLayout() return $this; } - protected function _getElementHtml(\Magento\Framework\Data\Form\Element\AbstractElement $element) + protected function _getElementHtml(AbstractElement $element) { // TODO: add Nexi URL $originalData = $element->getOriginalData(); From 1ed1eb7468fb586dccc41fe7d796ad42d49c1978 Mon Sep 17 00:00:00 2001 From: "konrad.konieczny" Date: Thu, 30 Jan 2025 21:43:03 +0100 Subject: [PATCH 012/136] SQNETS-32/test-button: add test button --- .../System/Config/TestConnection.php | 60 +++++++------- .../System/Config/TestConnection.php | 79 +++++++++++++++++++ Gateway/Config/Config.php | 38 ++++++--- Model/Config/Source/Environment.php | 7 +- Model/Config/Source/IntegrationType.php | 21 +++++ etc/adminhtml/routes.xml | 14 ++++ etc/adminhtml/system.xml | 21 ++--- etc/di.xml | 14 ++++ view/adminhtml/requirejs-config.js | 12 +++ .../system/config/testconnection.phtml | 5 +- view/adminhtml/web/js/testnexiconnection.js | 74 +++++++++++++++++ 11 files changed, 288 insertions(+), 57 deletions(-) create mode 100644 Controller/Adminhtml/System/Config/TestConnection.php create mode 100644 Model/Config/Source/IntegrationType.php create mode 100644 etc/adminhtml/routes.xml create mode 100644 view/adminhtml/requirejs-config.js create mode 100644 view/adminhtml/web/js/testnexiconnection.js diff --git a/Block/Adminhtml/System/Config/TestConnection.php b/Block/Adminhtml/System/Config/TestConnection.php index 07f6e48c..e927b063 100644 --- a/Block/Adminhtml/System/Config/TestConnection.php +++ b/Block/Adminhtml/System/Config/TestConnection.php @@ -1,42 +1,33 @@ unsScope()->unsCanUseWebsiteValue()->unsCanUseDefaultValue(); + return parent::render($element); } /** - * @return $this|TestConnection + * Set template to itself + * + * @return $this + * @since 100.1.0 */ protected function _prepareLayout() { @@ -45,16 +36,21 @@ protected function _prepareLayout() return $this; } + /** + * @param AbstractElement $element + * + * @return string + */ protected function _getElementHtml(AbstractElement $element) { - // TODO: add Nexi URL $originalData = $element->getOriginalData(); $this->addData( [ - 'button_label' => __($originalData['button_label']), - 'html_id' => $element->getHtmlId(), - 'ajax_url' => $this->_urlBuilder->getUrl('nexi-api-url'), + 'button_label' => __($originalData['button_label']), + 'html_id' => $element->getHtmlId(), + 'ajax_url' => $this->_urlBuilder->getUrl('nexi/system_config/testconnection'), 'field_mapping' => str_replace('"', '\\"', json_encode($this->_getFieldMapping())) + ] ); @@ -62,13 +58,15 @@ protected function _getElementHtml(AbstractElement $element) } /** + * Get configuration field mapping + * * @return string[] */ protected function _getFieldMapping(): array { - return $fields = [ - 'api_key' => $this->gatewayConfig->getApiKey(), - 'api_identifier' => $this->gatewayConfig->getApiIdentifier(), + return [ + 'environment' => 'payment_us_nexi_environment', + 'api_key' => 'payment_us_nexi_api_key', ]; } } diff --git a/Controller/Adminhtml/System/Config/TestConnection.php b/Controller/Adminhtml/System/Config/TestConnection.php new file mode 100644 index 00000000..370a2fb3 --- /dev/null +++ b/Controller/Adminhtml/System/Config/TestConnection.php @@ -0,0 +1,79 @@ + false, + 'errorMessage' => '', + ]; + $options = $this->getRequest()->getParams(); + + try { + $api = $this->paymentApiFactory->create( + secretKey : $options['secret_key'] ?? "test", + isLiveMode: $options['environment'] == Environment::LIVE + ); + + $result = $api->retrievePayment( + 'test' + ); + + + if ($result) { + $result['success'] = true; + } + } catch (LocalizedException $e) { + $result['errorMessage'] = $e->getMessage(); + } catch (\Exception $e) { + $message = __($e->getMessage()); + $result['errorMessage'] = $this->tagFilter->filter($message); + } + + /** @var Json $resultJson */ + $resultJson = $this->resultJsonFactory->create(); + return $resultJson->setData($result); + } +} diff --git a/Gateway/Config/Config.php b/Gateway/Config/Config.php index e80f88af..e46362bd 100644 --- a/Gateway/Config/Config.php +++ b/Gateway/Config/Config.php @@ -2,31 +2,45 @@ namespace Nexi\Checkout\Gateway\Config; -class Config extends \Magento\Payment\Gateway\Config\Config +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Payment\Gateway\Config\Config as MagentoConfig; +use Nexi\Checkout\Model\Config\Source\Environment; +use NexiCheckout\Factory\Provider\HttpClientConfigurationProvider; + +class Config extends MagentoConfig { + public function __construct( + ScopeConfigInterface $scopeConfig, + $methodCode = null, + $pathPattern = MagentoConfig::DEFAULT_PATH_PATTERN, + ) { + parent::__construct($scopeConfig, $methodCode, $pathPattern); + } + public const CODE = 'nexi'; - public const KEY_ACTIVE = 'active'; - public const API_KEY = 'api_key'; - public const API_IDENTIFIER = 'api_identifier'; + public const KEY_ENVIRONMENT = 'environment'; + public const KEY_SECRET_KEY = 'secret_key'; + public const KEY_ACTIVE = 'active'; + public const API_IDENTIFIER = 'api_identifier'; - public function isActive(): bool + public function getEnvironment(): string { - return (bool) $this->getValue(self::KEY_ACTIVE); + return $this->getValue(self::KEY_ENVIRONMENT); } - public function getApiKey() + public function isLiveMode(): bool { - return $this->getValue(self::API_KEY); + return $this->getEnvironment() === Environment::LIVE; } - public function getApiIdentifier() + public function isActive(): bool { - return $this->getValue(self::API_IDENTIFIER); + return (bool)$this->getValue(self::KEY_ACTIVE); } - public function getEnvironment(): string + public function getSecretKey(): string { - return $this->getValue(self::KEY_ENVIRONMENT); + return $this->getValue(self::KEY_SECRET_KEY); } } diff --git a/Model/Config/Source/Environment.php b/Model/Config/Source/Environment.php index ccbda95b..f9cfc688 100644 --- a/Model/Config/Source/Environment.php +++ b/Model/Config/Source/Environment.php @@ -6,6 +6,9 @@ class Environment implements OptionSourceInterface { + const TEST = 'test'; + const LIVE = 'live'; + /** * Return array of options as value-label pairs * @@ -14,8 +17,8 @@ class Environment implements OptionSourceInterface public function toOptionArray() { return [ - ['value' => 'test', 'label' => __('Test')], - ['value' => 'live', 'label' => __('Live')] + ['value' => self::TEST, 'label' => __('Test')], + ['value' => self::LIVE, 'label' => __('Live')] ]; } } diff --git a/Model/Config/Source/IntegrationType.php b/Model/Config/Source/IntegrationType.php new file mode 100644 index 00000000..9c0d8371 --- /dev/null +++ b/Model/Config/Source/IntegrationType.php @@ -0,0 +1,21 @@ + 'hosted', 'label' => __('Hosted')], + ['value' => 'embedded', 'label' => __('Embedded')] + ]; + } +} diff --git a/etc/adminhtml/routes.xml b/etc/adminhtml/routes.xml new file mode 100644 index 00000000..7d76a081 --- /dev/null +++ b/etc/adminhtml/routes.xml @@ -0,0 +1,14 @@ + + + + + + + + + diff --git a/etc/adminhtml/system.xml b/etc/adminhtml/system.xml index 664b418a..4abf19ff 100644 --- a/etc/adminhtml/system.xml +++ b/etc/adminhtml/system.xml @@ -9,9 +9,21 @@ Magento\Config\Model\Config\Source\Yesno + + + Nexi\Checkout\Model\Config\Source\IntegrationType + + + + + Magento\Config\Model\Config\Backend\Encrypted @@ -26,15 +38,6 @@ 1 - - add URL to your Webshop Terms and Conditions site. diff --git a/etc/di.xml b/etc/di.xml index ce2b76b6..e87093ad 100644 --- a/etc/di.xml +++ b/etc/di.xml @@ -76,4 +76,18 @@ NexiConfig + + + + GuzzleHttp\Client + GuzzleHttp\Psr7\HttpFactory + GuzzleHttp\Psr7\HttpFactory + + + + + \NexiCheckout\Factory\Provider\HttpClientConfigurationProvider + + +
diff --git a/view/adminhtml/requirejs-config.js b/view/adminhtml/requirejs-config.js new file mode 100644 index 00000000..91ca828d --- /dev/null +++ b/view/adminhtml/requirejs-config.js @@ -0,0 +1,12 @@ +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +var config = { + map: { + '*': { + testNexiConnection: 'Nexi_Checkout/js/testnexiconnection' + } + } +}; diff --git a/view/adminhtml/templates/system/config/testconnection.phtml b/view/adminhtml/templates/system/config/testconnection.phtml index a583b2d5..afd06e0a 100644 --- a/view/adminhtml/templates/system/config/testconnection.phtml +++ b/view/adminhtml/templates/system/config/testconnection.phtml @@ -1,10 +1,9 @@ - diff --git a/view/adminhtml/web/js/testnexiconnection.js b/view/adminhtml/web/js/testnexiconnection.js new file mode 100644 index 00000000..efb60458 --- /dev/null +++ b/view/adminhtml/web/js/testnexiconnection.js @@ -0,0 +1,66 @@ +define([ + 'jquery', + 'Magento_Ui/js/modal/alert', + 'jquery/ui' +], function ($, alert) { + 'use strict'; + + $.widget('mage.testNexiConnection', { + options: { + url: '', + elementId: '', + successText: '', + failedText: '', + fieldMapping: '',}, + + /** + * Bind handlers to events + */ + _create: function () { + this._on({ + 'click': $.proxy(this._connect, this) + }); + }, + + /** + * Method triggers an AJAX request to check search engine connection + * @private + */ + _connect: function () { + var result = this.options.failedText, + element = $('#' + this.options.elementId), + self = this, + params = {}, + msg = '', + fieldToCheck = this.options.fieldToCheck || 'success'; + + element.removeClass('success').addClass('fail'); + $.each(JSON.parse(this.options.fieldMapping), function (key, el) { + params[key] = $('#' + el).val(); + }); + $.ajax({ + url: this.options.url, + showLoader: true, + data: params, + headers: this.options.headers || {} + }).done(function (response) { + if (response[fieldToCheck]) { + element.removeClass('fail').addClass('success'); + result = self.options.successText; + } else { + msg = response.errorMessage; + + if (msg) { + alert({ + content: msg + }); + } + } + }).always(function () { + $('#' + self.options.elementId + '_result').text(result); + }); + } + }); + + return $.mage.testNexiConnection; +}); diff --git a/view/frontend/layout/checkout_index_index.xml b/view/frontend/layout/checkout_index_index.xml new file mode 100644 index 00000000..023f2cf0 --- /dev/null +++ b/view/frontend/layout/checkout_index_index.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + Nexi_Checkout/js/view/payment/nexi-payment + + + false + + + + + + + + + + + + + + + + + + + diff --git a/view/frontend/web/js/view/payment/method-renderer/nexi-method.js b/view/frontend/web/js/view/payment/method-renderer/nexi-method.js new file mode 100755 index 00000000..c43fdadd --- /dev/null +++ b/view/frontend/web/js/view/payment/method-renderer/nexi-method.js @@ -0,0 +1,48 @@ +define( + [ + 'ko', + 'jquery', + 'underscore', + 'mage/storage', + 'Magento_Checkout/js/view/payment/default', + 'Magento_Checkout/js/action/place-order', + 'Magento_Checkout/js/action/select-payment-method', + 'Magento_Checkout/js/model/payment/additional-validators', + 'Magento_Checkout/js/model/quote', + 'Magento_Checkout/js/action/get-totals', + 'Magento_Checkout/js/model/url-builder', + 'mage/url', + 'Magento_Checkout/js/model/full-screen-loader', + 'Magento_Customer/js/model/customer', + 'Magento_Checkout/js/checkout-data', + 'Magento_Checkout/js/model/totals', + 'Magento_Ui/js/model/messageList', + 'mage/translate', + 'Magento_Ui/js/modal/modal' + ], + function (ko, $, _, storage, Component, placeOrderAction, selectPaymentMethodAction, additionalValidators, quote, getTotalsAction, urlBuilder, url, fullScreenLoader, customer, checkoutData, totals, messageList, $t, modal) { + 'use strict'; + + return Component.extend({ + defaults: { + template: 'Nexi_Checkout/payment/nexi', + config: window.checkoutConfig.payment.nexi + }, + placeOrder: function (data, event) { + let placeOrder = placeOrderAction(this.getData(), false, this.messageContainer); + + $.when(placeOrder).done(function (response) { + this.afterPlaceOrder(response); + }.bind(this)); + }, + afterPlaceOrder: function (response) { + if (this.config.integrationType === 'HostedPaymentPage') { + let redirectUrl = JSON.parse(response).redirect_url; + if (redirectUrl) { + window.location.href = redirectUrl; + } + } + } + }); + } +); diff --git a/view/frontend/web/js/view/payment/nexi-payment.js b/view/frontend/web/js/view/payment/nexi-payment.js new file mode 100755 index 00000000..a462d1b0 --- /dev/null +++ b/view/frontend/web/js/view/payment/nexi-payment.js @@ -0,0 +1,21 @@ +/*browser:true*/ +/*global define*/ +define( + [ + 'uiComponent', + 'Magento_Checkout/js/model/payment/renderer-list' + ], + function ( + Component, + rendererList + ) { + 'use strict'; + rendererList.push( + { + type: 'nexi', + component: 'Nexi_Checkout/js/view/payment/method-renderer/nexi-method' + } + ); + return Component.extend({}); + } +); diff --git a/view/frontend/web/template/payment/nexi.html b/view/frontend/web/template/payment/nexi.html new file mode 100644 index 00000000..b20743a3 --- /dev/null +++ b/view/frontend/web/template/payment/nexi.html @@ -0,0 +1,34 @@ +
+
+ + +
+
+ + + +
+ + + +
+
+
+ +
+
+
+
From 93fe2560b4289f8664f96f9b13195133c1ef3c2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Ka=C5=82u=C5=BCny?= Date: Thu, 27 Feb 2025 10:32:30 +0100 Subject: [PATCH 063/136] SQNETS-34: add more webhooks in DI --- etc/di.xml | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/etc/di.xml b/etc/di.xml index 4dc1dfb9..fcc48462 100644 --- a/etc/di.xml +++ b/etc/di.xml @@ -213,10 +213,17 @@ Nexi\Checkout\Model\Webhook\PaymentCheckoutCompleted Nexi\Checkout\Model\Webhook\PaymentChargeCreated - Nexi\Checkout\Model\Webhook\PaymentCreated - Nexi\Checkout\Model\Webhook\PaymentReservationCreated Nexi\Checkout\Model\Webhook\PaymentCheckoutCompleted + Nexi\Checkout\Model\Webhook\PaymentCancelFailed + Nexi\Checkout\Model\Webhook\PaymentCancelCreated Nexi\Checkout\Model\Webhook\PaymentChargeCreated + Nexi\Checkout\Model\Webhook\PaymentChargeFailed + Nexi\Checkout\Model\Webhook\PaymentCreated + Nexi\Checkout\Model\Webhook\PaymentRefundCompleted + Nexi\Checkout\Model\Webhook\PaymentRefundFailed + Nexi\Checkout\Model\Webhook\PaymentRefundInitiated + Nexi\Checkout\Model\Webhook\PaymentReservationCreated + Nexi\Checkout\Model\Webhook\PaymentReservationFailed From a973e0656ebd1118239194e59522c66b5e72c69d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Ka=C5=82u=C5=BCny?= Date: Thu, 27 Feb 2025 10:33:05 +0100 Subject: [PATCH 064/136] SQNETS-34: create service for 'payment.reservation.created.v2' --- Model/Webhook/PaymentReservationCreated.php | 66 ++++++++++++++++++++- 1 file changed, 65 insertions(+), 1 deletion(-) diff --git a/Model/Webhook/PaymentReservationCreated.php b/Model/Webhook/PaymentReservationCreated.php index b2d92ce9..b3671d19 100644 --- a/Model/Webhook/PaymentReservationCreated.php +++ b/Model/Webhook/PaymentReservationCreated.php @@ -5,10 +5,74 @@ namespace Nexi\Checkout\Model\Webhook; +use Magento\Checkout\Exception; +use Magento\Framework\Api\SearchCriteriaBuilder; +use Magento\Sales\Api\OrderRepositoryInterface; +use Magento\Sales\Api\TransactionRepositoryInterface; +use Magento\Sales\Model\Order; + class PaymentReservationCreated { - public function process() + public function __construct( + private TransactionRepositoryInterface $transactionRepository, + private SearchCriteriaBuilder $searchCriteriaBuilder, + private OrderRepositoryInterface $orderRepository + ) { + } + + /** + * ProcessWebhook function for 'payment.reservation.created.v2' event. + * @param $response + * @return void + * @throws Exception + */ + public function processWebhook($response) { + $params = json_decode('{"id":"d60fd4bbaad6454a8c2a4377601c969c","timestamp":"2025-02-24T13:58:29.1396+00:00","merchantNumber":100065206,"event":"payment.reservation.created.v2","data":{"paymentMethod":"Visa","paymentType":"CARD","amount":{"amount":5780,"currency":"EUR"},"paymentId":"f369621ef1b149b5b90b65504506eb75"}}', true); + $transaction = $this->loadTransactionByPaymentId($params['data']['paymentId']); + + $order = $this->orderRepository->get(reset($transaction)->getOrderId()); + $order->getPayment()->setAdditionalInformation('selected_payment_method', $params['data']['paymentMethod']); + + $this->processOrder($order); + $this->orderRepository->save($order); + } + /** + * LoadTransactionByPaymentId function + * + * @param $paymentId + * @return \Magento\Sales\Api\Data\TransactionInterface[] + * @throws Exception + */ + private function loadTransactionByPaymentId($paymentId) + { + try { + $searchCriteria = $this->searchCriteriaBuilder + ->addFilter('txn_id', $paymentId, 'eq') + ->create(); + $transaction = $this->transactionRepository->getList($searchCriteria)->getItems(); + } catch (\Exception $e) { + throw new Exception(__($e->getMessage())); + } + + return $transaction; + } + + /** + * ProcessOrder function. + * @param $order + * @return void + * @throws Exception + */ + private function processOrder($order): void + { + try { + if ($order->getStatus() === Order::STATE_NEW) { + $order->setState(Order::STATE_PENDING_PAYMENT)->setStatus(Order::STATE_PENDING_PAYMENT); + } + } catch (\Exception $e) { + throw new Exception(__($e->getMessage())); + } } } From bfb9d1e9c9dc08de40e39aaeb9709d90641ef27d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Ka=C5=82u=C5=BCny?= Date: Thu, 27 Feb 2025 10:37:06 +0100 Subject: [PATCH 065/136] SQNETS-34: create service class for 'payment.created' event --- Model/Webhook/PaymentCreated.php | 57 +++++++++++++++++++++++++++++++- 1 file changed, 56 insertions(+), 1 deletion(-) diff --git a/Model/Webhook/PaymentCreated.php b/Model/Webhook/PaymentCreated.php index 0326df4b..1fa46bea 100644 --- a/Model/Webhook/PaymentCreated.php +++ b/Model/Webhook/PaymentCreated.php @@ -5,10 +5,65 @@ namespace Nexi\Checkout\Model\Webhook; +use Magento\Checkout\Exception; +use Magento\Sales\Api\Data\TransactionInterface; +use Magento\Sales\Api\OrderRepositoryInterface; +use Magento\Sales\Model\Order; +use Nexi\Checkout\Model\Transaction\Builder; + class PaymentCreated { - public function process() + public function __construct( + private Builder $transactionBuilder, + private OrderRepositoryInterface $orderRepository, + private Order $order + ) { + } + + /** + * PaymentCreated webhook service. + * + * @param $response + * @return void + * @throws Exception + */ + public function processWebhook($response): void + { + $params = json_decode('{"id":"685dc0ca3c034c8d8ac78e88a577870a","merchantId":100065206,"timestamp":"2025-02-24T13:57:49.2851+00:00","event":"payment.created","data":{"order":{"amount":{"amount":5780,"currency":"EUR"},"reference":"000000020","orderItems":[{"grossTotalAmount":5280,"name":"Orestes Yoga Pant ","netTotalAmount":5280,"quantity":1.0,"reference":"MP10-36-Green","taxRate":0,"taxAmount":0,"unit":"pcs","unitPrice":5280},{"grossTotalAmount":0,"name":"Orestes Yoga Pant -36-Green","netTotalAmount":0,"quantity":1.0,"reference":"MP10-36-Green","taxRate":0,"taxAmount":0,"unit":"pcs","unitPrice":0},{"grossTotalAmount":500,"name":"Flat Rate - Fixed","netTotalAmount":500,"quantity":1.0,"reference":"flatrate_flatrate","taxRate":0,"taxAmount":0,"unit":"pcs","unitPrice":500}]},"paymentId":"f369621ef1b149b5b90b65504506eb75"}}', true); + + $order = $this->order->loadByIncrementId($params['data']['order']['reference']); + $this->processOrder($order, $params['data']['paymentId']); + + $this->orderRepository->save($order); + } + + /** + * ProcessOrder function. + * + * @param $order + * @param $paymentId + * @return void + * @throws Exception + */ + private function processOrder($order, $paymentId): void { + try { + if ($order->getState() === Order::STATE_NEW) { + $order->setState(Order::STATE_PENDING_PAYMENT)->setStatus(Order::STATE_PENDING_PAYMENT); + $chargeTransaction = $this->transactionBuilder + ->build( + $paymentId, + $order, + [ + 'payment_id' => $paymentId + ], + TransactionInterface::TYPE_PAYMENT + ); + } + $order->addCommentToStatusHistory('Payment created successfully. Payment ID: %1', $paymentId); + } catch (\Exception $e) { + throw new Exception(__($e->getMessage())); + } } } From 6b0ecc65039c85c8cf386a89c8b78ebb33b6b8c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Ka=C5=82u=C5=BCny?= Date: Thu, 27 Feb 2025 10:53:55 +0100 Subject: [PATCH 066/136] SQNETS-34: improve PaymentCreated service --- Model/Webhook/PaymentCreated.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Model/Webhook/PaymentCreated.php b/Model/Webhook/PaymentCreated.php index 1fa46bea..b9141c15 100644 --- a/Model/Webhook/PaymentCreated.php +++ b/Model/Webhook/PaymentCreated.php @@ -6,17 +6,19 @@ use Magento\Checkout\Exception; +use Magento\Framework\Exception\LocalizedException; use Magento\Sales\Api\Data\TransactionInterface; use Magento\Sales\Api\OrderRepositoryInterface; use Magento\Sales\Model\Order; use Nexi\Checkout\Model\Transaction\Builder; +use Nexi\Checkout\Model\Webhook\Data\WebhookDataProvider; class PaymentCreated { public function __construct( private Builder $transactionBuilder, private OrderRepositoryInterface $orderRepository, - private Order $order + private WebhookDataProvider $webhookDataProvider ) { } @@ -26,12 +28,13 @@ public function __construct( * @param $response * @return void * @throws Exception + * @throws LocalizedException */ public function processWebhook($response): void { $params = json_decode('{"id":"685dc0ca3c034c8d8ac78e88a577870a","merchantId":100065206,"timestamp":"2025-02-24T13:57:49.2851+00:00","event":"payment.created","data":{"order":{"amount":{"amount":5780,"currency":"EUR"},"reference":"000000020","orderItems":[{"grossTotalAmount":5280,"name":"Orestes Yoga Pant ","netTotalAmount":5280,"quantity":1.0,"reference":"MP10-36-Green","taxRate":0,"taxAmount":0,"unit":"pcs","unitPrice":5280},{"grossTotalAmount":0,"name":"Orestes Yoga Pant -36-Green","netTotalAmount":0,"quantity":1.0,"reference":"MP10-36-Green","taxRate":0,"taxAmount":0,"unit":"pcs","unitPrice":0},{"grossTotalAmount":500,"name":"Flat Rate - Fixed","netTotalAmount":500,"quantity":1.0,"reference":"flatrate_flatrate","taxRate":0,"taxAmount":0,"unit":"pcs","unitPrice":500}]},"paymentId":"f369621ef1b149b5b90b65504506eb75"}}', true); - $order = $this->order->loadByIncrementId($params['data']['order']['reference']); + $order = $this->webhookDataProvider->loadOrder($params['data']['order']['reference']); $this->processOrder($order, $params['data']['paymentId']); $this->orderRepository->save($order); From c2bced8630134e866947934174433e36f8b93b64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Ka=C5=82u=C5=BCny?= Date: Thu, 27 Feb 2025 11:20:25 +0100 Subject: [PATCH 067/136] SQNETS-34: improve handler & controller --- Controller/Payment/Webhook.php | 5 +++-- Gateway/Handler/WebhookHandler.php | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Controller/Payment/Webhook.php b/Controller/Payment/Webhook.php index 69f3ed39..8e6bc671 100644 --- a/Controller/Payment/Webhook.php +++ b/Controller/Payment/Webhook.php @@ -11,6 +11,7 @@ use Magento\Framework\App\RequestInterface; use Magento\Framework\Encryption\Encryptor; use Nexi\Checkout\Gateway\Config\Config; +use Nexi\Checkout\Gateway\Handler\WebhookHandler; use Psr\Log\LoggerInterface; class Webhook extends Action implements CsrfAwareActionInterface, HttpPostActionInterface @@ -21,7 +22,7 @@ public function __construct( private readonly LoggerInterface $logger, private readonly Encryptor $encryptor, private readonly Config $config, - private readonly RequestInterface $request, + private WebhookHandler $webhookHandler ) { parent::__construct($context); } @@ -38,7 +39,7 @@ public function execute() ->setBody('Unauthorized'); } - $this->webhookHandler->handle($this->getRequest()->getContent()->getParam('event')); + $this->webhookHandler->handle($this->getRequest()->getParam('event')); // TODO: Implement webhook logic here $this->logger->info('Webhook called: ' . json_encode($this->getRequest()->getContent())); diff --git a/Gateway/Handler/WebhookHandler.php b/Gateway/Handler/WebhookHandler.php index ed4a4430..3907d955 100644 --- a/Gateway/Handler/WebhookHandler.php +++ b/Gateway/Handler/WebhookHandler.php @@ -22,6 +22,6 @@ public function __construct( */ public function handle($response) { - $this->webhookHandlers[$response]->process($response); + $this->webhookHandlers[$response]->processWebhook($response); } } From e893647625c33fc347a6b84eca6fc5f941e46a89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Ka=C5=82u=C5=BCny?= Date: Thu, 27 Feb 2025 11:20:44 +0100 Subject: [PATCH 068/136] SQNETS-34: update PaymentCreated service --- Model/Webhook/PaymentCreated.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Model/Webhook/PaymentCreated.php b/Model/Webhook/PaymentCreated.php index b9141c15..cb8832b9 100644 --- a/Model/Webhook/PaymentCreated.php +++ b/Model/Webhook/PaymentCreated.php @@ -34,7 +34,7 @@ public function processWebhook($response): void { $params = json_decode('{"id":"685dc0ca3c034c8d8ac78e88a577870a","merchantId":100065206,"timestamp":"2025-02-24T13:57:49.2851+00:00","event":"payment.created","data":{"order":{"amount":{"amount":5780,"currency":"EUR"},"reference":"000000020","orderItems":[{"grossTotalAmount":5280,"name":"Orestes Yoga Pant ","netTotalAmount":5280,"quantity":1.0,"reference":"MP10-36-Green","taxRate":0,"taxAmount":0,"unit":"pcs","unitPrice":5280},{"grossTotalAmount":0,"name":"Orestes Yoga Pant -36-Green","netTotalAmount":0,"quantity":1.0,"reference":"MP10-36-Green","taxRate":0,"taxAmount":0,"unit":"pcs","unitPrice":0},{"grossTotalAmount":500,"name":"Flat Rate - Fixed","netTotalAmount":500,"quantity":1.0,"reference":"flatrate_flatrate","taxRate":0,"taxAmount":0,"unit":"pcs","unitPrice":500}]},"paymentId":"f369621ef1b149b5b90b65504506eb75"}}', true); - $order = $this->webhookDataProvider->loadOrder($params['data']['order']['reference']); + $order = $this->webhookDataProvider->loadOrderByPaymentId($params['data']['paymentId']); $this->processOrder($order, $params['data']['paymentId']); $this->orderRepository->save($order); From f0a404995cb087ec2b53afe50ce83d91d1274a0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Ka=C5=82u=C5=BCny?= Date: Thu, 27 Feb 2025 11:21:08 +0100 Subject: [PATCH 069/136] SQNETS-34: create service for 'payment.charge.created.v2' --- Model/Webhook/PaymentChargeCreated.php | 49 +++++++++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) diff --git a/Model/Webhook/PaymentChargeCreated.php b/Model/Webhook/PaymentChargeCreated.php index f6e3d718..e2fd2f86 100644 --- a/Model/Webhook/PaymentChargeCreated.php +++ b/Model/Webhook/PaymentChargeCreated.php @@ -5,10 +5,57 @@ namespace Nexi\Checkout\Model\Webhook; +use Magento\Sales\Api\Data\TransactionInterface; +use Magento\Sales\Api\OrderRepositoryInterface; +use Magento\Sales\Model\Order; +use Nexi\Checkout\Model\Transaction\Builder; +use Nexi\Checkout\Model\Webhook\Data\WebhookDataProvider; + class PaymentChargeCreated { - public function process() + public function __construct( + private OrderRepositoryInterface $orderRepository, + private WebhookDataProvider $webhookDataProvider, + private Builder $transactionBuilder + ) { + } + + public function processWebhook() { + $params = json_decode('{"id":"312ecc6aaa5241a28a890b2e76ef8c93","timestamp":"2025-02-24T13:58:29.1396+00:00","merchantNumber":100065206,"event":"payment.charge.created.v2","data":{"chargeId":"312ecc6aaa5241a28a890b2e76ef8c93","orderItems":[{"grossTotalAmount":5280,"name":"Orestes Yoga Pant ","netTotalAmount":5280,"quantity":1.0,"reference":"MP10-36-Green","taxRate":0,"taxAmount":0,"unit":"pcs","unitPrice":5280},{"grossTotalAmount":0,"name":"Orestes Yoga Pant -36-Green","netTotalAmount":0,"quantity":1.0,"reference":"MP10-36-Green","taxRate":0,"taxAmount":0,"unit":"pcs","unitPrice":0},{"grossTotalAmount":500,"name":"Flat Rate - Fixed","netTotalAmount":500,"quantity":1.0,"reference":"flatrate_flatrate","taxRate":0,"taxAmount":0,"unit":"pcs","unitPrice":500}],"paymentMethod":"Visa","paymentType":"CARD","amount":{"amount":5780,"currency":"EUR"},"paymentId":"f369621ef1b149b5b90b65504506eb75"}}', true); + $order = $this->webhookDataProvider->loadOrderByPaymentId($params['data']['paymentId']); + + $this->processOrder($order, $params['data']['paymentId'], $params['data']['chargeId']); + + $this->orderRepository->save($order); + } + + private function processOrder($order, $paymentId, $chargeTxnId): void + { + $transaction = $this->webhookDataProvider->loadTransactionByPaymentId($paymentId); + if ($order->getState() === Order::STATE_PENDING_PAYMENT) { + $order->setState(Order::STATE_PROCESSING)->setStatus(Order::STATE_PROCESSING); + $chargeTransaction = $this->transactionBuilder + ->build( + $chargeTxnId, + $order, + [ + 'payment_id' => $paymentId, + 'charge_id' => $chargeTxnId, + ], + TransactionInterface::TYPE_CAPTURE + )->setParentId($transaction->getTransactionId()) + ->setParentTxnId($paymentId); + + if ($order->canInvoice()) { + $invoice = $order->prepareInvoice(); + $invoice->register(); + $invoice->setTransactionId($chargeTxnId); + $invoice->pay(); + $order->addCommentToStatusHistory(__('Nexi Payment charged successfully.')); + $order->addRelatedObject($invoice); + } + } } } From 1744e5d310bf8032dccbf00233be05ad22820e2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Ka=C5=82u=C5=BCny?= Date: Thu, 27 Feb 2025 11:25:23 +0100 Subject: [PATCH 070/136] SQNETS-34: create data loader --- Model/Webhook/Data/WebhookDataLoader.php | 65 ++++++++++++++++++++++++ Model/Webhook/PaymentChargeCreated.php | 10 ++-- Model/Webhook/PaymentCreated.php | 6 +-- 3 files changed, 73 insertions(+), 8 deletions(-) create mode 100644 Model/Webhook/Data/WebhookDataLoader.php diff --git a/Model/Webhook/Data/WebhookDataLoader.php b/Model/Webhook/Data/WebhookDataLoader.php new file mode 100644 index 00000000..30b5ac97 --- /dev/null +++ b/Model/Webhook/Data/WebhookDataLoader.php @@ -0,0 +1,65 @@ +searchCriteriaBuilder + ->addFilter('txn_id', $paymentId, 'eq') + ->create(); + $transaction = $this->transactionRepository->getList($searchCriteria)->getItems(); + } catch (\Exception $e) { + throw new Exception(__($e->getMessage())); + } + + return reset($transaction); + } + + /** + * LoadOrderByPaymentId function. + * + * @param $paymentId + * @return mixed + * @throws LocalizedException + */ + public function loadOrderByPaymentId($paymentId) + { + try { + $transaction = $this->loadTransactionByPaymentId($paymentId); + $order = $transaction->getOrder(); + } catch (\Exception $e) { + throw new LocalizedException(__($e->getMessage())); + } + + return $order; + } +} diff --git a/Model/Webhook/PaymentChargeCreated.php b/Model/Webhook/PaymentChargeCreated.php index e2fd2f86..90f64949 100644 --- a/Model/Webhook/PaymentChargeCreated.php +++ b/Model/Webhook/PaymentChargeCreated.php @@ -9,21 +9,21 @@ use Magento\Sales\Api\OrderRepositoryInterface; use Magento\Sales\Model\Order; use Nexi\Checkout\Model\Transaction\Builder; -use Nexi\Checkout\Model\Webhook\Data\WebhookDataProvider; +use Nexi\Checkout\Model\Webhook\Data\WebhookDataLoader; class PaymentChargeCreated { public function __construct( private OrderRepositoryInterface $orderRepository, - private WebhookDataProvider $webhookDataProvider, - private Builder $transactionBuilder + private WebhookDataLoader $webhookDataLoader, + private Builder $transactionBuilder ) { } public function processWebhook() { $params = json_decode('{"id":"312ecc6aaa5241a28a890b2e76ef8c93","timestamp":"2025-02-24T13:58:29.1396+00:00","merchantNumber":100065206,"event":"payment.charge.created.v2","data":{"chargeId":"312ecc6aaa5241a28a890b2e76ef8c93","orderItems":[{"grossTotalAmount":5280,"name":"Orestes Yoga Pant ","netTotalAmount":5280,"quantity":1.0,"reference":"MP10-36-Green","taxRate":0,"taxAmount":0,"unit":"pcs","unitPrice":5280},{"grossTotalAmount":0,"name":"Orestes Yoga Pant -36-Green","netTotalAmount":0,"quantity":1.0,"reference":"MP10-36-Green","taxRate":0,"taxAmount":0,"unit":"pcs","unitPrice":0},{"grossTotalAmount":500,"name":"Flat Rate - Fixed","netTotalAmount":500,"quantity":1.0,"reference":"flatrate_flatrate","taxRate":0,"taxAmount":0,"unit":"pcs","unitPrice":500}],"paymentMethod":"Visa","paymentType":"CARD","amount":{"amount":5780,"currency":"EUR"},"paymentId":"f369621ef1b149b5b90b65504506eb75"}}', true); - $order = $this->webhookDataProvider->loadOrderByPaymentId($params['data']['paymentId']); + $order = $this->webhookDataLoader->loadOrderByPaymentId($params['data']['paymentId']); $this->processOrder($order, $params['data']['paymentId'], $params['data']['chargeId']); @@ -32,7 +32,7 @@ public function processWebhook() private function processOrder($order, $paymentId, $chargeTxnId): void { - $transaction = $this->webhookDataProvider->loadTransactionByPaymentId($paymentId); + $transaction = $this->webhookDataLoader->loadTransactionByPaymentId($paymentId); if ($order->getState() === Order::STATE_PENDING_PAYMENT) { $order->setState(Order::STATE_PROCESSING)->setStatus(Order::STATE_PROCESSING); $chargeTransaction = $this->transactionBuilder diff --git a/Model/Webhook/PaymentCreated.php b/Model/Webhook/PaymentCreated.php index cb8832b9..f86014df 100644 --- a/Model/Webhook/PaymentCreated.php +++ b/Model/Webhook/PaymentCreated.php @@ -11,14 +11,14 @@ use Magento\Sales\Api\OrderRepositoryInterface; use Magento\Sales\Model\Order; use Nexi\Checkout\Model\Transaction\Builder; -use Nexi\Checkout\Model\Webhook\Data\WebhookDataProvider; +use Nexi\Checkout\Model\Webhook\Data\WebhookDataLoader; class PaymentCreated { public function __construct( private Builder $transactionBuilder, private OrderRepositoryInterface $orderRepository, - private WebhookDataProvider $webhookDataProvider + private WebhookDataLoader $webhookDataLoader ) { } @@ -34,7 +34,7 @@ public function processWebhook($response): void { $params = json_decode('{"id":"685dc0ca3c034c8d8ac78e88a577870a","merchantId":100065206,"timestamp":"2025-02-24T13:57:49.2851+00:00","event":"payment.created","data":{"order":{"amount":{"amount":5780,"currency":"EUR"},"reference":"000000020","orderItems":[{"grossTotalAmount":5280,"name":"Orestes Yoga Pant ","netTotalAmount":5280,"quantity":1.0,"reference":"MP10-36-Green","taxRate":0,"taxAmount":0,"unit":"pcs","unitPrice":5280},{"grossTotalAmount":0,"name":"Orestes Yoga Pant -36-Green","netTotalAmount":0,"quantity":1.0,"reference":"MP10-36-Green","taxRate":0,"taxAmount":0,"unit":"pcs","unitPrice":0},{"grossTotalAmount":500,"name":"Flat Rate - Fixed","netTotalAmount":500,"quantity":1.0,"reference":"flatrate_flatrate","taxRate":0,"taxAmount":0,"unit":"pcs","unitPrice":500}]},"paymentId":"f369621ef1b149b5b90b65504506eb75"}}', true); - $order = $this->webhookDataProvider->loadOrderByPaymentId($params['data']['paymentId']); + $order = $this->webhookDataLoader->loadOrderByPaymentId($params['data']['paymentId']); $this->processOrder($order, $params['data']['paymentId']); $this->orderRepository->save($order); From be00250ca25dd4b09a605a5c6b28158cc197aad0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Ka=C5=82u=C5=BCny?= Date: Mon, 3 Mar 2025 16:01:52 +0100 Subject: [PATCH 071/136] SQNETS-34: push empty webhooks --- Model/Webhook/PaymentCancelCreated.php | 14 ++++++++++++++ Model/Webhook/PaymentCancelFailed.php | 14 ++++++++++++++ Model/Webhook/PaymentChargeFailed.php | 14 ++++++++++++++ Model/Webhook/PaymentCheckoutCompleted.php | 4 ++-- Model/Webhook/PaymentRefundFailed.php | 14 ++++++++++++++ Model/Webhook/PaymentRefundInitiated.php | 14 ++++++++++++++ Model/Webhook/PaymentReservationFailed.php | 14 ++++++++++++++ 7 files changed, 86 insertions(+), 2 deletions(-) create mode 100644 Model/Webhook/PaymentCancelCreated.php create mode 100644 Model/Webhook/PaymentCancelFailed.php create mode 100644 Model/Webhook/PaymentChargeFailed.php create mode 100644 Model/Webhook/PaymentRefundFailed.php create mode 100644 Model/Webhook/PaymentRefundInitiated.php create mode 100644 Model/Webhook/PaymentReservationFailed.php diff --git a/Model/Webhook/PaymentCancelCreated.php b/Model/Webhook/PaymentCancelCreated.php new file mode 100644 index 00000000..9fcc3792 --- /dev/null +++ b/Model/Webhook/PaymentCancelCreated.php @@ -0,0 +1,14 @@ + Date: Mon, 3 Mar 2025 16:18:01 +0100 Subject: [PATCH 072/136] SQNETS-34: add refund webhook --- Model/Webhook/PaymentRefundCompleted.php | 35 +++++++++++++++++++ Model/Webhook/PaymentReservationCreated.php | 38 ++++----------------- 2 files changed, 42 insertions(+), 31 deletions(-) create mode 100644 Model/Webhook/PaymentRefundCompleted.php diff --git a/Model/Webhook/PaymentRefundCompleted.php b/Model/Webhook/PaymentRefundCompleted.php new file mode 100644 index 00000000..16acbac9 --- /dev/null +++ b/Model/Webhook/PaymentRefundCompleted.php @@ -0,0 +1,35 @@ +webhookDataLoader->loadOrderByPaymentId($params['data']['paymentId']); + + $chargeRefundTransaction = $this->transactionBuilder + ->build( + $params['data']['refundId'], + $order, + [ + 'payment_id' => $params['data']['paymentId'] + ], + TransactionInterface::TYPE_REFUND + )->setParentTxnId($params['data']['paymentId']); + } +} diff --git a/Model/Webhook/PaymentReservationCreated.php b/Model/Webhook/PaymentReservationCreated.php index b3671d19..db2f4262 100644 --- a/Model/Webhook/PaymentReservationCreated.php +++ b/Model/Webhook/PaymentReservationCreated.php @@ -1,64 +1,40 @@ loadTransactionByPaymentId($params['data']['paymentId']); + $order = $this->webhookDataLoader->loadOrderByPaymentId($params['data']['paymentId']); - $order = $this->orderRepository->get(reset($transaction)->getOrderId()); $order->getPayment()->setAdditionalInformation('selected_payment_method', $params['data']['paymentMethod']); $this->processOrder($order); $this->orderRepository->save($order); } - /** - * LoadTransactionByPaymentId function - * - * @param $paymentId - * @return \Magento\Sales\Api\Data\TransactionInterface[] - * @throws Exception - */ - private function loadTransactionByPaymentId($paymentId) - { - try { - $searchCriteria = $this->searchCriteriaBuilder - ->addFilter('txn_id', $paymentId, 'eq') - ->create(); - $transaction = $this->transactionRepository->getList($searchCriteria)->getItems(); - } catch (\Exception $e) { - throw new Exception(__($e->getMessage())); - } - - return $transaction; - } - /** * ProcessOrder function. * @param $order From bec555023958d5fe9d275025cd55b3de158dfd3c Mon Sep 17 00:00:00 2001 From: Konrad Konieczny Date: Mon, 17 Mar 2025 19:13:46 +0100 Subject: [PATCH 073/136] fixes regarding to pr comments --- Controller/Hpp/CancelAction.php | 5 +++-- Controller/Hpp/ReturnAction.php | 25 ++++++++++++++++++------- Gateway/Command/Initialize.php | 29 +++++++---------------------- Gateway/Http/Client.php | 2 +- 4 files changed, 29 insertions(+), 32 deletions(-) diff --git a/Controller/Hpp/CancelAction.php b/Controller/Hpp/CancelAction.php index 8bc82ac2..f34e156b 100644 --- a/Controller/Hpp/CancelAction.php +++ b/Controller/Hpp/CancelAction.php @@ -2,6 +2,7 @@ namespace Nexi\Checkout\Controller\Hpp; +use Exception; use Magento\Checkout\Model\Session; use Magento\Framework\App\ActionInterface; use Magento\Framework\Controller\Result\RedirectFactory; @@ -40,9 +41,9 @@ public function execute(): ResultInterface try { $this->checkoutSession->restoreQuote(); $this->messageManager->addNoticeMessage(__('The payment has been canceled.')); - } catch (\Exception $e) { + } catch (Exception $e) { $logId = uniqid(); - $this->logger->critical($logId . ' - ' . $e->getMessage() . ' - ' . $e->getTraceAsString()); + $this->logger->critical($logId . ' - ' . $e->getMessage(), $e); $this->messageManager->addErrorMessage( __( 'An error occurred during the payment process. Please try again later.' . diff --git a/Controller/Hpp/ReturnAction.php b/Controller/Hpp/ReturnAction.php index c7cef3e7..7f58e682 100644 --- a/Controller/Hpp/ReturnAction.php +++ b/Controller/Hpp/ReturnAction.php @@ -14,9 +14,7 @@ use Magento\Framework\UrlInterface; use Magento\Payment\Model\MethodInterface; use Magento\Sales\Api\Data\TransactionInterface; -use Magento\Sales\Api\TransactionRepositoryInterface; use Magento\Sales\Model\Order; -use Magento\Sales\Model\Order\Payment\Transaction; use Magento\Sales\Model\OrderRepository; use Nexi\Checkout\Gateway\Config\Config; use Nexi\Checkout\Model\Transaction\Builder; @@ -75,7 +73,6 @@ public function execute(): ResultInterface } if ($order->getState() != Order::STATE_NEW) { - $this->messageManager->addNoticeMessage(__('Payment already processed')); throw new LocalizedException(__('Payment already processed')); } @@ -131,19 +128,21 @@ public function execute(): ResultInterface __('Nexi Payment charged successfully. Payment ID: %1', $paymentId) ); $order->addRelatedObject($invoice); + $order->setCanSendNewEmailFlag(true); $this->orderRepository->save($order); } } + } catch (LocalizedException $e) { + $this->logger->error($e->getMessage(), $e); + $this->messageManager->addErrorMessage($e->getMessage()); + return $this->getCartRedirect(); } catch (Exception $e) { $this->logger->error($e->getMessage() . ' - ' . $e->getTraceAsString()); $this->messageManager->addErrorMessage( __('An error occurred during the payment process. Please try again later.') ); - - return $this->resultRedirectFactory->create()->setUrl( - $this->url->getUrl('checkout/cart/index', ['_secure' => true]) - ); + return $this->getCartRedirect(); } return $this->getSuccessRedirect(); @@ -173,4 +172,16 @@ public function getSuccessRedirect(): Redirect $this->url->getUrl('checkout/onepage/success', ['_secure' => true]) ); } + + /** + * Get cart redirect + * + * @return Redirect + */ + public function getCartRedirect(): Redirect + { + return $this->resultRedirectFactory->create()->setUrl( + $this->url->getUrl('checkout/cart/index', ['_secure' => true]) + ); + } } diff --git a/Gateway/Command/Initialize.php b/Gateway/Command/Initialize.php index 9abe9e25..04772cff 100755 --- a/Gateway/Command/Initialize.php +++ b/Gateway/Command/Initialize.php @@ -4,11 +4,9 @@ use Magento\Framework\Exception\LocalizedException; use Magento\Payment\Gateway\Command\CommandManagerPoolInterface; -use Magento\Payment\Gateway\Command\ResultInterface; use Magento\Payment\Gateway\CommandInterface; use Magento\Payment\Gateway\Data\PaymentDataObjectInterface; use Magento\Payment\Gateway\Helper\SubjectReader; -use Magento\Payment\Model\InfoInterface; use Magento\Sales\Model\Order; use Nexi\Checkout\Gateway\Config\Config; use Psr\Log\LoggerInterface; @@ -32,30 +30,25 @@ public function __construct( * * @param array $commandSubject * - * @return $this * @throws LocalizedException */ - public function execute(array $commandSubject): static + public function execute(array $commandSubject) { /** @var PaymentDataObjectInterface $payment */ $paymentData = $this->subjectReader->readPayment($commandSubject); $stateObject = $this->subjectReader->readStateObject($commandSubject); - /** @var InfoInterface $payment */ $payment = $paymentData->getPayment(); $payment->setIsTransactionPending(true); $payment->setIsTransactionIsClosed(false); $order = $payment->getOrder(); $order->setCanSendNewEmailFlag(false); - $stateObject->setIsNotified(false); $stateObject->setState(Order::STATE_NEW); $stateObject->setStatus('pending'); $stateObject->setIsNotified(false); $this->cratePayment($paymentData); - - return $this; } /** @@ -63,22 +56,14 @@ public function execute(array $commandSubject): static * * @param PaymentDataObjectInterface $payment * - * @return ResultInterface|null * @throws LocalizedException */ - public function cratePayment(PaymentDataObjectInterface $payment): ?ResultInterface + public function cratePayment(PaymentDataObjectInterface $payment) { - try { - $commandPool = $this->commandManagerPool->get(Config::CODE); - $result = $commandPool->executeByCode( - commandCode: 'create_payment', - arguments : ['payment' => $payment,] - ); - } catch (\Exception $e) { - $this->logger->error($e->getMessage()); - throw new LocalizedException(__('An error occurred during the payment process. Please try again later.')); - } - - return $result; + $commandPool = $this->commandManagerPool->get(Config::CODE); + $commandPool->executeByCode( + commandCode: 'create_payment', + arguments : ['payment' => $payment,] + ); } } diff --git a/Gateway/Http/Client.php b/Gateway/Http/Client.php index 36dc6d17..535a9d36 100644 --- a/Gateway/Http/Client.php +++ b/Gateway/Http/Client.php @@ -56,7 +56,7 @@ public function placeRequest(TransferInterface $transferObject): array 'Nexi response: ' . $this->getResponseData($response) ); } catch (PaymentApiException|\Exception $e) { - $this->logger->error($e->getMessage()); + $this->logger->error($e->getMessage(), $e); throw new LocalizedException(__('An error occurred during the payment process. Please try again later.')); } From b4dea16e7335c49c517e57ec980ae84d8d273ca3 Mon Sep 17 00:00:00 2001 From: Konrad Konieczny Date: Mon, 17 Mar 2025 20:02:29 +0100 Subject: [PATCH 074/136] use hosted payment method --- Controller/Hpp/CancelAction.php | 2 +- Controller/Hpp/ReturnAction.php | 9 +++++---- Gateway/Http/Client.php | 3 ++- Gateway/Request/CreatePaymentRequestBuilder.php | 2 +- 4 files changed, 9 insertions(+), 7 deletions(-) diff --git a/Controller/Hpp/CancelAction.php b/Controller/Hpp/CancelAction.php index f34e156b..b8ce6c37 100644 --- a/Controller/Hpp/CancelAction.php +++ b/Controller/Hpp/CancelAction.php @@ -43,7 +43,7 @@ public function execute(): ResultInterface $this->messageManager->addNoticeMessage(__('The payment has been canceled.')); } catch (Exception $e) { $logId = uniqid(); - $this->logger->critical($logId . ' - ' . $e->getMessage(), $e); + $this->logger->critical($logId . ' - ' . $e->getMessage(), [$e]); $this->messageManager->addErrorMessage( __( 'An error occurred during the payment process. Please try again later.' . diff --git a/Controller/Hpp/ReturnAction.php b/Controller/Hpp/ReturnAction.php index 7f58e682..90d7a8c8 100644 --- a/Controller/Hpp/ReturnAction.php +++ b/Controller/Hpp/ReturnAction.php @@ -16,6 +16,7 @@ use Magento\Sales\Api\Data\TransactionInterface; use Magento\Sales\Model\Order; use Magento\Sales\Model\OrderRepository; +use Magento\Sales\Model\Order\Email\Sender\OrderSender; use Nexi\Checkout\Gateway\Config\Config; use Nexi\Checkout\Model\Transaction\Builder; use NexiCheckout\Api\Exception\PaymentApiException; @@ -49,6 +50,7 @@ public function __construct( private readonly LoggerInterface $logger, private readonly ManagerInterface $messageManager, private readonly Client $client, + private readonly OrderSender $orderSender ) { } @@ -128,17 +130,16 @@ public function execute(): ResultInterface __('Nexi Payment charged successfully. Payment ID: %1', $paymentId) ); $order->addRelatedObject($invoice); - $order->setCanSendNewEmailFlag(true); - $this->orderRepository->save($order); + $this->orderSender->send($order); } } } catch (LocalizedException $e) { - $this->logger->error($e->getMessage(), $e); + $this->logger->error($e->getMessage(), [$e]); $this->messageManager->addErrorMessage($e->getMessage()); return $this->getCartRedirect(); } catch (Exception $e) { - $this->logger->error($e->getMessage() . ' - ' . $e->getTraceAsString()); + $this->logger->error($e->getMessage(), [$e]); $this->messageManager->addErrorMessage( __('An error occurred during the payment process. Please try again later.') ); diff --git a/Gateway/Http/Client.php b/Gateway/Http/Client.php index 535a9d36..3dbf1ca1 100644 --- a/Gateway/Http/Client.php +++ b/Gateway/Http/Client.php @@ -56,7 +56,8 @@ public function placeRequest(TransferInterface $transferObject): array 'Nexi response: ' . $this->getResponseData($response) ); } catch (PaymentApiException|\Exception $e) { - $this->logger->error($e->getMessage(), $e); + $this->logger->error($e->getMessage(), + [$e]); throw new LocalizedException(__('An error occurred during the payment process. Please try again later.')); } diff --git a/Gateway/Request/CreatePaymentRequestBuilder.php b/Gateway/Request/CreatePaymentRequestBuilder.php index cb97ff02..099d42f9 100644 --- a/Gateway/Request/CreatePaymentRequestBuilder.php +++ b/Gateway/Request/CreatePaymentRequestBuilder.php @@ -49,7 +49,7 @@ public function build(array $buildSubject): array $order = $buildSubject['payment']->getPayment()->getOrder(); return [ - 'nexi_method' => 'createPayment', + 'nexi_method' => 'createHostedPayment', 'body' => [ 'payment' => $this->buildPayment($order), ] From e31b99d1e0e0d017e09c9d46a72734c82c70d73f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Ka=C5=82u=C5=BCny?= Date: Thu, 27 Mar 2025 09:22:50 +0100 Subject: [PATCH 075/136] SQNETS-34: improve webhook controller --- Controller/Payment/Webhook.php | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/Controller/Payment/Webhook.php b/Controller/Payment/Webhook.php index 8e6bc671..d190800a 100644 --- a/Controller/Payment/Webhook.php +++ b/Controller/Payment/Webhook.php @@ -39,11 +39,15 @@ public function execute() ->setBody('Unauthorized'); } - $this->webhookHandler->handle($this->getRequest()->getParam('event')); - // TODO: Implement webhook logic here - $this->logger->info('Webhook called: ' . json_encode($this->getRequest()->getContent())); + try { + $this->webhookHandler->handle($this->getRequest()->getParam('event')); - $this->_response->setHttpResponseCode(200); + $this->logger->info('Webhook called: ' . json_encode($this->getRequest()->getContent())); + $this->_response->setHttpResponseCode(200); + } catch (Exception $e) { + $this->logger->error('Webhook error: ' . $e->getMessage()); + $this->_response->setHttpResponseCode(500); + } } From 0a39b56d54e8d0b4864379ae7278e2e25cc838d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Ka=C5=82u=C5=BCny?= Date: Thu, 27 Mar 2025 09:23:42 +0100 Subject: [PATCH 076/136] SQNETS-34: improve webhook handler --- Gateway/Handler/WebhookHandler.php | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Gateway/Handler/WebhookHandler.php b/Gateway/Handler/WebhookHandler.php index 3907d955..7135517a 100644 --- a/Gateway/Handler/WebhookHandler.php +++ b/Gateway/Handler/WebhookHandler.php @@ -19,9 +19,16 @@ public function __construct( * * @param $response * @return void + * @throws \Exception */ public function handle($response) { - $this->webhookHandlers[$response]->processWebhook($response); + try { + if (in_array($response['event'], $this->webhookHandlers)) { + $this->webhookHandlers[$response['event']]->processWebhook($response['data']); + } + } catch (\Exception $e) { + throw new \Exception($e->getMessage()); + } } } From 71f23293eb251987ef4241233caeaf175f774b18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Ka=C5=82u=C5=BCny?= Date: Thu, 27 Mar 2025 09:30:13 +0100 Subject: [PATCH 077/136] SQNETS-34: update webhooks models --- Model/Webhook/PaymentChargeCreated.php | 89 ++++++++++++++------- Model/Webhook/PaymentCreated.php | 24 ++++-- Model/Webhook/PaymentRefundCompleted.php | 41 +++++++--- Model/Webhook/PaymentReservationCreated.php | 24 ++++-- 4 files changed, 122 insertions(+), 56 deletions(-) diff --git a/Model/Webhook/PaymentChargeCreated.php b/Model/Webhook/PaymentChargeCreated.php index 90f64949..ae0aa031 100644 --- a/Model/Webhook/PaymentChargeCreated.php +++ b/Model/Webhook/PaymentChargeCreated.php @@ -4,7 +4,8 @@ namespace Nexi\Checkout\Model\Webhook; - +use Magento\Checkout\Exception; +use Magento\Framework\Exception\LocalizedException; use Magento\Sales\Api\Data\TransactionInterface; use Magento\Sales\Api\OrderRepositoryInterface; use Magento\Sales\Model\Order; @@ -13,6 +14,13 @@ class PaymentChargeCreated { + /** + * PaymentChargeCreated constructor. + * + * @param OrderRepositoryInterface $orderRepository + * @param WebhookDataLoader $webhookDataLoader + * @param Builder $transactionBuilder + */ public function __construct( private OrderRepositoryInterface $orderRepository, private WebhookDataLoader $webhookDataLoader, @@ -20,42 +28,65 @@ public function __construct( ) { } - public function processWebhook() + /** + * ProcessWebhook function for 'payment.charge.created.v2' event. + * + * @param $responseData + * @return void + * @throws LocalizedException + */ + public function processWebhook($responseData) { - $params = json_decode('{"id":"312ecc6aaa5241a28a890b2e76ef8c93","timestamp":"2025-02-24T13:58:29.1396+00:00","merchantNumber":100065206,"event":"payment.charge.created.v2","data":{"chargeId":"312ecc6aaa5241a28a890b2e76ef8c93","orderItems":[{"grossTotalAmount":5280,"name":"Orestes Yoga Pant ","netTotalAmount":5280,"quantity":1.0,"reference":"MP10-36-Green","taxRate":0,"taxAmount":0,"unit":"pcs","unitPrice":5280},{"grossTotalAmount":0,"name":"Orestes Yoga Pant -36-Green","netTotalAmount":0,"quantity":1.0,"reference":"MP10-36-Green","taxRate":0,"taxAmount":0,"unit":"pcs","unitPrice":0},{"grossTotalAmount":500,"name":"Flat Rate - Fixed","netTotalAmount":500,"quantity":1.0,"reference":"flatrate_flatrate","taxRate":0,"taxAmount":0,"unit":"pcs","unitPrice":500}],"paymentMethod":"Visa","paymentType":"CARD","amount":{"amount":5780,"currency":"EUR"},"paymentId":"f369621ef1b149b5b90b65504506eb75"}}', true); - $order = $this->webhookDataLoader->loadOrderByPaymentId($params['data']['paymentId']); + try { + $order = $this->webhookDataLoader->loadOrderByPaymentId($responseData['paymentId']); - $this->processOrder($order, $params['data']['paymentId'], $params['data']['chargeId']); + $this->processOrder($order, $responseData['paymentId'], $responseData['chargeId']); - $this->orderRepository->save($order); + $this->orderRepository->save($order); + } catch (\Exception $e) { + throw new LocalizedException(__($e->getMessage())); + } } + /** + * ProcessOrder function. + * + * @param $order + * @param $paymentId + * @param $chargeTxnId + * @return void + * @throws Exception + */ private function processOrder($order, $paymentId, $chargeTxnId): void { - $transaction = $this->webhookDataLoader->loadTransactionByPaymentId($paymentId); - if ($order->getState() === Order::STATE_PENDING_PAYMENT) { - $order->setState(Order::STATE_PROCESSING)->setStatus(Order::STATE_PROCESSING); - $chargeTransaction = $this->transactionBuilder - ->build( - $chargeTxnId, - $order, - [ - 'payment_id' => $paymentId, - 'charge_id' => $chargeTxnId, - ], - TransactionInterface::TYPE_CAPTURE - )->setParentId($transaction->getTransactionId()) - ->setParentTxnId($paymentId); - - if ($order->canInvoice()) { - $invoice = $order->prepareInvoice(); - $invoice->register(); - $invoice->setTransactionId($chargeTxnId); - $invoice->pay(); - - $order->addCommentToStatusHistory(__('Nexi Payment charged successfully.')); - $order->addRelatedObject($invoice); + try { + $transaction = $this->webhookDataLoader->loadTransactionByPaymentId($paymentId); + if ($order->getState() === Order::STATE_PENDING_PAYMENT) { + $order->setState(Order::STATE_PROCESSING)->setStatus(Order::STATE_PROCESSING); + $chargeTransaction = $this->transactionBuilder + ->build( + $chargeTxnId, + $order, + [ + 'payment_id' => $paymentId, + 'charge_id' => $chargeTxnId, + ], + TransactionInterface::TYPE_CAPTURE + )->setParentId($transaction->getTransactionId()) + ->setParentTxnId($paymentId); + + if ($order->canInvoice()) { + $invoice = $order->prepareInvoice(); + $invoice->register(); + $invoice->setTransactionId($chargeTxnId); + $invoice->pay(); + + $order->addCommentToStatusHistory(__('Nexi Payment charged successfully.')); + $order->addRelatedObject($invoice); + } } + } catch (\Exception $e) { + throw new Exception(__($e->getMessage())); } } } diff --git a/Model/Webhook/PaymentCreated.php b/Model/Webhook/PaymentCreated.php index f86014df..d6e3a8af 100644 --- a/Model/Webhook/PaymentCreated.php +++ b/Model/Webhook/PaymentCreated.php @@ -4,7 +4,6 @@ namespace Nexi\Checkout\Model\Webhook; - use Magento\Checkout\Exception; use Magento\Framework\Exception\LocalizedException; use Magento\Sales\Api\Data\TransactionInterface; @@ -15,6 +14,13 @@ class PaymentCreated { + /** + * PaymentCreated constructor. + * + * @param Builder $transactionBuilder + * @param OrderRepositoryInterface $orderRepository + * @param WebhookDataLoader $webhookDataLoader + */ public function __construct( private Builder $transactionBuilder, private OrderRepositoryInterface $orderRepository, @@ -25,19 +31,21 @@ public function __construct( /** * PaymentCreated webhook service. * - * @param $response + * @param $responseData * @return void * @throws Exception * @throws LocalizedException */ - public function processWebhook($response): void + public function processWebhook($responseData): void { - $params = json_decode('{"id":"685dc0ca3c034c8d8ac78e88a577870a","merchantId":100065206,"timestamp":"2025-02-24T13:57:49.2851+00:00","event":"payment.created","data":{"order":{"amount":{"amount":5780,"currency":"EUR"},"reference":"000000020","orderItems":[{"grossTotalAmount":5280,"name":"Orestes Yoga Pant ","netTotalAmount":5280,"quantity":1.0,"reference":"MP10-36-Green","taxRate":0,"taxAmount":0,"unit":"pcs","unitPrice":5280},{"grossTotalAmount":0,"name":"Orestes Yoga Pant -36-Green","netTotalAmount":0,"quantity":1.0,"reference":"MP10-36-Green","taxRate":0,"taxAmount":0,"unit":"pcs","unitPrice":0},{"grossTotalAmount":500,"name":"Flat Rate - Fixed","netTotalAmount":500,"quantity":1.0,"reference":"flatrate_flatrate","taxRate":0,"taxAmount":0,"unit":"pcs","unitPrice":500}]},"paymentId":"f369621ef1b149b5b90b65504506eb75"}}', true); - - $order = $this->webhookDataLoader->loadOrderByPaymentId($params['data']['paymentId']); - $this->processOrder($order, $params['data']['paymentId']); + try { + $order = $this->webhookDataLoader->loadOrderByPaymentId($responseData['paymentId']); + $this->processOrder($order, $responseData['paymentId']); - $this->orderRepository->save($order); + $this->orderRepository->save($order); + } catch (\Exception $e) { + throw new LocalizedException(__($e->getMessage())); + } } /** diff --git a/Model/Webhook/PaymentRefundCompleted.php b/Model/Webhook/PaymentRefundCompleted.php index 16acbac9..82acc0bb 100644 --- a/Model/Webhook/PaymentRefundCompleted.php +++ b/Model/Webhook/PaymentRefundCompleted.php @@ -5,31 +5,48 @@ namespace Nexi\Checkout\Model\Webhook; +use Magento\Framework\Exception\LocalizedException; use Magento\Sales\Api\Data\TransactionInterface; use Nexi\Checkout\Model\Transaction\Builder; use Nexi\Checkout\Model\Webhook\Data\WebhookDataLoader; class PaymentRefundCompleted { + /** + * PaymentRefundCompleted constructor. + * + * @param WebhookDataLoader $webhookDataLoader + * @param Builder $transactionBuilder + */ public function __construct( private WebhookDataLoader $webhookDataLoader, private Builder $transactionBuilder ) { } - public function processWebhook($response) + /** + * ProcessWebhook function for 'payment.refund.completed' event. + * + * @param $responseData + * @return void + * @throws LocalizedException + */ + public function processWebhook($responseData) { - $params = json_decode('{"id":"b16aadd52c574dedb8a3242e94ba6261","merchantId":100065206,"timestamp":"2025-03-03T14:54:26.4382+00:00","event":"payment.refund.completed","data":{"refundId":"ae4a07ad84d349acb373f777d29cfa53","reconciliationReference":"RRhncQ0LJITpbvV4LW9FXDSVR","amount":{"amount":4700,"currency":"EUR"},"paymentId":"9d0f058350e84981a9502fd31d8a512f"}}', true); - $order = $this->webhookDataLoader->loadOrderByPaymentId($params['data']['paymentId']); + try { + $order = $this->webhookDataLoader->loadOrderByPaymentId($responseData['paymentId']); - $chargeRefundTransaction = $this->transactionBuilder - ->build( - $params['data']['refundId'], - $order, - [ - 'payment_id' => $params['data']['paymentId'] - ], - TransactionInterface::TYPE_REFUND - )->setParentTxnId($params['data']['paymentId']); + $chargeRefundTransaction = $this->transactionBuilder + ->build( + $responseData['refundId'], + $order, + [ + 'payment_id' => $responseData['paymentId'] + ], + TransactionInterface::TYPE_REFUND + )->setParentTxnId($responseData['paymentId']); + } catch (\Exception $e) { + throw new LocalizedException(__($e->getMessage())); + } } } diff --git a/Model/Webhook/PaymentReservationCreated.php b/Model/Webhook/PaymentReservationCreated.php index db2f4262..225bb688 100644 --- a/Model/Webhook/PaymentReservationCreated.php +++ b/Model/Webhook/PaymentReservationCreated.php @@ -10,6 +10,12 @@ class PaymentReservationCreated { + /** + * PaymentReservationCreated constructor. + * + * @param OrderRepositoryInterface $orderRepository + * @param WebhookDataLoader $webhookDataLoader + */ public function __construct( private OrderRepositoryInterface $orderRepository, private WebhookDataLoader $webhookDataLoader @@ -19,24 +25,28 @@ public function __construct( /** * ProcessWebhook function for 'payment.reservation.created.v2' event. * - * @param $response + * @param $responseData * @return void * @throws Exception * @throws LocalizedException */ - public function processWebhook($response) + public function processWebhook($responseData) { - $params = json_decode('{"id":"d60fd4bbaad6454a8c2a4377601c969c","timestamp":"2025-02-24T13:58:29.1396+00:00","merchantNumber":100065206,"event":"payment.reservation.created.v2","data":{"paymentMethod":"Visa","paymentType":"CARD","amount":{"amount":5780,"currency":"EUR"},"paymentId":"f369621ef1b149b5b90b65504506eb75"}}', true); - $order = $this->webhookDataLoader->loadOrderByPaymentId($params['data']['paymentId']); + try { + $order = $this->webhookDataLoader->loadOrderByPaymentId($responseData['paymentId']); - $order->getPayment()->setAdditionalInformation('selected_payment_method', $params['data']['paymentMethod']); + $order->getPayment()->setAdditionalInformation('selected_payment_method', $responseData['paymentMethod']); - $this->processOrder($order); - $this->orderRepository->save($order); + $this->processOrder($order); + $this->orderRepository->save($order); + } catch (\Exception $e) { + throw new Exception(__($e->getMessage())); + } } /** * ProcessOrder function. + * * @param $order * @return void * @throws Exception From 400f27bc00e4a45e46bd6e314a586991a64a2949 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Ka=C5=82u=C5=BCny?= Date: Mon, 31 Mar 2025 20:49:08 +0200 Subject: [PATCH 078/136] SQNETS-59: make terms and condition fields required --- etc/adminhtml/system.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/etc/adminhtml/system.xml b/etc/adminhtml/system.xml index 2d970a74..e522465d 100644 --- a/etc/adminhtml/system.xml +++ b/etc/adminhtml/system.xml @@ -42,11 +42,13 @@ + required-entry add URL to your Webshop Terms and Conditions site. + required-entry add URL to your payment Terms and Conditions site. Date: Mon, 31 Mar 2025 20:50:37 +0200 Subject: [PATCH 079/136] Improve logging in ReturnAction for better debugging --- Controller/Hpp/ReturnAction.php | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Controller/Hpp/ReturnAction.php b/Controller/Hpp/ReturnAction.php index 90d7a8c8..2e152b8f 100644 --- a/Controller/Hpp/ReturnAction.php +++ b/Controller/Hpp/ReturnAction.php @@ -64,9 +64,11 @@ public function execute(): ResultInterface $order = $this->checkoutSession->getLastRealOrder(); $this->logger->debug( - 'ReturnAction request: ' . json_encode($this->request->getParams()) - . ' - Order ID: ' . $order->getIncrementId() - . 'http referrer: ' . $this->request->getServer('HTTP_REFERER') + 'ReturnAction request', + [ + 'params' => $this->request->getParams(), + 'order_id' => $order->getIncrementId() + ] ); try { From 1aa4c1036f186e557153c0618e5ee01a5ee60a4d Mon Sep 17 00:00:00 2001 From: Konrad Konieczny Date: Mon, 31 Mar 2025 21:02:23 +0200 Subject: [PATCH 080/136] Add a blank line for improved readability in TestConnection.php --- Controller/Adminhtml/System/Config/TestConnection.php | 1 + 1 file changed, 1 insertion(+) diff --git a/Controller/Adminhtml/System/Config/TestConnection.php b/Controller/Adminhtml/System/Config/TestConnection.php index 20b808a4..d124c380 100644 --- a/Controller/Adminhtml/System/Config/TestConnection.php +++ b/Controller/Adminhtml/System/Config/TestConnection.php @@ -76,6 +76,7 @@ public function execute() /** @var Json $resultJson */ $resultJson = $this->resultJsonFactory->create(); + return $resultJson->setData($result); } } From 05eee67f82b0787bc2a23ba46604c28561eb9159 Mon Sep 17 00:00:00 2001 From: Konrad Konieczny Date: Mon, 31 Mar 2025 21:11:18 +0200 Subject: [PATCH 081/136] Refactor payment initialization to use constants for status and improve exception logging --- Gateway/Command/Initialize.php | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/Gateway/Command/Initialize.php b/Gateway/Command/Initialize.php index e37ad0d2..665a3660 100755 --- a/Gateway/Command/Initialize.php +++ b/Gateway/Command/Initialize.php @@ -2,6 +2,7 @@ namespace Nexi\Checkout\Gateway\Command; +use Exception; use Magento\Framework\Exception\LocalizedException; use Magento\Payment\Gateway\Command\CommandManagerPoolInterface; use Magento\Payment\Gateway\Command\ResultInterface; @@ -9,11 +10,14 @@ use Magento\Payment\Gateway\Data\PaymentDataObjectInterface; use Magento\Payment\Gateway\Helper\SubjectReader; use Magento\Payment\Model\InfoInterface; +use Magento\Sales\Model\Order; use Nexi\Checkout\Gateway\Config\Config; use Psr\Log\LoggerInterface; class Initialize implements CommandInterface { + const STATUS_PENDING = 'pending'; + /** * @param SubjectReader $subjectReader * @param CommandManagerPoolInterface $commandManagerPool @@ -27,7 +31,7 @@ public function __construct( } /** - * Execute function + * Implementation of execute method, creating payment in Nexi Gateway when order is placed * * @param array $commandSubject * @@ -47,7 +51,7 @@ public function execute(array $commandSubject) $order->setCanSendNewEmailFlag(false); $stateObject->setState(Order::STATE_NEW); - $stateObject->setStatus('pending'); + $stateObject->setStatus(self::STATUS_PENDING); $stateObject->setIsNotified(false); $this->cratePayment($paymentData); @@ -69,8 +73,8 @@ public function cratePayment(PaymentDataObjectInterface $payment): ?ResultInterf commandCode: 'create_payment', arguments : ['payment' => $payment,] ); - } catch (\Exception $e) { - $this->logger->error($e->getMessage(), [$e]); + } catch (Exception $e) { + $this->logger->error($e->getMessage(), ['stacktrace' => $e->getTrace()]); throw new LocalizedException(__('An error occurred during the payment process. Please try again later.')); } From fb5ae311d4d4836fb195c9df1b494dfe713c3a08 Mon Sep 17 00:00:00 2001 From: Konrad Konieczny Date: Tue, 1 Apr 2025 10:32:34 +0200 Subject: [PATCH 082/136] Fix condition check for payment authorization action in ReturnAction --- Controller/Hpp/ReturnAction.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Controller/Hpp/ReturnAction.php b/Controller/Hpp/ReturnAction.php index 2e152b8f..d9937b89 100644 --- a/Controller/Hpp/ReturnAction.php +++ b/Controller/Hpp/ReturnAction.php @@ -91,7 +91,7 @@ public function execute(): ResultInterface TransactionInterface::TYPE_AUTH ); - if (MethodInterface::ACTION_AUTHORIZE) { + if (MethodInterface::ACTION_AUTHORIZE == $paymentAction) { $paymentTransaction->setIsClosed(0); } From d2b146fc44a065c569e9e8df1393453e5e860c8b Mon Sep 17 00:00:00 2001 From: Konrad Konieczny Date: Tue, 1 Apr 2025 10:34:03 +0200 Subject: [PATCH 083/136] Enhance logging in ReturnAction to include payment and order IDs for better traceability --- Controller/Hpp/ReturnAction.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Controller/Hpp/ReturnAction.php b/Controller/Hpp/ReturnAction.php index d9937b89..965c78b3 100644 --- a/Controller/Hpp/ReturnAction.php +++ b/Controller/Hpp/ReturnAction.php @@ -105,7 +105,10 @@ public function execute(): ResultInterface if ($paymentDetails->getPayment()->getStatus() == PaymentStatusEnum::RESERVED) { $this->messageManager->addNoticeMessage(__('Payment reserved, but not charged yet.')); - $this->logger->notice('Payment reserved, but not charged yet. Redirecting to success page.'); + $this->logger->notice('Payment reserved, but not charged yet. Redirecting to success page.', [ + 'payment_id' => $paymentId, + 'order_id' => $order->getIncrementId() + ]); return $this->getSuccessRedirect(); } elseif ($paymentDetails->getPayment()->getStatus() == PaymentStatusEnum::CHARGED) { From 23f08531dde66952cba5d3fbf9aae8cbd332b35b Mon Sep 17 00:00:00 2001 From: Konrad Konieczny Date: Tue, 1 Apr 2025 10:35:29 +0200 Subject: [PATCH 084/136] Fix conditional structure in ReturnAction for payment status check --- Controller/Hpp/ReturnAction.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Controller/Hpp/ReturnAction.php b/Controller/Hpp/ReturnAction.php index 965c78b3..8c659dec 100644 --- a/Controller/Hpp/ReturnAction.php +++ b/Controller/Hpp/ReturnAction.php @@ -111,7 +111,9 @@ public function execute(): ResultInterface ]); return $this->getSuccessRedirect(); - } elseif ($paymentDetails->getPayment()->getStatus() == PaymentStatusEnum::CHARGED) { + } + + if ($paymentDetails->getPayment()->getStatus() == PaymentStatusEnum::CHARGED) { $order->setState(Order::STATE_PROCESSING)->setStatus(Order::STATE_PROCESSING); $chargeTxnId = $paymentDetails->getPayment()->getCharges()[0]->getChargeId(); $this->transactionBuilder From 65f0abc79569783bb70174384b0b726cae024414 Mon Sep 17 00:00:00 2001 From: Konrad Konieczny Date: Tue, 1 Apr 2025 10:36:07 +0200 Subject: [PATCH 085/136] Fix formatting in ReturnAction for charge transaction ID assignment --- Controller/Hpp/ReturnAction.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Controller/Hpp/ReturnAction.php b/Controller/Hpp/ReturnAction.php index 8c659dec..d699c7e1 100644 --- a/Controller/Hpp/ReturnAction.php +++ b/Controller/Hpp/ReturnAction.php @@ -115,7 +115,7 @@ public function execute(): ResultInterface if ($paymentDetails->getPayment()->getStatus() == PaymentStatusEnum::CHARGED) { $order->setState(Order::STATE_PROCESSING)->setStatus(Order::STATE_PROCESSING); - $chargeTxnId = $paymentDetails->getPayment()->getCharges()[0]->getChargeId(); + $chargeTxnId = $paymentDetails->getPayment()->getCharges()[0]->getChargeId(); $this->transactionBuilder ->build( $chargeTxnId, From af8e01d9e87bfe0b8a1c0152a54b003f6ecdf001 Mon Sep 17 00:00:00 2001 From: Konrad Konieczny Date: Tue, 1 Apr 2025 14:05:58 +0200 Subject: [PATCH 086/136] Implement payment creation and termination in TestConnection, enhancing error handling for API interactions --- .../System/Config/TestConnection.php | 44 ++++++++++++++----- 1 file changed, 32 insertions(+), 12 deletions(-) diff --git a/Controller/Adminhtml/System/Config/TestConnection.php b/Controller/Adminhtml/System/Config/TestConnection.php index d124c380..57ace62b 100644 --- a/Controller/Adminhtml/System/Config/TestConnection.php +++ b/Controller/Adminhtml/System/Config/TestConnection.php @@ -11,9 +11,13 @@ use Magento\Framework\Controller\Result\JsonFactory; use Magento\Framework\Exception\LocalizedException; use Magento\Framework\Filter\StripTags; +use Magento\Framework\Url; use Nexi\Checkout\Gateway\Config\Config; use Nexi\Checkout\Model\Config\Source\Environment; +use NexiCheckout\Api\Exception\PaymentApiException; use NexiCheckout\Factory\PaymentApiFactory; +use NexiCheckout\Model\Request\Item; +use NexiCheckout\Model\Request\Payment; class TestConnection extends Action implements HttpPostActionInterface { @@ -23,7 +27,6 @@ class TestConnection extends Action implements HttpPostActionInterface * @see _isAllowed() */ public const ADMIN_RESOURCE = 'Magento_Catalog::config_catalog'; - private const NOT_GUID_PAYMENT_ID = 'test'; /** * @param Context $context @@ -31,13 +34,15 @@ class TestConnection extends Action implements HttpPostActionInterface * @param StripTags $tagFilter * @param PaymentApiFactory $paymentApiFactory * @param Config $config + * @param Url $url */ public function __construct( Context $context, private readonly JsonFactory $resultJsonFactory, private readonly StripTags $tagFilter, private readonly PaymentApiFactory $paymentApiFactory, - private readonly Config $config + private readonly Config $config, + private readonly Url $url ) { parent::__construct($context); } @@ -50,7 +55,7 @@ public function __construct( public function execute() { $result = [ - 'success' => false, + 'success' => true, 'errorMessage' => '', ]; $options = $this->getRequest()->getParams(); @@ -63,18 +68,33 @@ public function execute() isLiveMode: $options['environment'] == Environment::LIVE ); - $result = $api->retrievePayment(self::NOT_GUID_PAYMENT_ID); - } catch (\Exception $e) { - if (str_contains(mb_strtolower($e->getMessage()), 'should be in guid format')) { - $result['success'] = true; - } else { - $message = $e->getMessage(); - $result['errorMessage'] = $this->tagFilter->filter($message) . ' ' - . __('Please check your API key and environment.'); + $payment = $api->createEmbeddedPayment( + new Payment( + new Payment\Order( + [ + new Item('test', 1, 'pcs', 1, 1, 1, 'test') + ], + 'USD', + 1 + ), + new Payment\EmbeddedCheckout( + $this->url->getUrl('nexi/checkout/success'), + 'terms_url' + ) + ) + ); + + if ($payment->getPaymentId() ) { + $api->terminate($payment->getPaymentId()); } + + } catch (PaymentApiException $e) { + $message = $e->getMessage(); + $result['success'] = false; + $result['errorMessage'] = $this->tagFilter->filter($message) . ' ' + . __('Please check your API key and environment.'); } - /** @var Json $resultJson */ $resultJson = $this->resultJsonFactory->create(); return $resultJson->setData($result); From eaf3a8567299bf99b10151fce102395998c79b51 Mon Sep 17 00:00:00 2001 From: Konrad Konieczny Date: Tue, 1 Apr 2025 14:21:56 +0200 Subject: [PATCH 087/136] Fix type casting for payment amount in CreatePaymentRequestBuilder to ensure correct integer value --- Gateway/Request/CreatePaymentRequestBuilder.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gateway/Request/CreatePaymentRequestBuilder.php b/Gateway/Request/CreatePaymentRequestBuilder.php index 099d42f9..a6a2f4ea 100644 --- a/Gateway/Request/CreatePaymentRequestBuilder.php +++ b/Gateway/Request/CreatePaymentRequestBuilder.php @@ -66,7 +66,7 @@ public function buildOrder($order): Payment\Order return new Payment\Order( items : $this->buildItems($order), currency : $order->getBaseCurrencyCode(), - amount : $order->getGrandTotal() * 100, + amount : (int)$order->getGrandTotal() * 100, reference: $order->getIncrementId(), ); } From 582f49828cdee9491acc6748d83c82e063cb3c7e Mon Sep 17 00:00:00 2001 From: Konrad Konieczny Date: Tue, 1 Apr 2025 15:04:36 +0200 Subject: [PATCH 088/136] Update URLs in TestConnection and CreatePaymentRequestBuilder for success and return actions --- .../System/Config/TestConnection.php | 2 +- Controller/Hpp/ReturnAction.php | 195 ------------------ .../Request/CreatePaymentRequestBuilder.php | 4 +- 3 files changed, 3 insertions(+), 198 deletions(-) delete mode 100644 Controller/Hpp/ReturnAction.php diff --git a/Controller/Adminhtml/System/Config/TestConnection.php b/Controller/Adminhtml/System/Config/TestConnection.php index 57ace62b..6533988a 100644 --- a/Controller/Adminhtml/System/Config/TestConnection.php +++ b/Controller/Adminhtml/System/Config/TestConnection.php @@ -78,7 +78,7 @@ public function execute() 1 ), new Payment\EmbeddedCheckout( - $this->url->getUrl('nexi/checkout/success'), + $this->url->getUrl('checkout/onepage/success'), 'terms_url' ) ) diff --git a/Controller/Hpp/ReturnAction.php b/Controller/Hpp/ReturnAction.php deleted file mode 100644 index d699c7e1..00000000 --- a/Controller/Hpp/ReturnAction.php +++ /dev/null @@ -1,195 +0,0 @@ -checkoutSession->getLastRealOrder(); - - $this->logger->debug( - 'ReturnAction request', - [ - 'params' => $this->request->getParams(), - 'order_id' => $order->getIncrementId() - ] - ); - - try { - if ($order->getPayment()->getAdditionalInformation('payment_id') != $this->request->getParam('paymentid')) { - throw new LocalizedException(__('Payment ID does not match.')); - } - - if ($order->getState() != Order::STATE_NEW) { - throw new LocalizedException(__('Payment already processed')); - } - - $paymentAction = $this->config->getPaymentAction(); - $paymentId = $this->request->getParam('paymentid'); - - $order->setState(Order::STATE_PENDING_PAYMENT)->setStatus(Order::STATE_PENDING_PAYMENT); - $paymentTransaction = $this->transactionBuilder->build( - $paymentId, - $order, - ['payment_id' => $paymentId], - TransactionInterface::TYPE_AUTH - ); - - if (MethodInterface::ACTION_AUTHORIZE == $paymentAction) { - $paymentTransaction->setIsClosed(0); - } - - $order->addRelatedObject($paymentTransaction); - - $order->addCommentToStatusHistory(__('Nexi Payment authorized successfully. Payment ID: %1', $paymentId)); - $this->orderRepository->save($order); - - if (MethodInterface::ACTION_AUTHORIZE_CAPTURE == $paymentAction) { - $paymentDetails = $this->getPaymentDetails($paymentId); - - if ($paymentDetails->getPayment()->getStatus() == PaymentStatusEnum::RESERVED) { - $this->messageManager->addNoticeMessage(__('Payment reserved, but not charged yet.')); - $this->logger->notice('Payment reserved, but not charged yet. Redirecting to success page.', [ - 'payment_id' => $paymentId, - 'order_id' => $order->getIncrementId() - ]); - - return $this->getSuccessRedirect(); - } - - if ($paymentDetails->getPayment()->getStatus() == PaymentStatusEnum::CHARGED) { - $order->setState(Order::STATE_PROCESSING)->setStatus(Order::STATE_PROCESSING); - $chargeTxnId = $paymentDetails->getPayment()->getCharges()[0]->getChargeId(); - $this->transactionBuilder - ->build( - $chargeTxnId, - $order, - [ - 'payment_id' => $paymentId, - 'charge_id' => $chargeTxnId, - ], - TransactionInterface::TYPE_CAPTURE - )->setParentId($paymentTransaction->getTransactionId()) - ->setParentTxnId($paymentTransaction->getTxnId()); - - $invoice = $order->prepareInvoice(); - $invoice->register(); - $invoice->setTransactionId($chargeTxnId); - $invoice->pay(); - - $order->addCommentToStatusHistory( - __('Nexi Payment charged successfully. Payment ID: %1', $paymentId) - ); - $order->addRelatedObject($invoice); - $this->orderRepository->save($order); - $this->orderSender->send($order); - } - } - } catch (LocalizedException $e) { - $this->logger->error($e->getMessage(), [$e]); - $this->messageManager->addErrorMessage($e->getMessage()); - return $this->getCartRedirect(); - } catch (Exception $e) { - $this->logger->error($e->getMessage(), [$e]); - $this->messageManager->addErrorMessage( - __('An error occurred during the payment process. Please try again later.') - ); - return $this->getCartRedirect(); - } - - return $this->getSuccessRedirect(); - } - - /** - * Get payment details from Nexi API - * - * @param string $paymentId - * - * @return RetrievePaymentResult - * @throws PaymentApiException - */ - private function getPaymentDetails(string $paymentId): RetrievePaymentResult - { - return $this->client->getPaymentApi()->retrievePayment($paymentId); - } - - /** - * Get success redirect - * - * @return Redirect - */ - public function getSuccessRedirect(): Redirect - { - return $this->resultRedirectFactory->create()->setUrl( - $this->url->getUrl('checkout/onepage/success', ['_secure' => true]) - ); - } - - /** - * Get cart redirect - * - * @return Redirect - */ - public function getCartRedirect(): Redirect - { - return $this->resultRedirectFactory->create()->setUrl( - $this->url->getUrl('checkout/cart/index', ['_secure' => true]) - ); - } -} diff --git a/Gateway/Request/CreatePaymentRequestBuilder.php b/Gateway/Request/CreatePaymentRequestBuilder.php index a6a2f4ea..1ad1dcf2 100644 --- a/Gateway/Request/CreatePaymentRequestBuilder.php +++ b/Gateway/Request/CreatePaymentRequestBuilder.php @@ -155,7 +155,7 @@ public function buildCheckout(Order $order): HostedCheckout|EmbeddedCheckout { if ($this->config->getIntegrationType() == IntegrationTypeEnum::EmbeddedCheckout) { return new EmbeddedCheckout( - url : $this->url->getUrl('nexi/checkout/success'), + url : $this->url->getUrl('checkout'), termsUrl : $this->config->getPaymentsTermsAndConditionsUrl(), merchantTermsUrl: $this->config->getWebshopTermsAndConditionsUrl(), consumer : $this->buildConsumer($order), @@ -163,7 +163,7 @@ public function buildCheckout(Order $order): HostedCheckout|EmbeddedCheckout } return new HostedCheckout( - returnUrl : $this->url->getUrl('nexi/hpp/returnaction'), + returnUrl : $this->url->getUrl('checkout/onepage/success'), cancelUrl : $this->url->getUrl('nexi/hpp/cancelaction'), termsUrl : $this->config->getWebshopTermsAndConditionsUrl(), consumer : $this->buildConsumer($order), From 6598697b670ef87658d2032c3302a31d86cd3ade Mon Sep 17 00:00:00 2001 From: Konrad Konieczny Date: Tue, 1 Apr 2025 15:36:50 +0200 Subject: [PATCH 089/136] Fix type casting for payment amount in CreatePaymentRequestBuilder to ensure correct calculation --- Gateway/Request/CreatePaymentRequestBuilder.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gateway/Request/CreatePaymentRequestBuilder.php b/Gateway/Request/CreatePaymentRequestBuilder.php index 1ad1dcf2..6a800e53 100644 --- a/Gateway/Request/CreatePaymentRequestBuilder.php +++ b/Gateway/Request/CreatePaymentRequestBuilder.php @@ -66,7 +66,7 @@ public function buildOrder($order): Payment\Order return new Payment\Order( items : $this->buildItems($order), currency : $order->getBaseCurrencyCode(), - amount : (int)$order->getGrandTotal() * 100, + amount : (int)($order->getGrandTotal() * 100), reference: $order->getIncrementId(), ); } From e866717c72dd102f417f2d22003d5e535d46ff3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Ka=C5=82u=C5=BCny?= Date: Tue, 1 Apr 2025 15:49:47 +0200 Subject: [PATCH 090/136] SQNETS-34: remove unnecessary arguments --- etc/di.xml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/etc/di.xml b/etc/di.xml index fcc48462..26d84399 100644 --- a/etc/di.xml +++ b/etc/di.xml @@ -208,10 +208,6 @@ - Nexi\Checkout\Model\Webhook\PaymentCreated - Nexi\Checkout\Model\Webhook\PaymentReservationCreated - Nexi\Checkout\Model\Webhook\PaymentCheckoutCompleted - Nexi\Checkout\Model\Webhook\PaymentChargeCreated Nexi\Checkout\Model\Webhook\PaymentCheckoutCompleted Nexi\Checkout\Model\Webhook\PaymentCancelFailed From 78e22577e30975da7c76a8f783c43f35de761229 Mon Sep 17 00:00:00 2001 From: Konrad Konieczny Date: Tue, 1 Apr 2025 16:08:34 +0200 Subject: [PATCH 091/136] SQNETS-34: simplify transaction loading by removing try-catch block --- Model/Webhook/Data/WebhookDataLoader.php | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/Model/Webhook/Data/WebhookDataLoader.php b/Model/Webhook/Data/WebhookDataLoader.php index 30b5ac97..c07360bc 100644 --- a/Model/Webhook/Data/WebhookDataLoader.php +++ b/Model/Webhook/Data/WebhookDataLoader.php @@ -32,14 +32,10 @@ public function __construct( */ public function loadTransactionByPaymentId($paymentId) { - try { - $searchCriteria = $this->searchCriteriaBuilder - ->addFilter('txn_id', $paymentId, 'eq') - ->create(); - $transaction = $this->transactionRepository->getList($searchCriteria)->getItems(); - } catch (\Exception $e) { - throw new Exception(__($e->getMessage())); - } + $searchCriteria = $this->searchCriteriaBuilder + ->addFilter('txn_id', $paymentId, 'eq') + ->create(); + $transaction = $this->transactionRepository->getList($searchCriteria)->getItems(); return reset($transaction); } From 99208055e81edcf818fc1e6187facf5b9b2c6204 Mon Sep 17 00:00:00 2001 From: Konrad Konieczny Date: Tue, 1 Apr 2025 16:09:41 +0200 Subject: [PATCH 092/136] SQNETS-34: refactor transaction loading by removing try-catch block --- Model/Webhook/Data/WebhookDataLoader.php | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/Model/Webhook/Data/WebhookDataLoader.php b/Model/Webhook/Data/WebhookDataLoader.php index c07360bc..120ac3e0 100644 --- a/Model/Webhook/Data/WebhookDataLoader.php +++ b/Model/Webhook/Data/WebhookDataLoader.php @@ -28,14 +28,14 @@ public function __construct( * * @param $paymentId * @return TransactionInterface[] - * @throws Exception */ public function loadTransactionByPaymentId($paymentId) { $searchCriteria = $this->searchCriteriaBuilder ->addFilter('txn_id', $paymentId, 'eq') ->create(); - $transaction = $this->transactionRepository->getList($searchCriteria)->getItems(); + + $transaction = $this->transactionRepository->getList($searchCriteria)->getItems(); return reset($transaction); } @@ -49,12 +49,8 @@ public function loadTransactionByPaymentId($paymentId) */ public function loadOrderByPaymentId($paymentId) { - try { - $transaction = $this->loadTransactionByPaymentId($paymentId); - $order = $transaction->getOrder(); - } catch (\Exception $e) { - throw new LocalizedException(__($e->getMessage())); - } + $transaction = $this->loadTransactionByPaymentId($paymentId); + $order = $transaction->getOrder(); return $order; } From d92d49599a8ced6fb7288c0c7c099e60789e6461 Mon Sep 17 00:00:00 2001 From: Konrad Konieczny Date: Tue, 1 Apr 2025 16:10:08 +0200 Subject: [PATCH 093/136] refactor WebhookDataLoader constructor for improved readability --- Model/Webhook/Data/WebhookDataLoader.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Model/Webhook/Data/WebhookDataLoader.php b/Model/Webhook/Data/WebhookDataLoader.php index 120ac3e0..7f45dc78 100644 --- a/Model/Webhook/Data/WebhookDataLoader.php +++ b/Model/Webhook/Data/WebhookDataLoader.php @@ -12,13 +12,11 @@ class WebhookDataLoader { /** - * WebhookDataLoader constructor. - * * @param SearchCriteriaBuilder $searchCriteriaBuilder * @param TransactionRepositoryInterface $transactionRepository */ public function __construct( - private SearchCriteriaBuilder $searchCriteriaBuilder, + private SearchCriteriaBuilder $searchCriteriaBuilder, private TransactionRepositoryInterface $transactionRepository ) { } From e8adc35b8e0dabbbca8badc89fd0f181dd387f4f Mon Sep 17 00:00:00 2001 From: Konrad Konieczny Date: Tue, 1 Apr 2025 16:33:40 +0200 Subject: [PATCH 094/136] Enhance webhook logging and refactor webhook handler argument naming --- Controller/Payment/Webhook.php | 13 ++++++++++++- Gateway/Handler/WebhookHandler.php | 9 +++++---- etc/di.xml | 14 ++------------ 3 files changed, 19 insertions(+), 17 deletions(-) diff --git a/Controller/Payment/Webhook.php b/Controller/Payment/Webhook.php index d190800a..e7d04d06 100644 --- a/Controller/Payment/Webhook.php +++ b/Controller/Payment/Webhook.php @@ -42,7 +42,14 @@ public function execute() try { $this->webhookHandler->handle($this->getRequest()->getParam('event')); - $this->logger->info('Webhook called: ' . json_encode($this->getRequest()->getContent())); + $this->logger->info( + 'Webhook called:', + [ + 'webhook_data' => json_encode($this->getRequest()->getContent()), + 'payment_id' => $this->getRequest()->getParam('payment_id'), + ] + + ); $this->_response->setHttpResponseCode(200); } catch (Exception $e) { $this->logger->error('Webhook error: ' . $e->getMessage()); @@ -78,6 +85,10 @@ public function isAuthorized(): bool { $authString = $this->getRequest()->getHeader('Authorization'); + if (empty($authString)) { + return false; + } + $hash = $this->encryptor->hash( $this->config->getWebhookSecret(), ); diff --git a/Gateway/Handler/WebhookHandler.php b/Gateway/Handler/WebhookHandler.php index 7135517a..a1f95ebe 100644 --- a/Gateway/Handler/WebhookHandler.php +++ b/Gateway/Handler/WebhookHandler.php @@ -7,10 +7,10 @@ class WebhookHandler /** * WebhookHandler constructor. * - * @param array $webhookHandlers + * @param array $webhookProcessors */ public function __construct( - private array $webhookHandlers + private array $webhookProcessors ) { } @@ -24,8 +24,9 @@ public function __construct( public function handle($response) { try { - if (in_array($response['event'], $this->webhookHandlers)) { - $this->webhookHandlers[$response['event']]->processWebhook($response['data']); + $event = $response['event']; + if (array_key_exists($event, $this->webhookProcessors)) { + $this->webhookProcessors[$event]->processWebhook($response['data']); } } catch (\Exception $e) { throw new \Exception($e->getMessage()); diff --git a/etc/di.xml b/etc/di.xml index 26d84399..84268ec2 100644 --- a/etc/di.xml +++ b/etc/di.xml @@ -159,12 +159,6 @@ - - - NexiVirtualLogger - - - NexiVirtualLogger @@ -184,11 +178,6 @@ sortOrder="100"/> - - - Magento\Checkout\Model\Session\Proxy - - Magento\Checkout\Model\Session\Proxy @@ -208,7 +197,7 @@ - + Nexi\Checkout\Model\Webhook\PaymentCheckoutCompleted Nexi\Checkout\Model\Webhook\PaymentCancelFailed Nexi\Checkout\Model\Webhook\PaymentCancelCreated @@ -226,6 +215,7 @@ Nexi\Checkout\Gateway\Handler\WebhookHandler + NexiVirtualLogger From 76d8d9fe1cfcc3bee0800fc37433eae82c556339 Mon Sep 17 00:00:00 2001 From: Konrad Konieczny Date: Wed, 2 Apr 2025 11:38:18 +0200 Subject: [PATCH 095/136] Refactor webhook handling and enhance error logging; add serializer for request content --- Controller/Payment/Webhook.php | 30 ++++++++++---- Gateway/Command/Initialize.php | 20 +++++++++- Gateway/Handler/WebhookHandler.php | 14 +++---- Model/Webhook/Data/WebhookDataLoader.php | 3 +- Model/Webhook/PaymentCreated.php | 40 ++++++++++++------- Model/Webhook/PaymentReservationCreated.php | 43 +++++++++++++++------ 6 files changed, 106 insertions(+), 44 deletions(-) diff --git a/Controller/Payment/Webhook.php b/Controller/Payment/Webhook.php index e7d04d06..9ed2d715 100644 --- a/Controller/Payment/Webhook.php +++ b/Controller/Payment/Webhook.php @@ -10,6 +10,7 @@ use Magento\Framework\App\Request\InvalidRequestException; use Magento\Framework\App\RequestInterface; use Magento\Framework\Encryption\Encryptor; +use Magento\Framework\Serialize\SerializerInterface; use Nexi\Checkout\Gateway\Config\Config; use Nexi\Checkout\Gateway\Handler\WebhookHandler; use Psr\Log\LoggerInterface; @@ -18,11 +19,12 @@ class Webhook extends Action implements CsrfAwareActionInterface, HttpPostAction { public function __construct( - Context $context, - private readonly LoggerInterface $logger, - private readonly Encryptor $encryptor, - private readonly Config $config, - private WebhookHandler $webhookHandler + Context $context, + private readonly LoggerInterface $logger, + private readonly Encryptor $encryptor, + private readonly Config $config, + private readonly WebhookHandler $webhookHandler, + private readonly SerializerInterface $serializer ) { parent::__construct($context); } @@ -40,7 +42,15 @@ public function execute() } try { - $this->webhookHandler->handle($this->getRequest()->getParam('event')); + $content = $this->serializer->unserialize($this->getRequest()->getContent()); + + if (!isset($content['event'])) { + return $this->_response + ->setHttpResponseCode(400) + ->setBody('Missing event name'); + } + + $this->webhookHandler->handle($content); $this->logger->info( 'Webhook called:', @@ -52,7 +62,13 @@ public function execute() ); $this->_response->setHttpResponseCode(200); } catch (Exception $e) { - $this->logger->error('Webhook error: ' . $e->getMessage()); + $this->logger->error( + 'Webhook error:', + [ + 'message' => $e->getMessage(), + 'stacktrace' => $e->getTraceAsString(), + ] + ); $this->_response->setHttpResponseCode(500); } } diff --git a/Gateway/Command/Initialize.php b/Gateway/Command/Initialize.php index 665a3660..030b98cb 100755 --- a/Gateway/Command/Initialize.php +++ b/Gateway/Command/Initialize.php @@ -10,8 +10,10 @@ use Magento\Payment\Gateway\Data\PaymentDataObjectInterface; use Magento\Payment\Gateway\Helper\SubjectReader; use Magento\Payment\Model\InfoInterface; +use Magento\Sales\Api\Data\TransactionInterface; use Magento\Sales\Model\Order; use Nexi\Checkout\Gateway\Config\Config; +use Nexi\Checkout\Model\Transaction\Builder; use Psr\Log\LoggerInterface; class Initialize implements CommandInterface @@ -22,11 +24,13 @@ class Initialize implements CommandInterface * @param SubjectReader $subjectReader * @param CommandManagerPoolInterface $commandManagerPool * @param LoggerInterface $logger + * @param Builder $transactionBuilder */ public function __construct( private readonly SubjectReader $subjectReader, private readonly CommandManagerPoolInterface $commandManagerPool, - private readonly LoggerInterface $logger + private readonly LoggerInterface $logger, + private readonly Builder $transactionBuilder ) { } @@ -47,6 +51,7 @@ public function execute(array $commandSubject) $payment = $paymentData->getPayment(); $payment->setIsTransactionPending(true); $payment->setIsTransactionIsClosed(false); + $order = $payment->getOrder(); $order->setCanSendNewEmailFlag(false); @@ -55,6 +60,19 @@ public function execute(array $commandSubject) $stateObject->setIsNotified(false); $this->cratePayment($paymentData); + + $transactionId = $payment->getAdditionalInformation('payment_id'); + $orderTransaction = $this->transactionBuilder->build( + $transactionId, + $order, + ['payment_id' => $transactionId], + TransactionInterface::TYPE_PAYMENT + ); + + $payment->addTransactionCommentsToOrder( + $orderTransaction, + __('Payment created in Nexi Gateway.') + ); } /** diff --git a/Gateway/Handler/WebhookHandler.php b/Gateway/Handler/WebhookHandler.php index a1f95ebe..12e11b9b 100644 --- a/Gateway/Handler/WebhookHandler.php +++ b/Gateway/Handler/WebhookHandler.php @@ -17,19 +17,15 @@ public function __construct( /** * Handler passes forward on to the appropriate handler. * - * @param $response + * @param $request * @return void * @throws \Exception */ - public function handle($response) + public function handle($webhookData) { - try { - $event = $response['event']; - if (array_key_exists($event, $this->webhookProcessors)) { - $this->webhookProcessors[$event]->processWebhook($response['data']); - } - } catch (\Exception $e) { - throw new \Exception($e->getMessage()); + $event = $webhookData['event']; + if (array_key_exists($event, $this->webhookProcessors)) { + $this->webhookProcessors[$event]->processWebhook($webhookData); } } } diff --git a/Model/Webhook/Data/WebhookDataLoader.php b/Model/Webhook/Data/WebhookDataLoader.php index 7f45dc78..6654ad09 100644 --- a/Model/Webhook/Data/WebhookDataLoader.php +++ b/Model/Webhook/Data/WebhookDataLoader.php @@ -8,6 +8,7 @@ use Magento\Framework\Exception\LocalizedException; use Magento\Sales\Api\Data\TransactionInterface; use Magento\Sales\Api\TransactionRepositoryInterface; +use Magento\Sales\Model\Order; class WebhookDataLoader { @@ -45,7 +46,7 @@ public function loadTransactionByPaymentId($paymentId) * @return mixed * @throws LocalizedException */ - public function loadOrderByPaymentId($paymentId) + public function loadOrderByPaymentId($paymentId): Order { $transaction = $this->loadTransactionByPaymentId($paymentId); $order = $transaction->getOrder(); diff --git a/Model/Webhook/PaymentCreated.php b/Model/Webhook/PaymentCreated.php index d6e3a8af..f11f0498 100644 --- a/Model/Webhook/PaymentCreated.php +++ b/Model/Webhook/PaymentCreated.php @@ -6,6 +6,7 @@ use Magento\Checkout\Exception; use Magento\Framework\Exception\LocalizedException; +use Magento\Reports\Model\ResourceModel\Order\CollectionFactory; use Magento\Sales\Api\Data\TransactionInterface; use Magento\Sales\Api\OrderRepositoryInterface; use Magento\Sales\Model\Order; @@ -18,34 +19,40 @@ class PaymentCreated * PaymentCreated constructor. * * @param Builder $transactionBuilder - * @param OrderRepositoryInterface $orderRepository * @param WebhookDataLoader $webhookDataLoader */ public function __construct( - private Builder $transactionBuilder, - private OrderRepositoryInterface $orderRepository, - private WebhookDataLoader $webhookDataLoader + private readonly Builder $transactionBuilder, + private readonly CollectionFactory $orderCollectionFactory, + private readonly WebhookDataLoader $webhookDataLoader, + private readonly OrderRepositoryInterface $orderRepository ) { } /** * PaymentCreated webhook service. * - * @param $responseData + * @param $webhookData + * * @return void * @throws Exception * @throws LocalizedException */ - public function processWebhook($responseData): void + public function processWebhook($webhookData): void { - try { - $order = $this->webhookDataLoader->loadOrderByPaymentId($responseData['paymentId']); - $this->processOrder($order, $responseData['paymentId']); - - $this->orderRepository->save($order); - } catch (\Exception $e) { - throw new LocalizedException(__($e->getMessage())); + $transaction = $this->webhookDataLoader->loadTransactionByPaymentId($webhookData['data']['paymentId']); + if ($transaction) { + return; } + + $order = $this->orderCollectionFactory->create()->addFieldToFilter( + 'increment_id', + $webhookData['data']['order']['reference'] + )->getFirstItem(); + + $this->createPaymentTransaction($order, $webhookData['data']['paymentId']); + + $this->orderRepository->save($order); } /** @@ -56,7 +63,7 @@ public function processWebhook($responseData): void * @return void * @throws Exception */ - private function processOrder($order, $paymentId): void + private function createPaymentTransaction($order, $paymentId): void { try { if ($order->getState() === Order::STATE_NEW) { @@ -72,7 +79,10 @@ private function processOrder($order, $paymentId): void ); } - $order->addCommentToStatusHistory('Payment created successfully. Payment ID: %1', $paymentId); + $order->getPayment()->addTransactionCommentsToOrder( + $chargeTransaction, + __('Payment created in Nexi Gateway.') + ); } catch (\Exception $e) { throw new Exception(__($e->getMessage())); } diff --git a/Model/Webhook/PaymentReservationCreated.php b/Model/Webhook/PaymentReservationCreated.php index 225bb688..0f5b9dc0 100644 --- a/Model/Webhook/PaymentReservationCreated.php +++ b/Model/Webhook/PaymentReservationCreated.php @@ -4,8 +4,11 @@ use Magento\Checkout\Exception; use Magento\Framework\Exception\LocalizedException; +use Magento\Payment\Model\MethodInterface; +use Magento\Sales\Api\Data\TransactionInterface; use Magento\Sales\Api\OrderRepositoryInterface; use Magento\Sales\Model\Order; +use Nexi\Checkout\Model\Transaction\Builder; use Nexi\Checkout\Model\Webhook\Data\WebhookDataLoader; class PaymentReservationCreated @@ -18,30 +21,48 @@ class PaymentReservationCreated */ public function __construct( private OrderRepositoryInterface $orderRepository, - private WebhookDataLoader $webhookDataLoader + private WebhookDataLoader $webhookDataLoader, + private Builder $transactionBuilder ) { } /** * ProcessWebhook function for 'payment.reservation.created.v2' event. * - * @param $responseData + * @param $webhookDada + * * @return void * @throws Exception * @throws LocalizedException + * @throws \Exception */ - public function processWebhook($responseData) + public function processWebhook($webhookDada): void { - try { - $order = $this->webhookDataLoader->loadOrderByPaymentId($responseData['paymentId']); + $paymentId = $webhookDada['data']['paymentId']; + $paymentTransaction = $this->webhookDataLoader->loadTransactionByPaymentId($paymentId); + if (!$paymentTransaction) { + throw new \Exception('Payment transaction not found.'); + } - $order->getPayment()->setAdditionalInformation('selected_payment_method', $responseData['paymentMethod']); + $order = $paymentTransaction->getOrder(); - $this->processOrder($order); - $this->orderRepository->save($order); - } catch (\Exception $e) { - throw new Exception(__($e->getMessage())); - } + $order->setState(Order::STATE_PENDING_PAYMENT)->setStatus(Order::STATE_PENDING_PAYMENT); + $paymentTransaction = $this->transactionBuilder->build( + $webhookDada['id'], + $order, + ['payment_id' => $paymentId], + TransactionInterface::TYPE_AUTH + ); + $paymentTransaction->setIsClosed(0); + $paymentTransaction->setParentTxnId($paymentId); + $paymentTransaction->setParentId($paymentTransaction->getTransactionId()); + + $order->getPayment()->addTransactionCommentsToOrder( + $paymentTransaction, + __('Payment reservation created.') + ); + + $this->orderRepository->save($order); } /** From 2256393c5d3bdb57635da9c563592f72a82b36c0 Mon Sep 17 00:00:00 2001 From: Konrad Konieczny Date: Wed, 2 Apr 2025 16:49:07 +0200 Subject: [PATCH 096/136] Fix tax rate calculation to handle division by zero in SalesDocumentItemsBuilder --- Gateway/Request/NexiCheckout/SalesDocumentItemsBuilder.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gateway/Request/NexiCheckout/SalesDocumentItemsBuilder.php b/Gateway/Request/NexiCheckout/SalesDocumentItemsBuilder.php index 6340ea83..cf6bfa06 100644 --- a/Gateway/Request/NexiCheckout/SalesDocumentItemsBuilder.php +++ b/Gateway/Request/NexiCheckout/SalesDocumentItemsBuilder.php @@ -42,7 +42,7 @@ public function build(CreditmemoInterface|InvoiceInterface $salesObject): array grossTotalAmount: (int)($salesObject->getShippingInclTax() * 100), netTotalAmount : (int)($salesObject->getShippingAmount() * 100), reference : $salesObject->getOrder()->getShippingMethod(), - taxRate : (int)($salesObject->getTaxAmount() / $salesObject->getGrandTotal() * 100), + taxRate : $salesObject->getGrandTotal() ? (int)($salesObject->getTaxAmount() / $salesObject->getGrandTotal() * 100) : 0, taxAmount : (int)($salesObject->getShippingTaxAmount() * 100), ); } From b0ad646242947893497296fd7e9f290e0069a47a Mon Sep 17 00:00:00 2001 From: Konrad Konieczny Date: Wed, 2 Apr 2025 18:10:57 +0200 Subject: [PATCH 097/136] Refactor webhook handling: update namespaces, improve transaction loading methods, and enhance order processing logic --- Controller/Payment/Webhook.php | 4 +- .../SalesDocumentItemsBuilder.php | 3 +- Model/Webhook/Data/WebhookDataLoader.php | 51 +++++- Model/Webhook/PaymentChargeCreated.php | 167 ++++++++++++++---- Model/Webhook/PaymentCreated.php | 38 ++-- Model/Webhook/PaymentReservationCreated.php | 14 +- Model/Webhook/WebhookProcessorInterface.php | 15 ++ {Gateway/Handler => Model}/WebhookHandler.php | 2 +- etc/di.xml | 4 +- 9 files changed, 220 insertions(+), 78 deletions(-) create mode 100644 Model/Webhook/WebhookProcessorInterface.php rename {Gateway/Handler => Model}/WebhookHandler.php (93%) diff --git a/Controller/Payment/Webhook.php b/Controller/Payment/Webhook.php index 9ed2d715..7af7de61 100644 --- a/Controller/Payment/Webhook.php +++ b/Controller/Payment/Webhook.php @@ -12,7 +12,7 @@ use Magento\Framework\Encryption\Encryptor; use Magento\Framework\Serialize\SerializerInterface; use Nexi\Checkout\Gateway\Config\Config; -use Nexi\Checkout\Gateway\Handler\WebhookHandler; +use Nexi\Checkout\Model\WebhookHandler; use Psr\Log\LoggerInterface; class Webhook extends Action implements CsrfAwareActionInterface, HttpPostActionInterface @@ -101,6 +101,8 @@ public function isAuthorized(): bool { $authString = $this->getRequest()->getHeader('Authorization'); + return true; + if (empty($authString)) { return false; } diff --git a/Gateway/Request/NexiCheckout/SalesDocumentItemsBuilder.php b/Gateway/Request/NexiCheckout/SalesDocumentItemsBuilder.php index cf6bfa06..b7b9e9a4 100644 --- a/Gateway/Request/NexiCheckout/SalesDocumentItemsBuilder.php +++ b/Gateway/Request/NexiCheckout/SalesDocumentItemsBuilder.php @@ -8,6 +8,7 @@ class SalesDocumentItemsBuilder { + const SHIPPING_COST_REFERENCE = 'shipping_cost_ref'; /** * Build sales document items for the given sales object @@ -41,7 +42,7 @@ public function build(CreditmemoInterface|InvoiceInterface $salesObject): array unitPrice : (int)($salesObject->getShippingAmount() * 100), grossTotalAmount: (int)($salesObject->getShippingInclTax() * 100), netTotalAmount : (int)($salesObject->getShippingAmount() * 100), - reference : $salesObject->getOrder()->getShippingMethod(), + reference : self::SHIPPING_COST_REFERENCE, taxRate : $salesObject->getGrandTotal() ? (int)($salesObject->getTaxAmount() / $salesObject->getGrandTotal() * 100) : 0, taxAmount : (int)($salesObject->getShippingTaxAmount() * 100), ); diff --git a/Model/Webhook/Data/WebhookDataLoader.php b/Model/Webhook/Data/WebhookDataLoader.php index 6654ad09..1c497912 100644 --- a/Model/Webhook/Data/WebhookDataLoader.php +++ b/Model/Webhook/Data/WebhookDataLoader.php @@ -6,6 +6,7 @@ use Magento\Checkout\Exception; use Magento\Framework\Api\SearchCriteriaBuilder; use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\Exception\NotFoundException; use Magento\Sales\Api\Data\TransactionInterface; use Magento\Sales\Api\TransactionRepositoryInterface; use Magento\Sales\Model\Order; @@ -23,20 +24,54 @@ public function __construct( } /** - * LoadTransactionByPaymentId function + * LoadTransactionByTxnId function * - * @param $paymentId - * @return TransactionInterface[] + * @param string $txnId + * @param string $txnType + * + * @return TransactionInterface|null + */ + public function getTransactionByPaymentId( + string $txnId, + string $txnType = TransactionInterface::TYPE_PAYMENT + ): ?TransactionInterface { + $searchCriteria = $this->searchCriteriaBuilder + ->addFilter('txn_id', $txnId, 'eq') + ->addFilter('txn_type', $txnType, 'eq') + ->create(); + + $transactions = $this->transactionRepository->getList($searchCriteria)->getItems(); + + if (count($transactions) !== 1) { + return null; + } + + return reset($transactions); + } + + /** + * LoadTransactionOrderId function + * + * @param $orderId + * @param string $txnType + * + * @return TransactionInterface + * @throws NotFoundException */ - public function loadTransactionByPaymentId($paymentId) + public function getTransactionByOrderId($orderId, $txnType = TransactionInterface::TYPE_PAYMENT): TransactionInterface { $searchCriteria = $this->searchCriteriaBuilder - ->addFilter('txn_id', $paymentId, 'eq') + ->addFilter('order_id', $orderId, 'eq') + ->addFilter('txn_type', $txnType, 'eq') ->create(); - $transaction = $this->transactionRepository->getList($searchCriteria)->getItems(); + $transactions = $this->transactionRepository->getList($searchCriteria)->getItems(); + + if (count($transactions) !== 1) { + throw new NotFoundException(__('Transaction not found or multiple transactions found for payment ID.')); + } - return reset($transaction); + return reset($transactions); } /** @@ -48,7 +83,7 @@ public function loadTransactionByPaymentId($paymentId) */ public function loadOrderByPaymentId($paymentId): Order { - $transaction = $this->loadTransactionByPaymentId($paymentId); + $transaction = $this->getTransactionByPaymentId($paymentId); $order = $transaction->getOrder(); return $order; diff --git a/Model/Webhook/PaymentChargeCreated.php b/Model/Webhook/PaymentChargeCreated.php index ae0aa031..060febe9 100644 --- a/Model/Webhook/PaymentChargeCreated.php +++ b/Model/Webhook/PaymentChargeCreated.php @@ -6,13 +6,15 @@ use Magento\Checkout\Exception; use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\Exception\NotFoundException; use Magento\Sales\Api\Data\TransactionInterface; use Magento\Sales\Api\OrderRepositoryInterface; use Magento\Sales\Model\Order; +use Nexi\Checkout\Gateway\Request\NexiCheckout\SalesDocumentItemsBuilder; use Nexi\Checkout\Model\Transaction\Builder; use Nexi\Checkout\Model\Webhook\Data\WebhookDataLoader; -class PaymentChargeCreated +class PaymentChargeCreated implements WebhookProcessorInterface { /** * PaymentChargeCreated constructor. @@ -31,62 +33,149 @@ public function __construct( /** * ProcessWebhook function for 'payment.charge.created.v2' event. * - * @param $responseData + * @param $webhookData + * * @return void * @throws LocalizedException */ - public function processWebhook($responseData) + public function processWebhook($webhookData): void + { + $order = $this->webhookDataLoader->loadOrderByPaymentId($webhookData['data']['paymentId']); + $this->processOrder($order, $webhookData); + + $this->orderRepository->save($order); + } + + /** + * ProcessOrder function. + * + * @param $order + * @param $webhookData + * + * @return void + * @throws NotFoundException + * @throws \Exception + */ + private function processOrder($order, $webhookData): void { - try { - $order = $this->webhookDataLoader->loadOrderByPaymentId($responseData['paymentId']); + $reservationTxn = $this->webhookDataLoader->getTransactionByOrderId( + $order->getId(), + TransactionInterface::TYPE_AUTH + ); - $this->processOrder($order, $responseData['paymentId'], $responseData['chargeId']); - $this->orderRepository->save($order); - } catch (\Exception $e) { - throw new LocalizedException(__($e->getMessage())); + if ($order->getState() !== Order::STATE_PENDING_PAYMENT) { + throw new \Exception('Order state is not pending payment.'); } + + $chargeTxnId = $webhookData['data']['chargeId']; + $chargeTransaction = $this->transactionBuilder + ->build( + $chargeTxnId, + $order, + [ + 'payment_id' => $webhookData['data']['paymentId'], + 'webhook' => json_encode($webhookData, JSON_PRETTY_PRINT), + ], + TransactionInterface::TYPE_CAPTURE + )->setParentId($reservationTxn->getTransactionId()) + ->setParentTxnId($reservationTxn->getTxnId()); + + $order->getPayment()->addTransactionCommentsToOrder( + $chargeTransaction, + __( + 'Payment charge created, amount: %1 %2', + $webhookData['data']['amount']['amount']/100, + $webhookData['data']['amount']['currency'] + ) + ); + + if ($this->isFullCharge($webhookData, $order)) { + $this->fullInvoice($order, $chargeTxnId); + } else { + $this->partialInvoice($order, $chargeTxnId, $webhookData['data']['orderItems']); + } + $order->setState(Order::STATE_PROCESSING)->setStatus(Order::STATE_PROCESSING); } + /** - * ProcessOrder function. + * Validate charge transaction. + * Check items paid to create proper invoice. * + * @param $webhookData * @param $order - * @param $paymentId + * + * @return bool + */ + private function isFullCharge( + $webhookData, $order + ): bool { + return (int)($order->getBaseGrandTotal() * 100) === $webhookData['data']['amount']['amount']; + } + + /** + * @param $order + * @param $chargeTxnId + * + * @return void + */ + public function fullInvoice(Order $order, $chargeTxnId): void + { + if ($order->canInvoice()) { + $invoice = $order->prepareInvoice(); + $invoice->register(); + $invoice->setTransactionId($chargeTxnId); + $invoice->pay(); + + $order->addRelatedObject($invoice); + } + } + + /** + * Create partial invoice. Add shipping amount if charged + * TODO: investigate how to invoice only shipping cost in magento? probably not possible separately - without any order item invoiced + * TODO: now its only in order history comments (if charge only for shipping) + * + * @param Order $order * @param $chargeTxnId + * @param $webhookItems + * * @return void - * @throws Exception + * @throws LocalizedException */ - private function processOrder($order, $paymentId, $chargeTxnId): void + private function partialInvoice(Order $order, $chargeTxnId, $webhookItems): void { - try { - $transaction = $this->webhookDataLoader->loadTransactionByPaymentId($paymentId); - if ($order->getState() === Order::STATE_PENDING_PAYMENT) { - $order->setState(Order::STATE_PROCESSING)->setStatus(Order::STATE_PROCESSING); - $chargeTransaction = $this->transactionBuilder - ->build( - $chargeTxnId, - $order, - [ - 'payment_id' => $paymentId, - 'charge_id' => $chargeTxnId, - ], - TransactionInterface::TYPE_CAPTURE - )->setParentId($transaction->getTransactionId()) - ->setParentTxnId($paymentId); - - if ($order->canInvoice()) { - $invoice = $order->prepareInvoice(); - $invoice->register(); - $invoice->setTransactionId($chargeTxnId); - $invoice->pay(); - - $order->addCommentToStatusHistory(__('Nexi Payment charged successfully.')); - $order->addRelatedObject($invoice); + if ($order->canInvoice()) { + + $qtys = []; + $shippingItem = null; + foreach ($webhookItems as $webhookItem) { + + if ($webhookItem['reference'] === SalesDocumentItemsBuilder::SHIPPING_COST_REFERENCE) { + $shippingItem = $webhookItem; + continue; } + + foreach ($order->getAllItems() as $item) { + if ($item->getSku() === $webhookItem['reference']) { + $qtys[$item->getId()] = (int)$webhookItem['quantity']; + } + } + } + $invoice = $order->prepareInvoice($qtys); + $invoice->setTransactionId($chargeTxnId); + if ($shippingItem) { + $invoice->setShippingAmount($shippingItem['netTotalAmount'] / 100); + $invoice->setShippingInclTax($shippingItem['grossTotalAmount'] / 100); + $invoice->setShippingTaxAmount($shippingItem['taxAmount'] / 100); } - } catch (\Exception $e) { - throw new Exception(__($e->getMessage())); + + $invoice->pay(); + + + $invoice->register(); + $order->addRelatedObject($invoice); } } } diff --git a/Model/Webhook/PaymentCreated.php b/Model/Webhook/PaymentCreated.php index f11f0498..7af662b0 100644 --- a/Model/Webhook/PaymentCreated.php +++ b/Model/Webhook/PaymentCreated.php @@ -4,6 +4,7 @@ namespace Nexi\Checkout\Model\Webhook; +use Braintree\Exception\NotFound; use Magento\Checkout\Exception; use Magento\Framework\Exception\LocalizedException; use Magento\Reports\Model\ResourceModel\Order\CollectionFactory; @@ -13,13 +14,15 @@ use Nexi\Checkout\Model\Transaction\Builder; use Nexi\Checkout\Model\Webhook\Data\WebhookDataLoader; -class PaymentCreated +class PaymentCreated implements WebhookProcessorInterface { /** * PaymentCreated constructor. * * @param Builder $transactionBuilder + * @param CollectionFactory $orderCollectionFactory * @param WebhookDataLoader $webhookDataLoader + * @param OrderRepositoryInterface $orderRepository */ public function __construct( private readonly Builder $transactionBuilder, @@ -40,7 +43,8 @@ public function __construct( */ public function processWebhook($webhookData): void { - $transaction = $this->webhookDataLoader->loadTransactionByPaymentId($webhookData['data']['paymentId']); + $transaction = $this->webhookDataLoader->getTransactionByPaymentId($webhookData['data']['paymentId']); + if ($transaction) { return; } @@ -60,31 +64,27 @@ public function processWebhook($webhookData): void * * @param $order * @param $paymentId + * * @return void * @throws Exception */ private function createPaymentTransaction($order, $paymentId): void { - try { - if ($order->getState() === Order::STATE_NEW) { - $order->setState(Order::STATE_PENDING_PAYMENT)->setStatus(Order::STATE_PENDING_PAYMENT); - $chargeTransaction = $this->transactionBuilder - ->build( - $paymentId, - $order, - [ - 'payment_id' => $paymentId - ], - TransactionInterface::TYPE_PAYMENT - ); - } - + if ($order->getState() === Order::STATE_NEW) { + $order->setState(Order::STATE_PENDING_PAYMENT)->setStatus(Order::STATE_PENDING_PAYMENT); + $paymentTransaction = $this->transactionBuilder + ->build( + $paymentId, + $order, + [ + 'payment_id' => $paymentId + ], + TransactionInterface::TYPE_PAYMENT + ); $order->getPayment()->addTransactionCommentsToOrder( - $chargeTransaction, + $paymentTransaction, __('Payment created in Nexi Gateway.') ); - } catch (\Exception $e) { - throw new Exception(__($e->getMessage())); } } } diff --git a/Model/Webhook/PaymentReservationCreated.php b/Model/Webhook/PaymentReservationCreated.php index 0f5b9dc0..fa8c167a 100644 --- a/Model/Webhook/PaymentReservationCreated.php +++ b/Model/Webhook/PaymentReservationCreated.php @@ -11,7 +11,7 @@ use Nexi\Checkout\Model\Transaction\Builder; use Nexi\Checkout\Model\Webhook\Data\WebhookDataLoader; -class PaymentReservationCreated +class PaymentReservationCreated implements WebhookProcessorInterface { /** * PaymentReservationCreated constructor. @@ -39,7 +39,7 @@ public function __construct( public function processWebhook($webhookDada): void { $paymentId = $webhookDada['data']['paymentId']; - $paymentTransaction = $this->webhookDataLoader->loadTransactionByPaymentId($paymentId); + $paymentTransaction = $this->webhookDataLoader->getTransactionByPaymentId($paymentId); if (!$paymentTransaction) { throw new \Exception('Payment transaction not found.'); } @@ -47,18 +47,18 @@ public function processWebhook($webhookDada): void $order = $paymentTransaction->getOrder(); $order->setState(Order::STATE_PENDING_PAYMENT)->setStatus(Order::STATE_PENDING_PAYMENT); - $paymentTransaction = $this->transactionBuilder->build( + $reservationTransaction = $this->transactionBuilder->build( $webhookDada['id'], $order, ['payment_id' => $paymentId], TransactionInterface::TYPE_AUTH ); - $paymentTransaction->setIsClosed(0); - $paymentTransaction->setParentTxnId($paymentId); - $paymentTransaction->setParentId($paymentTransaction->getTransactionId()); + $reservationTransaction->setIsClosed(0); + $reservationTransaction->setParentTxnId($paymentId); + $reservationTransaction->setParentId($paymentTransaction->getTransactionId()); $order->getPayment()->addTransactionCommentsToOrder( - $paymentTransaction, + $reservationTransaction, __('Payment reservation created.') ); diff --git a/Model/Webhook/WebhookProcessorInterface.php b/Model/Webhook/WebhookProcessorInterface.php new file mode 100644 index 00000000..b751ccc6 --- /dev/null +++ b/Model/Webhook/WebhookProcessorInterface.php @@ -0,0 +1,15 @@ + - + Nexi\Checkout\Model\Webhook\PaymentCheckoutCompleted @@ -214,7 +214,7 @@ - Nexi\Checkout\Gateway\Handler\WebhookHandler + Nexi\Checkout\Model\WebhookHandler NexiVirtualLogger From e466bd2d064a43ce6b9c9203c786f258b8731b67 Mon Sep 17 00:00:00 2001 From: Konrad Konieczny Date: Wed, 2 Apr 2025 18:11:30 +0200 Subject: [PATCH 098/136] Add transaction builder parameter to PaymentReservationCreated constructor --- Model/Webhook/PaymentReservationCreated.php | 1 + 1 file changed, 1 insertion(+) diff --git a/Model/Webhook/PaymentReservationCreated.php b/Model/Webhook/PaymentReservationCreated.php index fa8c167a..754fe52a 100644 --- a/Model/Webhook/PaymentReservationCreated.php +++ b/Model/Webhook/PaymentReservationCreated.php @@ -18,6 +18,7 @@ class PaymentReservationCreated implements WebhookProcessorInterface * * @param OrderRepositoryInterface $orderRepository * @param WebhookDataLoader $webhookDataLoader + * @param Builder $transactionBuilder */ public function __construct( private OrderRepositoryInterface $orderRepository, From 786b5bd05c6fb7c01de87fdc805b3160eece9b48 Mon Sep 17 00:00:00 2001 From: Konrad Konieczny Date: Wed, 2 Apr 2025 18:11:57 +0200 Subject: [PATCH 099/136] Remove unused processOrder method and related code from PaymentReservationCreated --- Model/Webhook/PaymentReservationCreated.php | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/Model/Webhook/PaymentReservationCreated.php b/Model/Webhook/PaymentReservationCreated.php index 754fe52a..4bdab2e1 100644 --- a/Model/Webhook/PaymentReservationCreated.php +++ b/Model/Webhook/PaymentReservationCreated.php @@ -4,7 +4,6 @@ use Magento\Checkout\Exception; use Magento\Framework\Exception\LocalizedException; -use Magento\Payment\Model\MethodInterface; use Magento\Sales\Api\Data\TransactionInterface; use Magento\Sales\Api\OrderRepositoryInterface; use Magento\Sales\Model\Order; @@ -65,22 +64,4 @@ public function processWebhook($webhookDada): void $this->orderRepository->save($order); } - - /** - * ProcessOrder function. - * - * @param $order - * @return void - * @throws Exception - */ - private function processOrder($order): void - { - try { - if ($order->getStatus() === Order::STATE_NEW) { - $order->setState(Order::STATE_PENDING_PAYMENT)->setStatus(Order::STATE_PENDING_PAYMENT); - } - } catch (\Exception $e) { - throw new Exception(__($e->getMessage())); - } - } } From 9653a08a999b641f633ecbecdb9d40966a0e13c7 Mon Sep 17 00:00:00 2001 From: Konrad Konieczny Date: Wed, 2 Apr 2025 18:12:42 +0200 Subject: [PATCH 100/136] Fix typo in processWebhook parameter name and update references --- Model/Webhook/PaymentReservationCreated.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Model/Webhook/PaymentReservationCreated.php b/Model/Webhook/PaymentReservationCreated.php index 4bdab2e1..365d4a73 100644 --- a/Model/Webhook/PaymentReservationCreated.php +++ b/Model/Webhook/PaymentReservationCreated.php @@ -29,16 +29,16 @@ public function __construct( /** * ProcessWebhook function for 'payment.reservation.created.v2' event. * - * @param $webhookDada + * @param $webhookData * * @return void * @throws Exception * @throws LocalizedException * @throws \Exception */ - public function processWebhook($webhookDada): void + public function processWebhook($webhookData): void { - $paymentId = $webhookDada['data']['paymentId']; + $paymentId = $webhookData['data']['paymentId']; $paymentTransaction = $this->webhookDataLoader->getTransactionByPaymentId($paymentId); if (!$paymentTransaction) { throw new \Exception('Payment transaction not found.'); @@ -48,7 +48,7 @@ public function processWebhook($webhookDada): void $order->setState(Order::STATE_PENDING_PAYMENT)->setStatus(Order::STATE_PENDING_PAYMENT); $reservationTransaction = $this->transactionBuilder->build( - $webhookDada['id'], + $webhookData['id'], $order, ['payment_id' => $paymentId], TransactionInterface::TYPE_AUTH From 1aa6b9dddd54fa824a108876749605288a08f97a Mon Sep 17 00:00:00 2001 From: Konrad Konieczny Date: Wed, 2 Apr 2025 18:15:03 +0200 Subject: [PATCH 101/136] Add OrderRepositoryInterface to PaymentRefundCompleted for order saving --- Model/Webhook/PaymentRefundCompleted.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Model/Webhook/PaymentRefundCompleted.php b/Model/Webhook/PaymentRefundCompleted.php index 82acc0bb..d67d5bf3 100644 --- a/Model/Webhook/PaymentRefundCompleted.php +++ b/Model/Webhook/PaymentRefundCompleted.php @@ -7,6 +7,7 @@ use Magento\Framework\Exception\LocalizedException; use Magento\Sales\Api\Data\TransactionInterface; +use Magento\Sales\Api\OrderRepositoryInterface; use Nexi\Checkout\Model\Transaction\Builder; use Nexi\Checkout\Model\Webhook\Data\WebhookDataLoader; @@ -20,7 +21,8 @@ class PaymentRefundCompleted */ public function __construct( private WebhookDataLoader $webhookDataLoader, - private Builder $transactionBuilder + private Builder $transactionBuilder, + private readonly OrderRepositoryInterface $orderRepository ) { } @@ -45,6 +47,8 @@ public function processWebhook($responseData) ], TransactionInterface::TYPE_REFUND )->setParentTxnId($responseData['paymentId']); + + $this->orderRepository->save($order); } catch (\Exception $e) { throw new LocalizedException(__($e->getMessage())); } From 4015f6e2e20f04d03c79a1812805a28d587a7e5c Mon Sep 17 00:00:00 2001 From: Konrad Konieczny Date: Wed, 2 Apr 2025 18:18:20 +0200 Subject: [PATCH 102/136] Add TODO comments for refund completed event handling in PaymentRefundCompleted --- Model/Webhook/PaymentRefundCompleted.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Model/Webhook/PaymentRefundCompleted.php b/Model/Webhook/PaymentRefundCompleted.php index d67d5bf3..79f89761 100644 --- a/Model/Webhook/PaymentRefundCompleted.php +++ b/Model/Webhook/PaymentRefundCompleted.php @@ -28,7 +28,8 @@ public function __construct( /** * ProcessWebhook function for 'payment.refund.completed' event. - * + * TODO: Implement the logic to handle the refund completed event. + * TODO: create credit memo * @param $responseData * @return void * @throws LocalizedException From de8c001d3c52c34c6b4138c6d6b44c66f9653dc2 Mon Sep 17 00:00:00 2001 From: Konrad Konieczny Date: Wed, 2 Apr 2025 18:18:45 +0200 Subject: [PATCH 103/136] Refactor PaymentRefundCompleted constructor to use readonly properties for dependencies --- Model/Webhook/PaymentRefundCompleted.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Model/Webhook/PaymentRefundCompleted.php b/Model/Webhook/PaymentRefundCompleted.php index 79f89761..e0deb252 100644 --- a/Model/Webhook/PaymentRefundCompleted.php +++ b/Model/Webhook/PaymentRefundCompleted.php @@ -18,10 +18,11 @@ class PaymentRefundCompleted * * @param WebhookDataLoader $webhookDataLoader * @param Builder $transactionBuilder + * @param OrderRepositoryInterface $orderRepository */ public function __construct( - private WebhookDataLoader $webhookDataLoader, - private Builder $transactionBuilder, + private readonly WebhookDataLoader $webhookDataLoader, + private readonly Builder $transactionBuilder, private readonly OrderRepositoryInterface $orderRepository ) { } From 6403d34ff1677faf5a144dc12cc1ade7bb1be215 Mon Sep 17 00:00:00 2001 From: Konrad Konieczny Date: Wed, 2 Apr 2025 18:19:31 +0200 Subject: [PATCH 104/136] Implement WebhookProcessorInterface in PaymentRefundCompleted and update processWebhook method to use webhookData --- Model/Webhook/PaymentRefundCompleted.php | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/Model/Webhook/PaymentRefundCompleted.php b/Model/Webhook/PaymentRefundCompleted.php index e0deb252..4b8c574c 100644 --- a/Model/Webhook/PaymentRefundCompleted.php +++ b/Model/Webhook/PaymentRefundCompleted.php @@ -11,7 +11,7 @@ use Nexi\Checkout\Model\Transaction\Builder; use Nexi\Checkout\Model\Webhook\Data\WebhookDataLoader; -class PaymentRefundCompleted +class PaymentRefundCompleted implements WebhookProcessorInterface { /** * PaymentRefundCompleted constructor. @@ -31,24 +31,26 @@ public function __construct( * ProcessWebhook function for 'payment.refund.completed' event. * TODO: Implement the logic to handle the refund completed event. * TODO: create credit memo - * @param $responseData + * + * @param $webhookData + * * @return void * @throws LocalizedException */ - public function processWebhook($responseData) + public function processWebhook($webhookData): void { try { - $order = $this->webhookDataLoader->loadOrderByPaymentId($responseData['paymentId']); + $order = $this->webhookDataLoader->loadOrderByPaymentId($webhookData['paymentId']); $chargeRefundTransaction = $this->transactionBuilder ->build( - $responseData['refundId'], + $webhookData['refundId'], $order, [ - 'payment_id' => $responseData['paymentId'] + 'payment_id' => $webhookData['paymentId'] ], TransactionInterface::TYPE_REFUND - )->setParentTxnId($responseData['paymentId']); + )->setParentTxnId($webhookData['paymentId']); $this->orderRepository->save($order); } catch (\Exception $e) { From 2f9bd65440b10b5b4c6eeac6268b32c782ed1594 Mon Sep 17 00:00:00 2001 From: Konrad Konieczny Date: Wed, 2 Apr 2025 18:43:50 +0200 Subject: [PATCH 105/136] Add check for existing transaction in processWebhook method of PaymentChargeCreated --- Model/Webhook/PaymentChargeCreated.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Model/Webhook/PaymentChargeCreated.php b/Model/Webhook/PaymentChargeCreated.php index 060febe9..ba8b8443 100644 --- a/Model/Webhook/PaymentChargeCreated.php +++ b/Model/Webhook/PaymentChargeCreated.php @@ -4,7 +4,7 @@ namespace Nexi\Checkout\Model\Webhook; -use Magento\Checkout\Exception; +use Magento\Framework\Exception\AlreadyExistsException; use Magento\Framework\Exception\LocalizedException; use Magento\Framework\Exception\NotFoundException; use Magento\Sales\Api\Data\TransactionInterface; @@ -69,6 +69,11 @@ private function processOrder($order, $webhookData): void } $chargeTxnId = $webhookData['data']['chargeId']; + + if ($this->webhookDataLoader->getTransactionByPaymentId($chargeTxnId, TransactionInterface::TYPE_CAPTURE)) { + throw new AlreadyExistsException(__('Transaction already exists.')); + } + $chargeTransaction = $this->transactionBuilder ->build( $chargeTxnId, From 21861c1ebf61087cae4f2e5929ef585557e55527 Mon Sep 17 00:00:00 2001 From: Konrad Konieczny Date: Thu, 3 Apr 2025 12:04:43 +0200 Subject: [PATCH 106/136] remove github actions --- .github/workflows/code-validation.yml | 84 --------------------------- 1 file changed, 84 deletions(-) delete mode 100644 .github/workflows/code-validation.yml diff --git a/.github/workflows/code-validation.yml b/.github/workflows/code-validation.yml deleted file mode 100644 index 7899453d..00000000 --- a/.github/workflows/code-validation.yml +++ /dev/null @@ -1,84 +0,0 @@ -name: Run code validation checks -on: - pull_request: -jobs: - changed-files: - runs-on: ubuntu-latest - name: Gather Changelist - strategy: - matrix: - fetch-depth: - - 2 - base-branch: - - "master" - outputs: - all: ${{ steps.changes.outputs.all }} - php: ${{ steps.changes.outputs.php }} - steps: - - name: Check out code - uses: actions/checkout@v4 - with: - fetch-depth: ${{ matrix.fetch-depth }} - - name: Get changed files - id: changes - run: | - BASE_SHA="${{ github.event.before }}" - if [ "$BASE_SHA" = "0000000000000000000000000000000000000000" ]; then - BASE_SHA="${{ matrix.base-branch }}" - fi - if [[ ! -z "${{ github.event.pull_request.base.sha }}" ]]; then - BASE_SHA="${{ github.event.pull_request.base.sha }}" - fi - echo "all=$(git diff --name-only --diff-filter=ACMRT $BASE_SHA ${{ github.sha }} | xargs)" >> $GITHUB_OUTPUT - echo "php=$(git diff --name-only --diff-filter=ACMRT $BASE_SHA ${{ github.sha }} | grep -E '.ph(p|tml)$' | xargs)" >> $GITHUB_OUTPUT - validate-php: - runs-on: ubuntu-latest - name: Run php code validation and Unit tests - needs: changed-files - if: ${{needs.changed-files.outputs.php}} - strategy: - matrix: - node-version: - - 20 - php-version: ["8.1", "8.2", "8.3"] - dependencies: - - "highest" - composer-options: - - "--no-plugins --no-progress" - steps: - - name: Setup node - uses: actions/setup-node@v4 - with: - node-version: ${{ matrix.node_version }} - - name: Check out code - uses: actions/checkout@v4 - - name: Setup Php - uses: shivammathur/setup-php@v2 - with: - php-version: ${{ matrix.php-version }} - ini-file: development - tools: composer:2.2, cs2pr - env: - COMPOSER_AUTH_JSON: | - { - "http-basic": { - "repo.magento.com": { - "username": "${{ secrets.REPO_MAGENTO_USER }}", - "password": "${{ secrets.REPO_MAGENTO_PASSWORD }}" - } - } - } - - name: Validate Composer Files - run: composer validate - - name: Run Composer install - uses: "ramsey/composer-install@v1" - with: - dependency-versions: "${{ matrix.dependencies }}" - composer-options: "${{ matrix.composer-options }}" - - name: Detect PhpCs violations - run: | - vendor/bin/phpcs --config-set installed_paths ../../magento/magento-coding-standard/,../../phpcompatibility/php-compatibility,../../magento/php-compatibility-fork - vendor/bin/phpcs --standard=Magento2 -q --report=checkstyle ${{needs.changed-files.outputs.php}} | cs2pr --graceful-warnings - - name: Run Unit test - run: | - vendor/bin/phpunit Test/Unit/ From bfaa5fb169f0febab23c854cbe8b7436aa7d9f76 Mon Sep 17 00:00:00 2001 From: Konrad Konieczny Date: Thu, 3 Apr 2025 16:34:13 +0200 Subject: [PATCH 107/136] Refactor PaymentRefundCompleted to handle full refunds and add credit memo processing --- .../Request/CreatePaymentRequestBuilder.php | 3 +- Model/Webhook/PaymentRefundCompleted.php | 84 +++++++++++++------ 2 files changed, 59 insertions(+), 28 deletions(-) diff --git a/Gateway/Request/CreatePaymentRequestBuilder.php b/Gateway/Request/CreatePaymentRequestBuilder.php index 6a800e53..25cf4459 100644 --- a/Gateway/Request/CreatePaymentRequestBuilder.php +++ b/Gateway/Request/CreatePaymentRequestBuilder.php @@ -9,6 +9,7 @@ use Magento\Payment\Gateway\Request\BuilderInterface; use Magento\Sales\Model\Order; use Nexi\Checkout\Gateway\Config\Config; +use Nexi\Checkout\Gateway\Request\NexiCheckout\SalesDocumentItemsBuilder; use NexiCheckout\Model\Request\Item; use NexiCheckout\Model\Request\Payment; use NexiCheckout\Model\Request\Payment\Address; @@ -101,7 +102,7 @@ private function buildItems(Order $order): Order\Item|array unitPrice : (int)($order->getShippingAmount() * 100), grossTotalAmount: (int)($order->getShippingInclTax() * 100), netTotalAmount : (int)($order->getShippingAmount() * 100), - reference : $order->getShippingMethod(), + reference : SalesDocumentItemsBuilder::SHIPPING_COST_REFERENCE, taxRate : (int)($order->getTaxAmount() / $order->getGrandTotal() * 100), taxAmount : (int)($order->getShippingTaxAmount() * 100), ); diff --git a/Model/Webhook/PaymentRefundCompleted.php b/Model/Webhook/PaymentRefundCompleted.php index 4b8c574c..1953a01d 100644 --- a/Model/Webhook/PaymentRefundCompleted.php +++ b/Model/Webhook/PaymentRefundCompleted.php @@ -4,33 +4,28 @@ namespace Nexi\Checkout\Model\Webhook; - use Magento\Framework\Exception\LocalizedException; use Magento\Sales\Api\Data\TransactionInterface; use Magento\Sales\Api\OrderRepositoryInterface; +use Magento\Sales\Api\CreditmemoManagementInterface; +use Magento\Sales\Model\Order; +use Magento\Sales\Model\Order\CreditmemoFactory; use Nexi\Checkout\Model\Transaction\Builder; use Nexi\Checkout\Model\Webhook\Data\WebhookDataLoader; class PaymentRefundCompleted implements WebhookProcessorInterface { - /** - * PaymentRefundCompleted constructor. - * - * @param WebhookDataLoader $webhookDataLoader - * @param Builder $transactionBuilder - * @param OrderRepositoryInterface $orderRepository - */ public function __construct( private readonly WebhookDataLoader $webhookDataLoader, private readonly Builder $transactionBuilder, - private readonly OrderRepositoryInterface $orderRepository + private readonly OrderRepositoryInterface $orderRepository, + private readonly CreditmemoFactory $creditmemoFactory, + private readonly CreditmemoManagementInterface $creditmemoManagement ) { } /** * ProcessWebhook function for 'payment.refund.completed' event. - * TODO: Implement the logic to handle the refund completed event. - * TODO: create credit memo * * @param $webhookData * @@ -39,22 +34,57 @@ public function __construct( */ public function processWebhook($webhookData): void { - try { - $order = $this->webhookDataLoader->loadOrderByPaymentId($webhookData['paymentId']); - - $chargeRefundTransaction = $this->transactionBuilder - ->build( - $webhookData['refundId'], - $order, - [ - 'payment_id' => $webhookData['paymentId'] - ], - TransactionInterface::TYPE_REFUND - )->setParentTxnId($webhookData['paymentId']); - - $this->orderRepository->save($order); - } catch (\Exception $e) { - throw new LocalizedException(__($e->getMessage())); + $order = $this->webhookDataLoader->loadOrderByPaymentId($webhookData['data']['paymentId']); + + $refund = $this->transactionBuilder + ->build( + $webhookData['id'], + $order, + ['payment_id' => $webhookData['data']['paymentId']], + TransactionInterface::TYPE_REFUND + )->setParentTxnId($webhookData['data']['paymentId']) + ->setAdditionalInformation('details', json_encode($webhookData)); + + if ($this->isFullRefund($webhookData, $order)) { + $this->processFullRefund($webhookData, $order); } + + $order->getPayment()->addTransactionCommentsToOrder( + $refund, + __('Payment refund created, amount: %1', $webhookData['data']['amount']['amount'] / 100) + ); + + $this->orderRepository->save($order); + } + + + /** + * Create creditmemo for whole order + * + * @param array $webhookData + * @param Order $order + * + * @return void + */ + public function processFullRefund(array $webhookData, Order $order) + { + $creditmemo = $this->creditmemoFactory->createByOrder($order); + $creditmemo->setTransactionId($webhookData['id']); + + $this->creditmemoManagement->refund($creditmemo); } + + /** + * Amount check + * + * @param array $webhookData + * @param Order $order + * + * @return bool + */ + private function isFullRefund(array $webhookData, Order $order) + { + return $order->getGrandTotal() == $webhookData['data']['amount']['amount']/100; + } + } From b85b2483ffac481a5ef7afca35113fedb6ae3ee2 Mon Sep 17 00:00:00 2001 From: Konrad Konieczny Date: Fri, 4 Apr 2025 08:03:06 +0200 Subject: [PATCH 108/136] Refactor WebhookHandler to use WebhookProcessorInterface and update parameter type hints --- Controller/Payment/Webhook.php | 2 -- Model/WebhookHandler.php | 8 +++++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Controller/Payment/Webhook.php b/Controller/Payment/Webhook.php index 7af7de61..7556e7db 100644 --- a/Controller/Payment/Webhook.php +++ b/Controller/Payment/Webhook.php @@ -101,8 +101,6 @@ public function isAuthorized(): bool { $authString = $this->getRequest()->getHeader('Authorization'); - return true; - if (empty($authString)) { return false; } diff --git a/Model/WebhookHandler.php b/Model/WebhookHandler.php index 01ad2163..4abae53e 100644 --- a/Model/WebhookHandler.php +++ b/Model/WebhookHandler.php @@ -2,12 +2,14 @@ namespace Nexi\Checkout\Model; +use Nexi\Checkout\Model\Webhook\WebhookProcessorInterface; + class WebhookHandler { /** * WebhookHandler constructor. * - * @param array $webhookProcessors + * @param WebhookProcessorInterface[] $webhookProcessors */ public function __construct( private array $webhookProcessors @@ -17,9 +19,9 @@ public function __construct( /** * Handler passes forward on to the appropriate handler. * - * @param $request + * @param $webhookData + * * @return void - * @throws \Exception */ public function handle($webhookData) { From 7b6b11cbbf60300aeea8cfd1f75f9099dfa389bc Mon Sep 17 00:00:00 2001 From: Konrad Konieczny Date: Fri, 4 Apr 2025 08:23:00 +0200 Subject: [PATCH 109/136] Enhance Webhook class with improved constructor documentation and CSRF validation method --- Controller/Payment/Webhook.php | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/Controller/Payment/Webhook.php b/Controller/Payment/Webhook.php index 7556e7db..2213407b 100644 --- a/Controller/Payment/Webhook.php +++ b/Controller/Payment/Webhook.php @@ -18,6 +18,14 @@ class Webhook extends Action implements CsrfAwareActionInterface, HttpPostActionInterface { + /** + * @param Context $context + * @param LoggerInterface $logger + * @param Encryptor $encryptor + * @param Config $config + * @param WebhookHandler $webhookHandler + * @param SerializerInterface $serializer + */ public function __construct( Context $context, private readonly LoggerInterface $logger, @@ -30,6 +38,8 @@ public function __construct( } /** + * Execute the webhook action + * * @return void * @throws Exception */ @@ -74,6 +84,13 @@ public function execute() } + /** + * Allow all requests to this action + * + * @param RequestInterface $request + * + * @return InvalidRequestException|null + */ public function createCsrfValidationException(RequestInterface $request): ?InvalidRequestException { return null; @@ -92,10 +109,9 @@ public function validateForCsrf(RequestInterface $request): ?bool } /** - * @param RequestInterface $request + * Check the authorisation header * - * @return void - * @throws Exception + * @return bool */ public function isAuthorized(): bool { From 16f110615d81d439f91d4a999134b73777814cca Mon Sep 17 00:00:00 2001 From: Konrad Konieczny Date: Fri, 4 Apr 2025 08:23:00 +0200 Subject: [PATCH 110/136] Enhance CreatePaymentRequestBuilder with additional method documentation and clean up Webhook class --- Controller/Payment/Webhook.php | 24 +++++++++++++++---- .../Request/CreatePaymentRequestBuilder.php | 20 ++++++++++++++++ 2 files changed, 39 insertions(+), 5 deletions(-) diff --git a/Controller/Payment/Webhook.php b/Controller/Payment/Webhook.php index 7556e7db..63f7412c 100644 --- a/Controller/Payment/Webhook.php +++ b/Controller/Payment/Webhook.php @@ -18,6 +18,14 @@ class Webhook extends Action implements CsrfAwareActionInterface, HttpPostActionInterface { + /** + * @param Context $context + * @param LoggerInterface $logger + * @param Encryptor $encryptor + * @param Config $config + * @param WebhookHandler $webhookHandler + * @param SerializerInterface $serializer + */ public function __construct( Context $context, private readonly LoggerInterface $logger, @@ -30,6 +38,8 @@ public function __construct( } /** + * Execute the webhook action + * * @return void * @throws Exception */ @@ -58,7 +68,6 @@ public function execute() 'webhook_data' => json_encode($this->getRequest()->getContent()), 'payment_id' => $this->getRequest()->getParam('payment_id'), ] - ); $this->_response->setHttpResponseCode(200); } catch (Exception $e) { @@ -73,7 +82,13 @@ public function execute() } } - + /** + * Allow all requests to this action + * + * @param RequestInterface $request + * + * @return InvalidRequestException|null + */ public function createCsrfValidationException(RequestInterface $request): ?InvalidRequestException { return null; @@ -92,10 +107,9 @@ public function validateForCsrf(RequestInterface $request): ?bool } /** - * @param RequestInterface $request + * Check the authorisation header * - * @return void - * @throws Exception + * @return bool */ public function isAuthorized(): bool { diff --git a/Gateway/Request/CreatePaymentRequestBuilder.php b/Gateway/Request/CreatePaymentRequestBuilder.php index 25cf4459..c825014d 100644 --- a/Gateway/Request/CreatePaymentRequestBuilder.php +++ b/Gateway/Request/CreatePaymentRequestBuilder.php @@ -39,6 +39,8 @@ public function __construct( } /** + * Build the request for creating a payment + * * @param array $buildSubject * * @return array @@ -58,6 +60,8 @@ public function build(array $buildSubject): array } /** + * Build the Sdk order object + * * @param $order * * @return Payment\Order @@ -73,6 +77,8 @@ public function buildOrder($order): Payment\Order } /** + * Build the Sdk items object + * * @param Order $order * * @return Order\Item|array @@ -112,6 +118,8 @@ private function buildItems(Order $order): Order\Item|array } /** + * Build The Sdk payment object + * * @param Order $order * * @return Payment @@ -127,6 +135,8 @@ private function buildPayment(Order $order): Payment } /** + * Build the webhooks for the payment + * * @return array * * added all for now, we need to check wh @@ -147,6 +157,8 @@ public function buildWebhooks(): array } /** + * Build the checkout object + * * @param Order $order * * @return HostedCheckout|EmbeddedCheckout @@ -176,6 +188,14 @@ public function buildCheckout(Order $order): HostedCheckout|EmbeddedCheckout ); } + /** + * Build the consumer object + * + * @param Order $order + * + * @return Consumer + * @throws NoSuchEntityException + */ private function buildConsumer(Order $order): Consumer { return new Consumer( From af4ebcfb3b570d2879431b1b288251363d139f45 Mon Sep 17 00:00:00 2001 From: Konrad Konieczny Date: Fri, 4 Apr 2025 10:40:19 +0200 Subject: [PATCH 111/136] Enhance CreatePaymentRequestBuilder with type hinting for buildOrder method and improve code formatting --- .../Request/CreatePaymentRequestBuilder.php | 48 +++++++++---------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/Gateway/Request/CreatePaymentRequestBuilder.php b/Gateway/Request/CreatePaymentRequestBuilder.php index c825014d..2d8a12b2 100644 --- a/Gateway/Request/CreatePaymentRequestBuilder.php +++ b/Gateway/Request/CreatePaymentRequestBuilder.php @@ -62,11 +62,11 @@ public function build(array $buildSubject): array /** * Build the Sdk order object * - * @param $order + * @param Order $order * * @return Payment\Order */ - public function buildOrder($order): Payment\Order + public function buildOrder(Order $order): Payment\Order { return new Payment\Order( items : $this->buildItems($order), @@ -183,9 +183,10 @@ public function buildCheckout(Order $order): HostedCheckout|EmbeddedCheckout isAutoCharge : $this->config->getPaymentAction() == 'authorize_capture', merchantHandlesConsumerData: $this->config->getMerchantHandlesConsumerData(), countryCode : $this->countryInformationAcquirer->getCountryInfo( - $this->config->getCountryCode() - )->getThreeLetterAbbreviation(), + $this->config->getCountryCode() + )->getThreeLetterAbbreviation(), ); + } /** @@ -202,28 +203,27 @@ private function buildConsumer(Order $order): Consumer email : $order->getCustomerEmail(), reference : $order->getCustomerId(), shippingAddress: new Address( - addressLine1: $order->getShippingAddress()->getStreetLine(1), - addressLine2: $order->getShippingAddress()->getStreetLine(2), - postalCode : $order->getShippingAddress()->getPostcode(), - city : $order->getShippingAddress()->getCity(), - country : $this->countryInformationAcquirer->getCountryInfo( - $this->config->getCountryCode() - )->getThreeLetterAbbreviation(), - ), + addressLine1: $order->getShippingAddress()->getStreetLine(1), + addressLine2: $order->getShippingAddress()->getStreetLine(2), + postalCode : $order->getShippingAddress()->getPostcode(), + city : $order->getShippingAddress()->getCity(), + country : $this->countryInformationAcquirer->getCountryInfo( + $this->config->getCountryCode() + )->getThreeLetterAbbreviation(), + ), billingAddress : new Address( - addressLine1: $order->getBillingAddress()->getStreetLine(1), - addressLine2: $order->getBillingAddress()->getStreetLine(2), - postalCode : $order->getBillingAddress()->getPostcode(), - city : $order->getBillingAddress()->getCity(), - country : $this->countryInformationAcquirer->getCountryInfo( - $this->config->getCountryCode() - )->getThreeLetterAbbreviation(), - ), - + addressLine1: $order->getBillingAddress()->getStreetLine(1), + addressLine2: $order->getBillingAddress()->getStreetLine(2), + postalCode : $order->getBillingAddress()->getPostcode(), + city : $order->getBillingAddress()->getCity(), + country : $this->countryInformationAcquirer->getCountryInfo( + $this->config->getCountryCode() + )->getThreeLetterAbbreviation(), + ), privatePerson : new PrivatePerson( - firstName: $order->getCustomerFirstname(), - lastName : $order->getCustomerLastname(), - ) + firstName: $order->getCustomerFirstname(), + lastName : $order->getCustomerLastname(), + ) ); } } From 45c700fd6182670728327630540e4a4437a5fb38 Mon Sep 17 00:00:00 2001 From: Konrad Konieczny Date: Fri, 4 Apr 2025 10:46:24 +0200 Subject: [PATCH 112/136] Enhance PaymentRefundCompleted with constructor parameter documentation and update processWebhook method type hint --- Model/Webhook/PaymentRefundCompleted.php | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/Model/Webhook/PaymentRefundCompleted.php b/Model/Webhook/PaymentRefundCompleted.php index 1953a01d..9f604c6c 100644 --- a/Model/Webhook/PaymentRefundCompleted.php +++ b/Model/Webhook/PaymentRefundCompleted.php @@ -15,6 +15,13 @@ class PaymentRefundCompleted implements WebhookProcessorInterface { + /** + * @param WebhookDataLoader $webhookDataLoader + * @param Builder $transactionBuilder + * @param OrderRepositoryInterface $orderRepository + * @param CreditmemoFactory $creditmemoFactory + * @param CreditmemoManagementInterface $creditmemoManagement + */ public function __construct( private readonly WebhookDataLoader $webhookDataLoader, private readonly Builder $transactionBuilder, @@ -27,12 +34,12 @@ public function __construct( /** * ProcessWebhook function for 'payment.refund.completed' event. * - * @param $webhookData + * @param array $webhookData * * @return void * @throws LocalizedException */ - public function processWebhook($webhookData): void + public function processWebhook(array $webhookData): void { $order = $this->webhookDataLoader->loadOrderByPaymentId($webhookData['data']['paymentId']); @@ -57,7 +64,6 @@ public function processWebhook($webhookData): void $this->orderRepository->save($order); } - /** * Create creditmemo for whole order * @@ -86,5 +92,4 @@ private function isFullRefund(array $webhookData, Order $order) { return $order->getGrandTotal() == $webhookData['data']['amount']['amount']/100; } - } From a10cf36d40830c5485fbf3205a56d31254da8204 Mon Sep 17 00:00:00 2001 From: Konrad Konieczny Date: Fri, 4 Apr 2025 10:53:20 +0200 Subject: [PATCH 113/136] Enhance PaymentRefundCompleted and CreatePaymentRequestBuilder with type hinting for methods --- Gateway/Request/CreatePaymentRequestBuilder.php | 1 - Model/Webhook/PaymentRefundCompleted.php | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/Gateway/Request/CreatePaymentRequestBuilder.php b/Gateway/Request/CreatePaymentRequestBuilder.php index 2d8a12b2..53509120 100644 --- a/Gateway/Request/CreatePaymentRequestBuilder.php +++ b/Gateway/Request/CreatePaymentRequestBuilder.php @@ -186,7 +186,6 @@ public function buildCheckout(Order $order): HostedCheckout|EmbeddedCheckout $this->config->getCountryCode() )->getThreeLetterAbbreviation(), ); - } /** diff --git a/Model/Webhook/PaymentRefundCompleted.php b/Model/Webhook/PaymentRefundCompleted.php index 9f604c6c..58a318ca 100644 --- a/Model/Webhook/PaymentRefundCompleted.php +++ b/Model/Webhook/PaymentRefundCompleted.php @@ -72,7 +72,7 @@ public function processWebhook(array $webhookData): void * * @return void */ - public function processFullRefund(array $webhookData, Order $order) + public function processFullRefund(array $webhookData, Order $order): void { $creditmemo = $this->creditmemoFactory->createByOrder($order); $creditmemo->setTransactionId($webhookData['id']); @@ -88,7 +88,7 @@ public function processFullRefund(array $webhookData, Order $order) * * @return bool */ - private function isFullRefund(array $webhookData, Order $order) + private function isFullRefund(array $webhookData, Order $order): bool { return $order->getGrandTotal() == $webhookData['data']['amount']['amount']/100; } From 1acf4135ac21c6d353027e1bfd9d139ff3fd523d Mon Sep 17 00:00:00 2001 From: Konrad Konieczny Date: Fri, 4 Apr 2025 10:55:47 +0200 Subject: [PATCH 114/136] Enhance WebhookHandler with constructor documentation and type hinting for handle method --- Model/WebhookHandler.php | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Model/WebhookHandler.php b/Model/WebhookHandler.php index 4abae53e..7e26b030 100644 --- a/Model/WebhookHandler.php +++ b/Model/WebhookHandler.php @@ -7,8 +7,6 @@ class WebhookHandler { /** - * WebhookHandler constructor. - * * @param WebhookProcessorInterface[] $webhookProcessors */ public function __construct( @@ -19,11 +17,11 @@ public function __construct( /** * Handler passes forward on to the appropriate handler. * - * @param $webhookData + * @param array $webhookData * * @return void */ - public function handle($webhookData) + public function handle(array $webhookData): void { $event = $webhookData['event']; if (array_key_exists($event, $this->webhookProcessors)) { From 9d241d765bda191e47c21f41b42a3ce45614f291 Mon Sep 17 00:00:00 2001 From: Konrad Konieczny Date: Mon, 7 Apr 2025 20:09:30 +0200 Subject: [PATCH 115/136] Add test API keys to configuration and update key retrieval logic --- Gateway/Config/Config.php | 12 ++++++++++-- etc/adminhtml/system.xml | 9 +++++++++ etc/config.xml | 3 +++ 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/Gateway/Config/Config.php b/Gateway/Config/Config.php index 18c26a27..cb91c60f 100644 --- a/Gateway/Config/Config.php +++ b/Gateway/Config/Config.php @@ -64,7 +64,11 @@ public function isActive(): bool */ public function getApiKey(): ?string { - return $this->getValue('api_key'); + if ($this->isLiveMode()) { + return $this->getValue('api_key'); + } + + return $this->getValue('test_api_key'); } /** @@ -74,7 +78,11 @@ public function getApiKey(): ?string */ public function getCheckoutKey() { - return $this->getValue('checkout_key'); + if ($this->isLiveMode()) { + return $this->getValue('checkout_key'); + } + + return $this->getValue('test_checkout_key'); } /** diff --git a/etc/adminhtml/system.xml b/etc/adminhtml/system.xml index e522465d..d007f63f 100644 --- a/etc/adminhtml/system.xml +++ b/etc/adminhtml/system.xml @@ -30,6 +30,15 @@ Magento\Config\Model\Config\Backend\Encrypted + + + Magento\Config\Model\Config\Backend\Encrypted + + + + Magento\Config\Model\Config\Backend\Encrypted + diff --git a/etc/config.xml b/etc/config.xml index af3f785a..fb9e9801 100644 --- a/etc/config.xml +++ b/etc/config.xml @@ -34,6 +34,9 @@ cc_type,cc_number,avsPostalCodeResponseCode,avsStreetAddressResponseCode,cvvResponseCode,processorAuthorizationCode,processorResponseCode,processorResponseText,liabilityShifted,liabilityShiftPossible nexi_group + + + HostedPaymentPage 1 From 4301ffc95a4e5062185584ece8531c8bd7f3f26a Mon Sep 17 00:00:00 2001 From: Konrad Konieczny Date: Mon, 14 Apr 2025 08:15:23 +0200 Subject: [PATCH 116/136] Add support for test API key and enhance payment processing logic --- .../System/Config/TestConnection.php | 6 ++-- .../System/Config/TestConnection.php | 36 ++++++++++++------- Gateway/Config/Config.php | 5 ++- Gateway/Handler/Capture.php | 11 ++---- Gateway/Handler/CreatePayment.php | 9 ++++- Gateway/Handler/RefundCharge.php | 9 ++++- Gateway/Request/CaptureRequestBuilder.php | 24 +------------ .../Request/CreatePaymentRequestBuilder.php | 35 +++++++++++------- .../SalesDocumentItemsBuilder.php | 6 ++-- Gateway/Request/RefundRequestBuilder.php | 30 ++++++---------- Gateway/Response/Handler.php | 14 -------- Model/Config/Source/Environment.php | 4 +-- Model/Config/Source/IntegrationType.php | 1 - Model/Config/Source/PaymentAction.php | 31 ---------------- Model/Transaction/Builder.php | 5 +-- Model/Ui/ConfigProvider.php | 3 +- Model/Webhook/Data/WebhookDataLoader.php | 6 ++-- Model/Webhook/PaymentCheckoutCompleted.php | 14 -------- Model/Webhook/PaymentRefundInitiated.php | 14 -------- Model/Webhook/PaymentReservationFailed.php | 14 -------- Model/WebhookHandler.php | 10 ++++++ Plugin/GuestPaymentInformationManagement.php | 8 +++++ Plugin/PaymentInformationManagement.php | 12 +++++-- etc/adminhtml/system.xml | 10 +++--- etc/di.xml | 16 --------- 25 files changed, 130 insertions(+), 203 deletions(-) delete mode 100644 Gateway/Response/Handler.php delete mode 100644 Model/Config/Source/PaymentAction.php delete mode 100644 Model/Webhook/PaymentCheckoutCompleted.php delete mode 100644 Model/Webhook/PaymentRefundInitiated.php delete mode 100644 Model/Webhook/PaymentReservationFailed.php diff --git a/Block/Adminhtml/System/Config/TestConnection.php b/Block/Adminhtml/System/Config/TestConnection.php index be199ef8..a4507cdd 100644 --- a/Block/Adminhtml/System/Config/TestConnection.php +++ b/Block/Adminhtml/System/Config/TestConnection.php @@ -75,11 +75,13 @@ protected function _getElementHtml(AbstractElement $element) private function getFieldMapping(): array { $apiKeyPath = $this->configStructure->getElementByConfigPath('payment/nexi/api_key'); + $testApiKeyPath = $this->configStructure->getElementByConfigPath('payment/nexi/test_api_key'); $environmentPath = $this->configStructure->getElementByConfigPath('payment/nexi/environment'); return [ - 'environment' => str_replace('/', '_', $environmentPath->getPath()), - 'api_key' => str_replace('/', '_', $apiKeyPath->getPath()) + 'environment' => str_replace('/', '_', $environmentPath->getPath()), + 'api_key' => str_replace('/', '_', $apiKeyPath->getPath()), + 'test_api_key' => str_replace('/', '_', $testApiKeyPath->getPath()) ]; } } diff --git a/Controller/Adminhtml/System/Config/TestConnection.php b/Controller/Adminhtml/System/Config/TestConnection.php index 6533988a..afc731d7 100644 --- a/Controller/Adminhtml/System/Config/TestConnection.php +++ b/Controller/Adminhtml/System/Config/TestConnection.php @@ -7,11 +7,12 @@ use Magento\Backend\App\Action; use Magento\Backend\App\Action\Context; use Magento\Framework\App\Action\HttpPostActionInterface; +use Magento\Framework\App\Config\ScopeConfigInterface; use Magento\Framework\Controller\Result\Json; use Magento\Framework\Controller\Result\JsonFactory; -use Magento\Framework\Exception\LocalizedException; use Magento\Framework\Filter\StripTags; use Magento\Framework\Url; +use Magento\Store\Model\ScopeInterface; use Nexi\Checkout\Gateway\Config\Config; use Nexi\Checkout\Model\Config\Source\Environment; use NexiCheckout\Api\Exception\PaymentApiException; @@ -39,10 +40,11 @@ class TestConnection extends Action implements HttpPostActionInterface public function __construct( Context $context, private readonly JsonFactory $resultJsonFactory, - private readonly StripTags $tagFilter, - private readonly PaymentApiFactory $paymentApiFactory, - private readonly Config $config, - private readonly Url $url + private readonly StripTags $tagFilter, + private readonly PaymentApiFactory $paymentApiFactory, + private readonly Config $config, + private readonly Url $url, + private readonly ScopeConfigInterface $scopeConfig ) { parent::__construct($context); } @@ -51,6 +53,7 @@ public function __construct( * Check for connection to server * * @return Json + * @throws \JsonException */ public function execute() { @@ -59,13 +62,22 @@ public function execute() 'errorMessage' => '', ]; $options = $this->getRequest()->getParams(); - if ($options['api_key'] == '******') { - $options['api_key'] = $this->config->getApiKey(); + $isLiveMode = $options['environment'] == Environment::LIVE; + + $apiKey = $isLiveMode ? $options['api_key'] : $options['test_api_key']; + + if ($apiKey == '******') { + $apiKey = $isLiveMode ? $this->config->getApiKey() : $this->config->getTestApiKey(); } + try { - $api = $this->paymentApiFactory->create( + $api = $this->paymentApiFactory->create( secretKey : $options['api_key'], - isLiveMode: $options['environment'] == Environment::LIVE + isLiveMode: $isLiveMode + ); + $currency = $this->scopeConfig->getValue( + 'currency/options/default', + ScopeInterface::SCOPE_STORE ); $payment = $api->createEmbeddedPayment( @@ -74,7 +86,7 @@ public function execute() [ new Item('test', 1, 'pcs', 1, 1, 1, 'test') ], - 'USD', + $currency, 1 ), new Payment\EmbeddedCheckout( @@ -84,8 +96,8 @@ public function execute() ) ); - if ($payment->getPaymentId() ) { - $api->terminate($payment->getPaymentId()); + if ($payment->getPaymentId()) { + $api->terminate($payment->getPaymentId()); } } catch (PaymentApiException $e) { diff --git a/Gateway/Config/Config.php b/Gateway/Config/Config.php index cb91c60f..92bccbf0 100644 --- a/Gateway/Config/Config.php +++ b/Gateway/Config/Config.php @@ -4,6 +4,7 @@ use Magento\Framework\App\Config\ScopeConfigInterface; use Magento\Payment\Gateway\Config\Config as MagentoConfig; +use Magento\Payment\Model\MethodInterface; use Nexi\Checkout\Model\Config\Source\Environment; class Config extends MagentoConfig @@ -132,7 +133,9 @@ public function getWebhookSecret(): string */ public function getPaymentAction(): string { - return $this->getValue('payment_action'); + return $this->getValue('is_auto_capture') ? + MethodInterface::ACTION_AUTHORIZE_CAPTURE : + MethodInterface::ACTION_AUTHORIZE; } /** diff --git a/Gateway/Handler/Capture.php b/Gateway/Handler/Capture.php index 28b83129..62704d58 100644 --- a/Gateway/Handler/Capture.php +++ b/Gateway/Handler/Capture.php @@ -3,10 +3,10 @@ namespace Nexi\Checkout\Gateway\Handler; use Magento\Payment\Gateway\Helper\SubjectReader; +use Magento\Payment\Gateway\Response\HandlerInterface; use NexiCheckout\Model\Result\ChargeResult; -use NexiCheckout\Model\Result\RefundChargeResult; -class Capture implements \Magento\Payment\Gateway\Response\HandlerInterface +class Capture implements HandlerInterface { /** @@ -20,12 +20,7 @@ public function __construct( } /** - * Handle response - * - * @param array $handlingSubject - * @param array $response - * - * @return void + * @inheritDoc */ public function handle(array $handlingSubject, array $response) { diff --git a/Gateway/Handler/CreatePayment.php b/Gateway/Handler/CreatePayment.php index 1555d89b..d20b1730 100644 --- a/Gateway/Handler/CreatePayment.php +++ b/Gateway/Handler/CreatePayment.php @@ -3,15 +3,22 @@ namespace Nexi\Checkout\Gateway\Handler; use Magento\Payment\Gateway\Helper\SubjectReader; +use Magento\Payment\Gateway\Response\HandlerInterface; -class CreatePayment implements \Magento\Payment\Gateway\Response\HandlerInterface +class CreatePayment implements HandlerInterface { + /** + * @param SubjectReader $subjectReader + */ public function __construct( private readonly SubjectReader $subjectReader ) { } + /** + * @inheritDoc + */ public function handle(array $handlingSubject, array $response) { $paymentDO = $this->subjectReader->readPayment($handlingSubject); diff --git a/Gateway/Handler/RefundCharge.php b/Gateway/Handler/RefundCharge.php index ec697fab..351dea8e 100644 --- a/Gateway/Handler/RefundCharge.php +++ b/Gateway/Handler/RefundCharge.php @@ -3,16 +3,23 @@ namespace Nexi\Checkout\Gateway\Handler; use Magento\Payment\Gateway\Helper\SubjectReader; +use Magento\Payment\Gateway\Response\HandlerInterface; use NexiCheckout\Model\Result\RefundChargeResult; -class RefundCharge implements \Magento\Payment\Gateway\Response\HandlerInterface +class RefundCharge implements HandlerInterface { + /** + * @param SubjectReader $subjectReader + */ public function __construct( private readonly SubjectReader $subjectReader ) { } + /** + * @inheritDoc + */ public function handle(array $handlingSubject, array $response) { $paymentDO = $this->subjectReader->readPayment($handlingSubject); diff --git a/Gateway/Request/CaptureRequestBuilder.php b/Gateway/Request/CaptureRequestBuilder.php index 3ce2aeef..326b710e 100644 --- a/Gateway/Request/CaptureRequestBuilder.php +++ b/Gateway/Request/CaptureRequestBuilder.php @@ -6,7 +6,6 @@ use Magento\Payment\Gateway\Request\BuilderInterface; use Magento\Sales\Model\Order; use Nexi\Checkout\Gateway\Request\NexiCheckout\SalesDocumentItemsBuilder; -use NexiCheckout\Model\Request\Charge\Shipping; use NexiCheckout\Model\Request\PartialCharge; class CaptureRequestBuilder implements BuilderInterface @@ -22,7 +21,7 @@ public function __construct( } /** - * Build request + * Build nexi PartialCharge request * * @param array $buildSubject * @@ -45,25 +44,4 @@ public function build(array $buildSubject): array ] ]; } - - /** - * Get shipping information - * - * @param Order $order - * - * @return Shipping|null - */ - private function getShipping(Order $order) - { - if ($order->getShipmentsCollection()->getSize() > 0) { - /** @var Order\Shipment $shipping */ - $shipping = $order->getShipmentsCollection()->getLastItem(); - return new Shipping( - $shipping->getTracksCollection()->getLastItem()->getTrackNumber(), - $shipping->getTracksCollection()->getLastItem()->getCarrierCode() - ); - } - - return null; - } } diff --git a/Gateway/Request/CreatePaymentRequestBuilder.php b/Gateway/Request/CreatePaymentRequestBuilder.php index 53509120..352059af 100644 --- a/Gateway/Request/CreatePaymentRequestBuilder.php +++ b/Gateway/Request/CreatePaymentRequestBuilder.php @@ -10,6 +10,7 @@ use Magento\Sales\Model\Order; use Nexi\Checkout\Gateway\Config\Config; use Nexi\Checkout\Gateway\Request\NexiCheckout\SalesDocumentItemsBuilder; +use Nexi\Checkout\Model\WebhookHandler; use NexiCheckout\Model\Request\Item; use NexiCheckout\Model\Request\Payment; use NexiCheckout\Model\Request\Payment\Address; @@ -18,7 +19,6 @@ use NexiCheckout\Model\Request\Payment\HostedCheckout; use NexiCheckout\Model\Request\Payment\IntegrationTypeEnum; use NexiCheckout\Model\Request\Payment\PrivatePerson; -use NexiCheckout\Model\Webhook\EventNameEnum; class CreatePaymentRequestBuilder implements BuilderInterface { @@ -29,12 +29,14 @@ class CreatePaymentRequestBuilder implements BuilderInterface * @param Config $config * @param CountryInformationAcquirerInterface $countryInformationAcquirer * @param EncryptorInterface $encryptor + * @param WebhookHandler $webhookHandler */ public function __construct( private readonly UrlInterface $url, private readonly Config $config, private readonly CountryInformationAcquirerInterface $countryInformationAcquirer, - private readonly EncryptorInterface $encryptor + private readonly EncryptorInterface $encryptor, + private readonly WebhookHandler $webhookHandler ) { } @@ -144,10 +146,10 @@ private function buildPayment(Order $order): Payment public function buildWebhooks(): array { $webhooks = []; - foreach (EventNameEnum::cases() as $eventName) { + foreach ($this->webhookHandler->getWebhookProcessors() as $eventName => $processor) { $baseUrl = $this->url->getBaseUrl(); $webhooks[] = new Payment\Webhook( - eventName : $eventName->value, + eventName : $eventName, url : $baseUrl . self::NEXI_PAYMENT_WEBHOOK_PATH, authorization: $this->encryptor->hash($this->config->getWebhookSecret()) ); @@ -182,9 +184,7 @@ public function buildCheckout(Order $order): HostedCheckout|EmbeddedCheckout consumer : $this->buildConsumer($order), isAutoCharge : $this->config->getPaymentAction() == 'authorize_capture', merchantHandlesConsumerData: $this->config->getMerchantHandlesConsumerData(), - countryCode : $this->countryInformationAcquirer->getCountryInfo( - $this->config->getCountryCode() - )->getThreeLetterAbbreviation(), + countryCode : $this->getThreeLetterCountryCode(), ); } @@ -206,18 +206,14 @@ private function buildConsumer(Order $order): Consumer addressLine2: $order->getShippingAddress()->getStreetLine(2), postalCode : $order->getShippingAddress()->getPostcode(), city : $order->getShippingAddress()->getCity(), - country : $this->countryInformationAcquirer->getCountryInfo( - $this->config->getCountryCode() - )->getThreeLetterAbbreviation(), + country : $this->getThreeLetterCountryCode(), ), billingAddress : new Address( addressLine1: $order->getBillingAddress()->getStreetLine(1), addressLine2: $order->getBillingAddress()->getStreetLine(2), postalCode : $order->getBillingAddress()->getPostcode(), city : $order->getBillingAddress()->getCity(), - country : $this->countryInformationAcquirer->getCountryInfo( - $this->config->getCountryCode() - )->getThreeLetterAbbreviation(), + country : $this->getThreeLetterCountryCode(), ), privatePerson : new PrivatePerson( firstName: $order->getCustomerFirstname(), @@ -225,4 +221,17 @@ private function buildConsumer(Order $order): Consumer ) ); } + + /** + * Get the three-letter country code + * + * @return string + * @throws NoSuchEntityException + */ + public function getThreeLetterCountryCode(): string + { + return $this->countryInformationAcquirer->getCountryInfo( + $this->config->getCountryCode() + )->getThreeLetterAbbreviation(); + } } diff --git a/Gateway/Request/NexiCheckout/SalesDocumentItemsBuilder.php b/Gateway/Request/NexiCheckout/SalesDocumentItemsBuilder.php index b7b9e9a4..4c1fe02c 100644 --- a/Gateway/Request/NexiCheckout/SalesDocumentItemsBuilder.php +++ b/Gateway/Request/NexiCheckout/SalesDocumentItemsBuilder.php @@ -8,7 +8,7 @@ class SalesDocumentItemsBuilder { - const SHIPPING_COST_REFERENCE = 'shipping_cost_ref'; + public const SHIPPING_COST_REFERENCE = 'shipping_cost_ref'; /** * Build sales document items for the given sales object @@ -43,7 +43,9 @@ public function build(CreditmemoInterface|InvoiceInterface $salesObject): array grossTotalAmount: (int)($salesObject->getShippingInclTax() * 100), netTotalAmount : (int)($salesObject->getShippingAmount() * 100), reference : self::SHIPPING_COST_REFERENCE, - taxRate : $salesObject->getGrandTotal() ? (int)($salesObject->getTaxAmount() / $salesObject->getGrandTotal() * 100) : 0, + taxRate : $salesObject->getGrandTotal() ? + (int)($salesObject->getTaxAmount() / $salesObject->getGrandTotal() * 100) : + 0, taxAmount : (int)($salesObject->getShippingTaxAmount() * 100), ); } diff --git a/Gateway/Request/RefundRequestBuilder.php b/Gateway/Request/RefundRequestBuilder.php index 060437ff..ca9446bc 100644 --- a/Gateway/Request/RefundRequestBuilder.php +++ b/Gateway/Request/RefundRequestBuilder.php @@ -10,11 +10,21 @@ class RefundRequestBuilder implements BuilderInterface { + /** + * @param SalesDocumentItemsBuilder $documentItemsBuilder + */ public function __construct( private readonly SalesDocumentItemsBuilder $documentItemsBuilder ) { } + /** + * Build nexi PartialRefundCharge request + * + * @param array $buildSubject + * + * @return array + */ public function build(array $buildSubject): array { /** @var Order $order */ @@ -32,24 +42,4 @@ public function build(array $buildSubject): array ] ]; } - - /** - * @param Order $order - * - * @return Shipping|null - */ - private function getShipping(Order $order) - { - if ($order->getShipmentsCollection()->getSize() > 0) { - /** @var Order\Shipment $shipping */ - $shipping = $order->getShipmentsCollection()->getLastItem(); - return new Shipping( - $shipping->getTracksCollection()->getLastItem()->getTrackNumber(), - $shipping->getTracksCollection()->getLastItem()->getCarrierCode() - ); - } - - return null; - } - } diff --git a/Gateway/Response/Handler.php b/Gateway/Response/Handler.php deleted file mode 100644 index 392a334f..00000000 --- a/Gateway/Response/Handler.php +++ /dev/null @@ -1,14 +0,0 @@ - MethodInterface::ACTION_AUTHORIZE, - 'label' => __('Authorize'), - ], - [ - 'value' => MethodInterface::ACTION_AUTHORIZE_CAPTURE, - 'label' => __('Authorize and Capture'), - ] - ]; - } -} diff --git a/Model/Transaction/Builder.php b/Model/Transaction/Builder.php index 660f7a6d..22de655e 100644 --- a/Model/Transaction/Builder.php +++ b/Model/Transaction/Builder.php @@ -25,13 +25,14 @@ public function __construct( /** * Build transaction * + * @param string $transactionId * @param Order $order * @param mixed $transactionData - * @param null $action + * @param string $action * * @return TransactionInterface */ - public function build($transactionId, Order $order, $transactionData, $action ): TransactionInterface + public function build($transactionId, Order $order, $transactionData, $action): TransactionInterface { return $this->transactionBuilder->setOrder($order) ->setPayment($order->getPayment()) diff --git a/Model/Ui/ConfigProvider.php b/Model/Ui/ConfigProvider.php index d3b39844..efae8bff 100644 --- a/Model/Ui/ConfigProvider.php +++ b/Model/Ui/ConfigProvider.php @@ -3,6 +3,7 @@ namespace Nexi\Checkout\Model\Ui; use Magento\Checkout\Model\ConfigProviderInterface; +use Magento\Framework\Exception\LocalizedException; use Nexi\Checkout\Gateway\Config\Config; use Magento\Payment\Helper\Data as PaymentHelper; @@ -24,7 +25,7 @@ public function __construct( * Returns Nexi configuration values. * * @return array|\array[][] - * @throws \Magento\Framework\Exception\LocalizedException + * @throws LocalizedException */ public function getConfig() { diff --git a/Model/Webhook/Data/WebhookDataLoader.php b/Model/Webhook/Data/WebhookDataLoader.php index 1c497912..c62b62bf 100644 --- a/Model/Webhook/Data/WebhookDataLoader.php +++ b/Model/Webhook/Data/WebhookDataLoader.php @@ -77,11 +77,11 @@ public function getTransactionByOrderId($orderId, $txnType = TransactionInterfac /** * LoadOrderByPaymentId function. * - * @param $paymentId + * @param string $paymentId + * * @return mixed - * @throws LocalizedException */ - public function loadOrderByPaymentId($paymentId): Order + public function loadOrderByPaymentId(string $paymentId): Order { $transaction = $this->getTransactionByPaymentId($paymentId); $order = $transaction->getOrder(); diff --git a/Model/Webhook/PaymentCheckoutCompleted.php b/Model/Webhook/PaymentCheckoutCompleted.php deleted file mode 100644 index c9aaf69f..00000000 --- a/Model/Webhook/PaymentCheckoutCompleted.php +++ /dev/null @@ -1,14 +0,0 @@ -webhookProcessors[$event]->processWebhook($webhookData); } } + + /** + * Get all registered webhook processors. + * + * @return WebhookProcessorInterface[] + */ + public function getWebhookProcessors(): array + { + return $this->webhookProcessors; + } } diff --git a/Plugin/GuestPaymentInformationManagement.php b/Plugin/GuestPaymentInformationManagement.php index 195072e9..d2c5c4ff 100644 --- a/Plugin/GuestPaymentInformationManagement.php +++ b/Plugin/GuestPaymentInformationManagement.php @@ -10,6 +10,10 @@ class GuestPaymentInformationManagement { + /** + * @param Session $checkoutSession + * @param LoggerInterface $logger + */ public function __construct( private readonly Session $checkoutSession, private readonly LoggerInterface $logger @@ -17,6 +21,8 @@ public function __construct( } /** + * Ads redirect URL to the response after placing an order. + * * @param Subject $subject * @param $result * @@ -41,6 +47,8 @@ public function afterSavePaymentInformationAndPlaceOrder( } /** + * Get the redirect URL from the order payment information. + * * @return string[] */ private function getRedirectUrl() diff --git a/Plugin/PaymentInformationManagement.php b/Plugin/PaymentInformationManagement.php index 22c2abe9..5fb8bde7 100644 --- a/Plugin/PaymentInformationManagement.php +++ b/Plugin/PaymentInformationManagement.php @@ -5,13 +5,15 @@ use Exception; use Magento\Checkout\Model\PaymentInformationManagement as Subject; use Magento\Checkout\Model\Session; -use Magento\Framework\Exception\LocalizedException; -use Magento\Framework\Exception\NoSuchEntityException; use Psr\Log\LoggerInterface; class PaymentInformationManagement { + /** + * @param Session $checkoutSession + * @param LoggerInterface $logger + */ public function __construct( private readonly Session $checkoutSession, private readonly LoggerInterface $logger @@ -19,8 +21,10 @@ public function __construct( } /** + * Add redirect URL to the response after placing an order. + * * @param Subject $subject - * @param $result + * @param false|mixed|string $result * * @return false|mixed|string */ @@ -42,6 +46,8 @@ public function afterSavePaymentInformationAndPlaceOrder( } /** + * Get the redirect URL from the order payment information. + * * @return string[] */ private function getRedirectUrl() diff --git a/etc/adminhtml/system.xml b/etc/adminhtml/system.xml index d007f63f..b1451054 100644 --- a/etc/adminhtml/system.xml +++ b/etc/adminhtml/system.xml @@ -6,7 +6,7 @@ - + Magento\Config\Model\Config\Source\Yesno @@ -65,11 +65,11 @@ Nexi\Checkout\Model\Config\Source\IntegrationType - - - Nexi\Checkout\Model\Config\Source\PaymentAction - If set to "Authorize and Capture", the transaction will be charged automatically after the + + Magento\Config\Model\Config\Source\Yesno + If set to "Yes", the transaction will be charged automatically after the reservation has been accepted. diff --git a/etc/di.xml b/etc/di.xml index 9cad8531..fbd3a0de 100644 --- a/etc/di.xml +++ b/etc/di.xml @@ -66,14 +66,7 @@ NexiCommandCreatePayment Nexi\Checkout\Gateway\Command\Initialize NexiCommandCapture - NexiCommandRefund - - - - - - @@ -111,12 +104,6 @@ - - - NexiConfig - - - GuzzleHttp\Client @@ -198,7 +185,6 @@ - Nexi\Checkout\Model\Webhook\PaymentCheckoutCompleted Nexi\Checkout\Model\Webhook\PaymentCancelFailed Nexi\Checkout\Model\Webhook\PaymentCancelCreated Nexi\Checkout\Model\Webhook\PaymentChargeCreated @@ -206,9 +192,7 @@ Nexi\Checkout\Model\Webhook\PaymentCreated Nexi\Checkout\Model\Webhook\PaymentRefundCompleted Nexi\Checkout\Model\Webhook\PaymentRefundFailed - Nexi\Checkout\Model\Webhook\PaymentRefundInitiated Nexi\Checkout\Model\Webhook\PaymentReservationCreated - Nexi\Checkout\Model\Webhook\PaymentReservationFailed From 74d1299a5e20a61f36978aad95350c6049d4d994 Mon Sep 17 00:00:00 2001 From: Konrad Konieczny Date: Mon, 14 Apr 2025 08:34:51 +0200 Subject: [PATCH 117/136] Add method to retrieve test API key and update payment API connection logic --- .../Adminhtml/System/Config/TestConnection.php | 2 +- Gateway/Config/Config.php | 10 ++++++++++ Gateway/Response/Handler.php | 14 ++++++++++++++ 3 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 Gateway/Response/Handler.php diff --git a/Controller/Adminhtml/System/Config/TestConnection.php b/Controller/Adminhtml/System/Config/TestConnection.php index afc731d7..135c36ea 100644 --- a/Controller/Adminhtml/System/Config/TestConnection.php +++ b/Controller/Adminhtml/System/Config/TestConnection.php @@ -72,7 +72,7 @@ public function execute() try { $api = $this->paymentApiFactory->create( - secretKey : $options['api_key'], + secretKey : $apiKey, isLiveMode: $isLiveMode ); $currency = $this->scopeConfig->getValue( diff --git a/Gateway/Config/Config.php b/Gateway/Config/Config.php index 92bccbf0..609e80f2 100644 --- a/Gateway/Config/Config.php +++ b/Gateway/Config/Config.php @@ -69,6 +69,16 @@ public function getApiKey(): ?string return $this->getValue('api_key'); } + return $this->getTestApiKey(); + } + + /** + * Get test api key + * + * @return mixed|null + */ + public function getTestApiKey() + { return $this->getValue('test_api_key'); } diff --git a/Gateway/Response/Handler.php b/Gateway/Response/Handler.php new file mode 100644 index 00000000..392a334f --- /dev/null +++ b/Gateway/Response/Handler.php @@ -0,0 +1,14 @@ + Date: Mon, 14 Apr 2025 08:45:50 +0200 Subject: [PATCH 118/136] Rename API key references to secret key in configuration and update retrieval logic --- Block/Adminhtml/System/Config/TestConnection.php | 8 ++++---- Controller/Adminhtml/System/Config/TestConnection.php | 2 +- Gateway/Config/Config.php | 8 ++++---- etc/adminhtml/system.xml | 8 ++++---- etc/config.xml | 4 ++-- 5 files changed, 15 insertions(+), 15 deletions(-) diff --git a/Block/Adminhtml/System/Config/TestConnection.php b/Block/Adminhtml/System/Config/TestConnection.php index a4507cdd..ce885962 100644 --- a/Block/Adminhtml/System/Config/TestConnection.php +++ b/Block/Adminhtml/System/Config/TestConnection.php @@ -74,14 +74,14 @@ protected function _getElementHtml(AbstractElement $element) */ private function getFieldMapping(): array { - $apiKeyPath = $this->configStructure->getElementByConfigPath('payment/nexi/api_key'); - $testApiKeyPath = $this->configStructure->getElementByConfigPath('payment/nexi/test_api_key'); + $apiKeyPath = $this->configStructure->getElementByConfigPath('payment/nexi/secret_key'); + $testApiKeyPath = $this->configStructure->getElementByConfigPath('payment/nexi/test_secret_key'); $environmentPath = $this->configStructure->getElementByConfigPath('payment/nexi/environment'); return [ 'environment' => str_replace('/', '_', $environmentPath->getPath()), - 'api_key' => str_replace('/', '_', $apiKeyPath->getPath()), - 'test_api_key' => str_replace('/', '_', $testApiKeyPath->getPath()) + 'secret_key' => str_replace('/', '_', $apiKeyPath->getPath()), + 'test_secret_key' => str_replace('/', '_', $testApiKeyPath->getPath()) ]; } } diff --git a/Controller/Adminhtml/System/Config/TestConnection.php b/Controller/Adminhtml/System/Config/TestConnection.php index 135c36ea..4ee32287 100644 --- a/Controller/Adminhtml/System/Config/TestConnection.php +++ b/Controller/Adminhtml/System/Config/TestConnection.php @@ -64,7 +64,7 @@ public function execute() $options = $this->getRequest()->getParams(); $isLiveMode = $options['environment'] == Environment::LIVE; - $apiKey = $isLiveMode ? $options['api_key'] : $options['test_api_key']; + $apiKey = $isLiveMode ? $options['secret_key'] : $options['test_secret_key']; if ($apiKey == '******') { $apiKey = $isLiveMode ? $this->config->getApiKey() : $this->config->getTestApiKey(); diff --git a/Gateway/Config/Config.php b/Gateway/Config/Config.php index 609e80f2..6b74eab1 100644 --- a/Gateway/Config/Config.php +++ b/Gateway/Config/Config.php @@ -59,27 +59,27 @@ public function isActive(): bool } /** - * Get api key + * Get secret key * * @return string|null */ public function getApiKey(): ?string { if ($this->isLiveMode()) { - return $this->getValue('api_key'); + return $this->getValue('secret_key'); } return $this->getTestApiKey(); } /** - * Get test api key + * Get test secret key * * @return mixed|null */ public function getTestApiKey() { - return $this->getValue('test_api_key'); + return $this->getValue('test_secret_key'); } /** diff --git a/etc/adminhtml/system.xml b/etc/adminhtml/system.xml index b1451054..5e95fbee 100644 --- a/etc/adminhtml/system.xml +++ b/etc/adminhtml/system.xml @@ -21,8 +21,8 @@ Nexi\Checkout\Block\Adminhtml\System\Config\TestConnection - - + + Magento\Config\Model\Config\Backend\Encrypted Checkout Key Magento\Config\Model\Config\Backend\Encrypted - - + + Magento\Config\Model\Config\Backend\Encrypted avsPostalCodeResponseCode,avsStreetAddressResponseCode,cvvResponseCode,processorAuthorizationCode,processorResponseCode,processorResponseText,liabilityShifted,liabilityShiftPossible,riskDataId,riskDataDecision cc_type,cc_number,avsPostalCodeResponseCode,avsStreetAddressResponseCode,cvvResponseCode,processorAuthorizationCode,processorResponseCode,processorResponseText,liabilityShifted,liabilityShiftPossible nexi_group - + - + HostedPaymentPage From 808ce305b171dc469e0474278f508bf8634303e2 Mon Sep 17 00:00:00 2001 From: Konrad Konieczny Date: Mon, 14 Apr 2025 10:25:50 +0200 Subject: [PATCH 119/136] Add auto capture option to payment configuration --- etc/config.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/etc/config.xml b/etc/config.xml index 59338a41..04c74dca 100644 --- a/etc/config.xml +++ b/etc/config.xml @@ -7,6 +7,7 @@ authorize_capture 0 0 + 1 1 0 1 From 102208c25bc8936cf269938e19f8629b10a760b8 Mon Sep 17 00:00:00 2001 From: Konrad Konieczny Date: Mon, 14 Apr 2025 12:10:37 +0200 Subject: [PATCH 120/136] Add webhook secret generation functionality and update module version --- Setup/Patch/Data/GenerateWebhookSecret.php | 58 ++++++++++++++++++++++ etc/adminhtml/system.xml | 9 ---- etc/module.xml | 2 +- 3 files changed, 59 insertions(+), 10 deletions(-) create mode 100644 Setup/Patch/Data/GenerateWebhookSecret.php diff --git a/Setup/Patch/Data/GenerateWebhookSecret.php b/Setup/Patch/Data/GenerateWebhookSecret.php new file mode 100644 index 00000000..ce1325a5 --- /dev/null +++ b/Setup/Patch/Data/GenerateWebhookSecret.php @@ -0,0 +1,58 @@ +encryptor->encrypt($randomApiKey); + + // Save the API key to the configuration + $this->configWriter->save(self::CONFIG_PATH_API_KEY, $encryptedApiKey); + } + + /** + * @inheritDoc + */ + public static function getDependencies(): array + { + return []; + } + + /** + * @inheritDoc + */ + public function getAliases(): array + { + return []; + } +} diff --git a/etc/adminhtml/system.xml b/etc/adminhtml/system.xml index 5e95fbee..1848dae0 100644 --- a/etc/adminhtml/system.xml +++ b/etc/adminhtml/system.xml @@ -39,15 +39,6 @@ Magento\Config\Model\Config\Backend\Encrypted - - - Magento\Config\Model\Config\Backend\Encrypted - required-entry - - 1 - - diff --git a/etc/module.xml b/etc/module.xml index 26c312d5..8da02ee3 100644 --- a/etc/module.xml +++ b/etc/module.xml @@ -1,7 +1,7 @@ - + From 33e80011ee747714a1ce3503f6a359c0ab4e5dd8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Ka=C5=82u=C5=BCny?= Date: Wed, 7 May 2025 18:01:31 +0200 Subject: [PATCH 121/136] SQNETS-72: add validation for config fields --- etc/adminhtml/system.xml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/etc/adminhtml/system.xml b/etc/adminhtml/system.xml index 1848dae0..2f040556 100644 --- a/etc/adminhtml/system.xml +++ b/etc/adminhtml/system.xml @@ -12,22 +12,23 @@ + required-entry - - + required-entry Magento\Config\Model\Config\Backend\Encrypted + required-entry Magento\Config\Model\Config\Backend\Encrypted From 95b374fb9fbb522cc158b146c04ef2ad383abcb8 Mon Sep 17 00:00:00 2001 From: Konrad Konieczny Date: Fri, 9 May 2025 09:26:41 +0200 Subject: [PATCH 122/136] Add GitHub Actions workflow for PHP code validation and unit testing --- .github/workflows/code-validation.yml | 84 +++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 .github/workflows/code-validation.yml diff --git a/.github/workflows/code-validation.yml b/.github/workflows/code-validation.yml new file mode 100644 index 00000000..7899453d --- /dev/null +++ b/.github/workflows/code-validation.yml @@ -0,0 +1,84 @@ +name: Run code validation checks +on: + pull_request: +jobs: + changed-files: + runs-on: ubuntu-latest + name: Gather Changelist + strategy: + matrix: + fetch-depth: + - 2 + base-branch: + - "master" + outputs: + all: ${{ steps.changes.outputs.all }} + php: ${{ steps.changes.outputs.php }} + steps: + - name: Check out code + uses: actions/checkout@v4 + with: + fetch-depth: ${{ matrix.fetch-depth }} + - name: Get changed files + id: changes + run: | + BASE_SHA="${{ github.event.before }}" + if [ "$BASE_SHA" = "0000000000000000000000000000000000000000" ]; then + BASE_SHA="${{ matrix.base-branch }}" + fi + if [[ ! -z "${{ github.event.pull_request.base.sha }}" ]]; then + BASE_SHA="${{ github.event.pull_request.base.sha }}" + fi + echo "all=$(git diff --name-only --diff-filter=ACMRT $BASE_SHA ${{ github.sha }} | xargs)" >> $GITHUB_OUTPUT + echo "php=$(git diff --name-only --diff-filter=ACMRT $BASE_SHA ${{ github.sha }} | grep -E '.ph(p|tml)$' | xargs)" >> $GITHUB_OUTPUT + validate-php: + runs-on: ubuntu-latest + name: Run php code validation and Unit tests + needs: changed-files + if: ${{needs.changed-files.outputs.php}} + strategy: + matrix: + node-version: + - 20 + php-version: ["8.1", "8.2", "8.3"] + dependencies: + - "highest" + composer-options: + - "--no-plugins --no-progress" + steps: + - name: Setup node + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node_version }} + - name: Check out code + uses: actions/checkout@v4 + - name: Setup Php + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php-version }} + ini-file: development + tools: composer:2.2, cs2pr + env: + COMPOSER_AUTH_JSON: | + { + "http-basic": { + "repo.magento.com": { + "username": "${{ secrets.REPO_MAGENTO_USER }}", + "password": "${{ secrets.REPO_MAGENTO_PASSWORD }}" + } + } + } + - name: Validate Composer Files + run: composer validate + - name: Run Composer install + uses: "ramsey/composer-install@v1" + with: + dependency-versions: "${{ matrix.dependencies }}" + composer-options: "${{ matrix.composer-options }}" + - name: Detect PhpCs violations + run: | + vendor/bin/phpcs --config-set installed_paths ../../magento/magento-coding-standard/,../../phpcompatibility/php-compatibility,../../magento/php-compatibility-fork + vendor/bin/phpcs --standard=Magento2 -q --report=checkstyle ${{needs.changed-files.outputs.php}} | cs2pr --graceful-warnings + - name: Run Unit test + run: | + vendor/bin/phpunit Test/Unit/ From bf2cdae7f5ab5bdc2891ce44fed991b2a76e4055 Mon Sep 17 00:00:00 2001 From: Konrad Konieczny Date: Fri, 9 May 2025 10:13:32 +0200 Subject: [PATCH 123/136] Add GitHub Actions workflow for PHP code validation and unit testing --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 7049a0e5..e7b0d074 100644 --- a/composer.json +++ b/composer.json @@ -31,7 +31,7 @@ "require-dev": { "magento/magento-coding-standard": "*", "phpunit/phpunit": "^9.6", - "magento/product-enterprise-edition": ">2.4.7" + "magento/product-community-edition": ">2.4.7" }, "type": "magento2-module", "version": "0.0.1", From 29e7b10db30de5f8fe78e2c5313e53b06fd6a105 Mon Sep 17 00:00:00 2001 From: Konrad Konieczny Date: Fri, 9 May 2025 16:43:52 +0200 Subject: [PATCH 124/136] Update payment order reference and require specific version of payment SDK --- Controller/Adminhtml/System/Config/TestConnection.php | 3 ++- composer.json | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Controller/Adminhtml/System/Config/TestConnection.php b/Controller/Adminhtml/System/Config/TestConnection.php index 4ee32287..1abb4f73 100644 --- a/Controller/Adminhtml/System/Config/TestConnection.php +++ b/Controller/Adminhtml/System/Config/TestConnection.php @@ -19,6 +19,7 @@ use NexiCheckout\Factory\PaymentApiFactory; use NexiCheckout\Model\Request\Item; use NexiCheckout\Model\Request\Payment; +use NexiCheckout\Model\Request\Shared\Order; class TestConnection extends Action implements HttpPostActionInterface { @@ -82,7 +83,7 @@ public function execute() $payment = $api->createEmbeddedPayment( new Payment( - new Payment\Order( + new Order( [ new Item('test', 1, 'pcs', 1, 1, 1, 'test') ], diff --git a/composer.json b/composer.json index e7b0d074..6f560e15 100644 --- a/composer.json +++ b/composer.json @@ -23,7 +23,7 @@ ], "require": { "php": "^8.0", - "nexi-checkout/php-payment-sdk": "*", + "nexi-checkout/php-payment-sdk": "^0.4.1", "magento/framework": ">=102.0.0", "ext-curl": "*", "nesbot/carbon": "^2.57.0" From 9998782dbbd602a0ff967fa2a4498a30f0d8d8dc Mon Sep 17 00:00:00 2001 From: Konrad Konieczny Date: Mon, 12 May 2025 20:26:36 +0200 Subject: [PATCH 125/136] Refactor code for improved readability and maintainability; add strict types and update constructor parameters across multiple classes --- .../System/Config/TestConnection.php | 18 +++- .../System/Config/TestConnection.php | 18 ++-- Controller/Hpp/CancelAction.php | 9 +- Controller/Payment/Webhook.php | 31 +++---- Gateway/AmountConverter.php | 26 ++++++ Gateway/Command/Initialize.php | 4 +- Gateway/Config/Config.php | 8 +- Gateway/Handler/Capture.php | 3 +- Gateway/Handler/CreatePayment.php | 3 +- Gateway/Handler/RefundCharge.php | 3 +- Gateway/Http/Client.php | 41 ++-------- Gateway/Http/TransferFactory.php | 3 +- Gateway/Request/CaptureRequestBuilder.php | 2 + .../Request/CreatePaymentRequestBuilder.php | 70 +++++++++------- .../SalesDocumentItemsBuilder.php | 35 +++++--- Gateway/Request/RefundRequestBuilder.php | 2 + Gateway/Response/Handler.php | 14 ---- Model/Config/Source/Environment.php | 2 + Model/Config/Source/IntegrationType.php | 7 +- Model/Transaction/Builder.php | 5 +- Model/Ui/ConfigProvider.php | 6 +- Model/Webhook/Data/WebhookDataLoader.php | 15 ++-- Model/Webhook/PaymentChargeCreated.php | 82 +++++++++---------- Model/Webhook/PaymentCreated.php | 53 ++++++------ Model/Webhook/PaymentRefundCompleted.php | 9 +- Model/Webhook/PaymentReservationCreated.php | 24 +++--- Model/WebhookHandler.php | 2 + Plugin/GuestPaymentInformationManagement.php | 61 -------------- ...=> PaymentInformationManagementPlugin.php} | 27 +++--- Setup/Patch/Data/GenerateWebhookSecret.php | 5 +- Test/Unit/EnvironmentTest.php | 6 +- composer.json | 25 +----- etc/di.xml | 6 +- .../payment/method-renderer/nexi-method.js | 30 ++++++- .../web/template/payment/nexi-embedded.html | 36 ++++++++ .../payment/{nexi.html => nexi-hosted.html} | 0 36 files changed, 347 insertions(+), 344 deletions(-) create mode 100644 Gateway/AmountConverter.php delete mode 100644 Gateway/Response/Handler.php delete mode 100644 Plugin/GuestPaymentInformationManagement.php rename Plugin/{PaymentInformationManagement.php => PaymentInformationManagementPlugin.php} (62%) create mode 100644 view/frontend/web/template/payment/nexi-embedded.html rename view/frontend/web/template/payment/{nexi.html => nexi-hosted.html} (100%) diff --git a/Block/Adminhtml/System/Config/TestConnection.php b/Block/Adminhtml/System/Config/TestConnection.php index ce885962..5580b089 100644 --- a/Block/Adminhtml/System/Config/TestConnection.php +++ b/Block/Adminhtml/System/Config/TestConnection.php @@ -1,5 +1,7 @@ unsScope()->unsCanUseWebsiteValue()->unsCanUseDefaultValue(); + return parent::render($element); } @@ -44,10 +53,13 @@ protected function _prepareLayout() { parent::_prepareLayout(); $this->setTemplate('Nexi_Checkout::system/config/testconnection.phtml'); + return $this; } /** + * Get HTML for the element + * * @param AbstractElement $element * * @return string diff --git a/Controller/Adminhtml/System/Config/TestConnection.php b/Controller/Adminhtml/System/Config/TestConnection.php index 1abb4f73..18921c24 100644 --- a/Controller/Adminhtml/System/Config/TestConnection.php +++ b/Controller/Adminhtml/System/Config/TestConnection.php @@ -4,6 +4,7 @@ namespace Nexi\Checkout\Controller\Adminhtml\System\Config; +use JsonException; use Magento\Backend\App\Action; use Magento\Backend\App\Action\Context; use Magento\Framework\App\Action\HttpPostActionInterface; @@ -28,7 +29,7 @@ class TestConnection extends Action implements HttpPostActionInterface * * @see _isAllowed() */ - public const ADMIN_RESOURCE = 'Magento_Catalog::config_catalog'; + public const ADMIN_RESOURCE = 'Magento_Catalog::config_catalog'; /** * @param Context $context @@ -37,14 +38,15 @@ class TestConnection extends Action implements HttpPostActionInterface * @param PaymentApiFactory $paymentApiFactory * @param Config $config * @param Url $url + * @param ScopeConfigInterface $scopeConfig */ public function __construct( - Context $context, - private readonly JsonFactory $resultJsonFactory, - private readonly StripTags $tagFilter, - private readonly PaymentApiFactory $paymentApiFactory, - private readonly Config $config, - private readonly Url $url, + Context $context, + private readonly JsonFactory $resultJsonFactory, + private readonly StripTags $tagFilter, + private readonly PaymentApiFactory $paymentApiFactory, + private readonly Config $config, + private readonly Url $url, private readonly ScopeConfigInterface $scopeConfig ) { parent::__construct($context); @@ -54,7 +56,7 @@ public function __construct( * Check for connection to server * * @return Json - * @throws \JsonException + * @throws JsonException */ public function execute() { diff --git a/Controller/Hpp/CancelAction.php b/Controller/Hpp/CancelAction.php index 8d4571bb..51aad0f8 100644 --- a/Controller/Hpp/CancelAction.php +++ b/Controller/Hpp/CancelAction.php @@ -11,7 +11,6 @@ class CancelAction implements ActionInterface { - /** * @param RedirectFactory $resultRedirectFactory * @param UrlInterface $url @@ -19,10 +18,10 @@ class CancelAction implements ActionInterface * @param ManagerInterface $messageManager */ public function __construct( - private readonly RedirectFactory $resultRedirectFactory, - private readonly UrlInterface $url, - private readonly Session $checkoutSession, - private readonly ManagerInterface $messageManager + private readonly RedirectFactory $resultRedirectFactory, + private readonly UrlInterface $url, + private readonly Session $checkoutSession, + private readonly ManagerInterface $messageManager ) { } diff --git a/Controller/Payment/Webhook.php b/Controller/Payment/Webhook.php index 63f7412c..e8c99e92 100644 --- a/Controller/Payment/Webhook.php +++ b/Controller/Payment/Webhook.php @@ -1,5 +1,7 @@ isAuthorized()) { - return $this->_response - ->setHttpResponseCode(401) - ->setBody('Unauthorized'); - } +// if (!$this->isAuthorized()) { +// return $this->_response +// ->setHttpResponseCode(401) +// ->setBody('Unauthorized'); +// } try { $content = $this->serializer->unserialize($this->getRequest()->getContent()); @@ -71,13 +72,7 @@ public function execute() ); $this->_response->setHttpResponseCode(200); } catch (Exception $e) { - $this->logger->error( - 'Webhook error:', - [ - 'message' => $e->getMessage(), - 'stacktrace' => $e->getTraceAsString(), - ] - ); + $this->logger->error($e->getMessage(), ['stacktrace' => $e->getTrace()]); $this->_response->setHttpResponseCode(500); } } diff --git a/Gateway/AmountConverter.php b/Gateway/AmountConverter.php new file mode 100644 index 00000000..612dff82 --- /dev/null +++ b/Gateway/AmountConverter.php @@ -0,0 +1,26 @@ +scopeConfig->getValue('general/country/default'); + return $this->scopeConfig->isSetFlag('general/country/default'); } } diff --git a/Gateway/Handler/Capture.php b/Gateway/Handler/Capture.php index 62704d58..e2f8296a 100644 --- a/Gateway/Handler/Capture.php +++ b/Gateway/Handler/Capture.php @@ -1,5 +1,7 @@ getPaymentApi(); $nexiMethod = $transferObject->getUri(); $this->logger->debug( - 'Nexi method: ' . $nexiMethod . PHP_EOL . - 'Nexi request: ' . json_encode($transferObject->getBody()) + 'Nexi Client request: ', + [ + 'method' => $nexiMethod, + 'request' => $transferObject->getBody() + ] ); if (is_array($transferObject->getBody())) { $response = $paymentApi->$nexiMethod(...$transferObject->getBody()); @@ -53,7 +54,8 @@ public function placeRequest(TransferInterface $transferObject): array } $this->logger->debug( - 'Nexi response: ' . $this->getResponseData($response) + 'Nexi Client response: ', + ['response' => var_export($response, true)] ); } catch (PaymentApiException|\Exception $e) { $this->logger->error($e->getMessage(), [$e]); @@ -63,31 +65,6 @@ public function placeRequest(TransferInterface $transferObject): array return [$response]; } - /** - * Get response data - * - * @param JsonDeserializeInterface $response - * - * @return string|false - * @throws \ReflectionException - */ - public function getResponseData($response): string|false - { - $responseData = []; - - $responseReflection = new \ReflectionClass($response); - $methods = $responseReflection->getMethods(\ReflectionMethod::IS_PUBLIC); - - foreach ($methods as $method) { - if (str_starts_with($method->getName(), 'get')) { - $name = $method->getName(); - $value = $method->invoke($response); - $responseData[$name] = $value; - } - } - return json_encode($responseData); - } - /** * Get Payment API Client * diff --git a/Gateway/Http/TransferFactory.php b/Gateway/Http/TransferFactory.php index d0d24a3e..966b4f49 100644 --- a/Gateway/Http/TransferFactory.php +++ b/Gateway/Http/TransferFactory.php @@ -1,5 +1,7 @@ buildItems($order), currency : $order->getBaseCurrencyCode(), - amount : (int)($order->getGrandTotal() * 100), - reference: $order->getIncrementId(), + amount : $this->amountConverter->convertToNexiAmount($order->getBaseGrandTotal()), + reference: $order->getIncrementId() ); } @@ -83,22 +91,22 @@ public function buildOrder(Order $order): Payment\Order * * @param Order $order * - * @return Order\Item|array + * @return OrderItem|array */ - private function buildItems(Order $order): Order\Item|array + private function buildItems(Order $order): OrderItem|array { - /** @var Order\Item $items */ + /** @var OrderItem $items */ foreach ($order->getAllVisibleItems() as $item) { $items[] = new Item( name : $item->getName(), - quantity : (int)$item->getQtyOrdered(), + quantity : (float)$item->getQtyOrdered(), unit : 'pcs', - unitPrice : (int)($item->getPrice() * 100), - grossTotalAmount: (int)($item->getRowTotalInclTax() * 100), - netTotalAmount : (int)($item->getRowTotal() * 100), + unitPrice : $this->amountConverter->convertToNexiAmount($item->getPrice()), + grossTotalAmount: $this->amountConverter->convertToNexiAmount($item->getRowTotalInclTax()), + netTotalAmount : $this->amountConverter->convertToNexiAmount($item->getRowTotal()), reference : $item->getSku(), - taxRate : (int)($item->getTaxPercent() * 100), - taxAmount : (int)($item->getTaxAmount() * 100), + taxRate : $this->amountConverter->convertToNexiAmount($item->getTaxPercent()), + taxAmount : $this->amountConverter->convertToNexiAmount($item->getTaxAmount()), ); } @@ -107,12 +115,14 @@ private function buildItems(Order $order): Order\Item|array name : $order->getShippingDescription(), quantity : 1, unit : 'pcs', - unitPrice : (int)($order->getShippingAmount() * 100), - grossTotalAmount: (int)($order->getShippingInclTax() * 100), - netTotalAmount : (int)($order->getShippingAmount() * 100), + unitPrice : $this->amountConverter->convertToNexiAmount($order->getShippingAmount()), + grossTotalAmount: $this->amountConverter->convertToNexiAmount($order->getShippingInclTax()), + netTotalAmount : $this->amountConverter->convertToNexiAmount($order->getShippingAmount()), reference : SalesDocumentItemsBuilder::SHIPPING_COST_REFERENCE, - taxRate : (int)($order->getTaxAmount() / $order->getGrandTotal() * 100), - taxAmount : (int)($order->getShippingTaxAmount() * 100), + taxRate : $this->amountConverter->convertToNexiAmount( + $order->getTaxAmount() / $order->getGrandTotal() + ), + taxAmount : $this->amountConverter->convertToNexiAmount($order->getShippingTaxAmount()), ); } @@ -132,14 +142,14 @@ private function buildPayment(Order $order): Payment return new Payment( order : $this->buildOrder($order), checkout : $this->buildCheckout($order), - notification: new Payment\Notification($this->buildWebhooks()), + notification: new Notification($this->buildWebhooks()), ); } /** * Build the webhooks for the payment * - * @return array + * @return array * * added all for now, we need to check wh */ @@ -147,10 +157,10 @@ public function buildWebhooks(): array { $webhooks = []; foreach ($this->webhookHandler->getWebhookProcessors() as $eventName => $processor) { - $baseUrl = $this->url->getBaseUrl(); - $webhooks[] = new Payment\Webhook( + $webhookUrl = $this->url->getUrl(self::NEXI_PAYMENT_WEBHOOK_PATH); + $webhooks[] = new Webhook( eventName : $eventName, - url : $baseUrl . self::NEXI_PAYMENT_WEBHOOK_PATH, + url : $webhookUrl, authorization: $this->encryptor->hash($this->config->getWebhookSecret()) ); } @@ -183,7 +193,7 @@ public function buildCheckout(Order $order): HostedCheckout|EmbeddedCheckout termsUrl : $this->config->getWebshopTermsAndConditionsUrl(), consumer : $this->buildConsumer($order), isAutoCharge : $this->config->getPaymentAction() == 'authorize_capture', - merchantHandlesConsumerData: $this->config->getMerchantHandlesConsumerData(), + merchantHandlesConsumerData:(bool)$this->config->getMerchantHandlesConsumerData(), countryCode : $this->getThreeLetterCountryCode(), ); } diff --git a/Gateway/Request/NexiCheckout/SalesDocumentItemsBuilder.php b/Gateway/Request/NexiCheckout/SalesDocumentItemsBuilder.php index 4c1fe02c..c56620ae 100644 --- a/Gateway/Request/NexiCheckout/SalesDocumentItemsBuilder.php +++ b/Gateway/Request/NexiCheckout/SalesDocumentItemsBuilder.php @@ -1,15 +1,25 @@ getAllItems() as $item) { $items[] = new Item( name : $item->getName(), - quantity : (int)$item->getQty(), + quantity : (float)$item->getQty(), unit : 'pcs', - unitPrice : (int)($item->getPrice() * 100), - grossTotalAmount: (int)($item->getRowTotalInclTax() * 100), - netTotalAmount : (int)($item->getRowTotal() * 100), + unitPrice : $this->amountConverter->convertToNexiAmount($item->getPrice()), + grossTotalAmount: $this->amountConverter->convertToNexiAmount($item->getRowTotalInclTax()), + netTotalAmount : $this->amountConverter->convertToNexiAmount($item->getRowTotal()), reference : $item->getSku(), - taxRate : (int)($item->getTaxPercent() * 100), - taxAmount : (int)($item->getTaxAmount() * 100), + taxRate : $this->amountConverter->convertToNexiAmount($item->getTaxPercent()), + taxAmount : $this->amountConverter->convertToNexiAmount($item->getTaxAmount()), ); } @@ -39,14 +49,15 @@ public function build(CreditmemoInterface|InvoiceInterface $salesObject): array name : $salesObject->getOrder()->getShippingDescription(), quantity : 1, unit : 'pcs', - unitPrice : (int)($salesObject->getShippingAmount() * 100), - grossTotalAmount: (int)($salesObject->getShippingInclTax() * 100), - netTotalAmount : (int)($salesObject->getShippingAmount() * 100), + unitPrice : $this->amountConverter->convertToNexiAmount($salesObject->getShippingAmount()), + grossTotalAmount: $this->amountConverter->convertToNexiAmount($salesObject->getShippingInclTax()), + netTotalAmount : $this->amountConverter->convertToNexiAmount($salesObject->getShippingAmount()), reference : self::SHIPPING_COST_REFERENCE, taxRate : $salesObject->getGrandTotal() ? - (int)($salesObject->getTaxAmount() / $salesObject->getGrandTotal() * 100) : - 0, - taxAmount : (int)($salesObject->getShippingTaxAmount() * 100), + $this->amountConverter->convertToNexiAmount( + $salesObject->getTaxAmount() / $salesObject->getGrandTotal() + ) : 0, + taxAmount : $this->amountConverter->convertToNexiAmount($salesObject->getShippingTaxAmount()), ); } diff --git a/Gateway/Request/RefundRequestBuilder.php b/Gateway/Request/RefundRequestBuilder.php index ca9446bc..e40bb00b 100644 --- a/Gateway/Request/RefundRequestBuilder.php +++ b/Gateway/Request/RefundRequestBuilder.php @@ -1,5 +1,7 @@ IntegrationTypeEnum::HostedPaymentPage->name, - 'label' => __('Hosted Payment Page'), + 'label' => __('Hosted Checkout'), ] ]; } diff --git a/Model/Transaction/Builder.php b/Model/Transaction/Builder.php index 22de655e..0803f0c8 100644 --- a/Model/Transaction/Builder.php +++ b/Model/Transaction/Builder.php @@ -1,5 +1,7 @@ searchCriteriaBuilder ->addFilter('order_id', $orderId, 'eq') ->addFilter('txn_type', $txnType, 'eq') diff --git a/Model/Webhook/PaymentChargeCreated.php b/Model/Webhook/PaymentChargeCreated.php index ba8b8443..e608d9a2 100644 --- a/Model/Webhook/PaymentChargeCreated.php +++ b/Model/Webhook/PaymentChargeCreated.php @@ -4,6 +4,7 @@ namespace Nexi\Checkout\Model\Webhook; +use Exception; use Magento\Framework\Exception\AlreadyExistsException; use Magento\Framework\Exception\LocalizedException; use Magento\Framework\Exception\NotFoundException; @@ -17,28 +18,26 @@ class PaymentChargeCreated implements WebhookProcessorInterface { /** - * PaymentChargeCreated constructor. - * * @param OrderRepositoryInterface $orderRepository * @param WebhookDataLoader $webhookDataLoader * @param Builder $transactionBuilder */ public function __construct( - private OrderRepositoryInterface $orderRepository, - private WebhookDataLoader $webhookDataLoader, - private Builder $transactionBuilder + private readonly OrderRepositoryInterface $orderRepository, + private readonly WebhookDataLoader $webhookDataLoader, + private readonly Builder $transactionBuilder ) { } /** * ProcessWebhook function for 'payment.charge.created.v2' event. * - * @param $webhookData + * @param array $webhookData * * @return void * @throws LocalizedException */ - public function processWebhook($webhookData): void + public function processWebhook(array $webhookData): void { $order = $this->webhookDataLoader->loadOrderByPaymentId($webhookData['data']['paymentId']); $this->processOrder($order, $webhookData); @@ -49,26 +48,26 @@ public function processWebhook($webhookData): void /** * ProcessOrder function. * - * @param $order - * @param $webhookData + * @param Order $order + * @param array $webhookData * * @return void + * @throws AlreadyExistsException + * @throws LocalizedException * @throws NotFoundException - * @throws \Exception */ - private function processOrder($order, $webhookData): void + private function processOrder(Order $order, array $webhookData): void { $reservationTxn = $this->webhookDataLoader->getTransactionByOrderId( $order->getId(), TransactionInterface::TYPE_AUTH ); - if ($order->getState() !== Order::STATE_PENDING_PAYMENT) { - throw new \Exception('Order state is not pending payment.'); + throw new Exception('Order state is not pending payment.'); } - $chargeTxnId = $webhookData['data']['chargeId']; + $chargeTxnId = $webhookData['data']['chargeId']; if ($this->webhookDataLoader->getTransactionByPaymentId($chargeTxnId, TransactionInterface::TYPE_CAPTURE)) { throw new AlreadyExistsException(__('Transaction already exists.')); @@ -80,7 +79,7 @@ private function processOrder($order, $webhookData): void $order, [ 'payment_id' => $webhookData['data']['paymentId'], - 'webhook' => json_encode($webhookData, JSON_PRETTY_PRINT), + 'webhook' => json_encode($webhookData, JSON_PRETTY_PRINT), ], TransactionInterface::TYPE_CAPTURE )->setParentId($reservationTxn->getTransactionId()) @@ -90,7 +89,7 @@ private function processOrder($order, $webhookData): void $chargeTransaction, __( 'Payment charge created, amount: %1 %2', - $webhookData['data']['amount']['amount']/100, + $webhookData['data']['amount']['amount'] / 100, $webhookData['data']['amount']['currency'] ) ); @@ -103,57 +102,59 @@ private function processOrder($order, $webhookData): void $order->setState(Order::STATE_PROCESSING)->setStatus(Order::STATE_PROCESSING); } - /** * Validate charge transaction. - * Check items paid to create proper invoice. * - * @param $webhookData - * @param $order + * @param array $webhookData + * @param Order $order * * @return bool */ - private function isFullCharge( - $webhookData, $order - ): bool { + private function isFullCharge(array $webhookData, Order $order): bool + { return (int)($order->getBaseGrandTotal() * 100) === $webhookData['data']['amount']['amount']; } /** - * @param $order - * @param $chargeTxnId + * Process + * + * @param Order $order + * @param string $chargeTxnId * * @return void + * @throws LocalizedException */ - public function fullInvoice(Order $order, $chargeTxnId): void + public function fullInvoice(Order $order, string $chargeTxnId): void { - if ($order->canInvoice()) { - $invoice = $order->prepareInvoice(); - $invoice->register(); - $invoice->setTransactionId($chargeTxnId); - $invoice->pay(); - - $order->addRelatedObject($invoice); + if (!$order->canInvoice()) { + return; } + + $invoice = $order->prepareInvoice(); + $invoice->register(); + $invoice->setTransactionId($chargeTxnId); + $invoice->pay(); + + $order->addRelatedObject($invoice); } /** * Create partial invoice. Add shipping amount if charged - * TODO: investigate how to invoice only shipping cost in magento? probably not possible separately - without any order item invoiced - * TODO: now its only in order history comments (if charge only for shipping) + * + * TODO: investigate how to invoice only shipping cost in magento? probably not possible separately - without any + * TODO: order item invoiced now its only in order history comments (if charge only for shipping) * * @param Order $order - * @param $chargeTxnId - * @param $webhookItems + * @param string $chargeTxnId + * @param array $webhookItems * * @return void * @throws LocalizedException */ - private function partialInvoice(Order $order, $chargeTxnId, $webhookItems): void + private function partialInvoice(Order $order, string $chargeTxnId, array $webhookItems): void { if ($order->canInvoice()) { - - $qtys = []; + $qtys = []; $shippingItem = null; foreach ($webhookItems as $webhookItem) { @@ -178,7 +179,6 @@ private function partialInvoice(Order $order, $chargeTxnId, $webhookItems): void $invoice->pay(); - $invoice->register(); $order->addRelatedObject($invoice); } diff --git a/Model/Webhook/PaymentCreated.php b/Model/Webhook/PaymentCreated.php index 7af662b0..058c913b 100644 --- a/Model/Webhook/PaymentCreated.php +++ b/Model/Webhook/PaymentCreated.php @@ -4,7 +4,6 @@ namespace Nexi\Checkout\Model\Webhook; -use Braintree\Exception\NotFound; use Magento\Checkout\Exception; use Magento\Framework\Exception\LocalizedException; use Magento\Reports\Model\ResourceModel\Order\CollectionFactory; @@ -25,23 +24,21 @@ class PaymentCreated implements WebhookProcessorInterface * @param OrderRepositoryInterface $orderRepository */ public function __construct( - private readonly Builder $transactionBuilder, - private readonly CollectionFactory $orderCollectionFactory, - private readonly WebhookDataLoader $webhookDataLoader, - private readonly OrderRepositoryInterface $orderRepository + private readonly Builder $transactionBuilder, + private readonly CollectionFactory $orderCollectionFactory, + private readonly WebhookDataLoader $webhookDataLoader, + private readonly OrderRepositoryInterface $orderRepository ) { } /** * PaymentCreated webhook service. * - * @param $webhookData + * @param array $webhookData * * @return void - * @throws Exception - * @throws LocalizedException */ - public function processWebhook($webhookData): void + public function processWebhook(array $webhookData): void { $transaction = $this->webhookDataLoader->getTransactionByPaymentId($webhookData['data']['paymentId']); @@ -62,29 +59,29 @@ public function processWebhook($webhookData): void /** * ProcessOrder function. * - * @param $order - * @param $paymentId + * @param Order $order + * @param string $paymentId * * @return void - * @throws Exception */ - private function createPaymentTransaction($order, $paymentId): void + private function createPaymentTransaction(Order $order, string $paymentId): void { - if ($order->getState() === Order::STATE_NEW) { - $order->setState(Order::STATE_PENDING_PAYMENT)->setStatus(Order::STATE_PENDING_PAYMENT); - $paymentTransaction = $this->transactionBuilder - ->build( - $paymentId, - $order, - [ - 'payment_id' => $paymentId - ], - TransactionInterface::TYPE_PAYMENT - ); - $order->getPayment()->addTransactionCommentsToOrder( - $paymentTransaction, - __('Payment created in Nexi Gateway.') - ); + if ($order->getState() !== Order::STATE_NEW) { + return; } + $order->setState(Order::STATE_PENDING_PAYMENT)->setStatus(Order::STATE_PENDING_PAYMENT); + $paymentTransaction = $this->transactionBuilder + ->build( + $paymentId, + $order, + [ + 'payment_id' => $paymentId + ], + TransactionInterface::TYPE_PAYMENT + ); + $order->getPayment()->addTransactionCommentsToOrder( + $paymentTransaction, + __('Payment created in Nexi Gateway.') + ); } } diff --git a/Model/Webhook/PaymentRefundCompleted.php b/Model/Webhook/PaymentRefundCompleted.php index 58a318ca..cc00e52f 100644 --- a/Model/Webhook/PaymentRefundCompleted.php +++ b/Model/Webhook/PaymentRefundCompleted.php @@ -10,6 +10,7 @@ use Magento\Sales\Api\CreditmemoManagementInterface; use Magento\Sales\Model\Order; use Magento\Sales\Model\Order\CreditmemoFactory; +use Nexi\Checkout\Gateway\AmountConverter; use Nexi\Checkout\Model\Transaction\Builder; use Nexi\Checkout\Model\Webhook\Data\WebhookDataLoader; @@ -21,13 +22,15 @@ class PaymentRefundCompleted implements WebhookProcessorInterface * @param OrderRepositoryInterface $orderRepository * @param CreditmemoFactory $creditmemoFactory * @param CreditmemoManagementInterface $creditmemoManagement + * @param AmountConverter $amountConverter */ public function __construct( private readonly WebhookDataLoader $webhookDataLoader, private readonly Builder $transactionBuilder, private readonly OrderRepositoryInterface $orderRepository, private readonly CreditmemoFactory $creditmemoFactory, - private readonly CreditmemoManagementInterface $creditmemoManagement + private readonly CreditmemoManagementInterface $creditmemoManagement, + private readonly AmountConverter $amountConverter, ) { } @@ -90,6 +93,8 @@ public function processFullRefund(array $webhookData, Order $order): void */ private function isFullRefund(array $webhookData, Order $order): bool { - return $order->getGrandTotal() == $webhookData['data']['amount']['amount']/100; + $GrandTotal = $this->amountConverter->convertToNexiAmount($order->getGrandTotal()); + + return $GrandTotal == $webhookData['data']['amount']['amount']; } } diff --git a/Model/Webhook/PaymentReservationCreated.php b/Model/Webhook/PaymentReservationCreated.php index 365d4a73..56dbb5f5 100644 --- a/Model/Webhook/PaymentReservationCreated.php +++ b/Model/Webhook/PaymentReservationCreated.php @@ -1,9 +1,10 @@ webhookDataLoader->getTransactionByPaymentId($paymentId); if (!$paymentTransaction) { - throw new \Exception('Payment transaction not found.'); + throw new NotFoundException(__('Payment transaction not found for %1.', $paymentId)); } + /** @var \Magento\Sales\Model\Order $order */ $order = $paymentTransaction->getOrder(); $order->setState(Order::STATE_PENDING_PAYMENT)->setStatus(Order::STATE_PENDING_PAYMENT); diff --git a/Model/WebhookHandler.php b/Model/WebhookHandler.php index 9d301937..49c3d130 100644 --- a/Model/WebhookHandler.php +++ b/Model/WebhookHandler.php @@ -1,5 +1,7 @@ getRedirectUrl(); - - if ($redirectUrl) { - $result = json_encode(['result' => $result, 'redirect_url' => $redirectUrl]); - } - } catch (Exception $e) { - $this->logger->error($e->getMessage() . ' ' . $e->getTraceAsString()); - } - - return $result; - } - - /** - * Get the redirect URL from the order payment information. - * - * @return string[] - */ - private function getRedirectUrl() - { - $order = $this->checkoutSession->getLastRealOrder(); - $payment = $order->getPayment(); - - return $payment->getAdditionalInformation('redirect_url'); - } -} diff --git a/Plugin/PaymentInformationManagement.php b/Plugin/PaymentInformationManagementPlugin.php similarity index 62% rename from Plugin/PaymentInformationManagement.php rename to Plugin/PaymentInformationManagementPlugin.php index 5fb8bde7..20ff006e 100644 --- a/Plugin/PaymentInformationManagement.php +++ b/Plugin/PaymentInformationManagementPlugin.php @@ -1,21 +1,22 @@ getRedirectUrl(); - - if ($redirectUrl) { - $result = json_encode(['result' => $result, 'redirect_url' => $redirectUrl]); - } - } catch (Exception $e) { - $this->logger->error($e->getMessage() . ' ' . $e->getTraceAsString()); + $redirectUrl = $this->getRedirectUrl(); + + if ($redirectUrl) { + $result = json_encode(['result' => $result, 'redirect_url' => $redirectUrl]); } return $result; diff --git a/Setup/Patch/Data/GenerateWebhookSecret.php b/Setup/Patch/Data/GenerateWebhookSecret.php index ce1325a5..13a9e3d9 100644 --- a/Setup/Patch/Data/GenerateWebhookSecret.php +++ b/Setup/Patch/Data/GenerateWebhookSecret.php @@ -1,10 +1,10 @@ assertEquals([ - ['value' => 'test', 'label' => __('Test')], - ['value' => 'live', 'label' => __('Live')] - ], $environment->toOptionArray()); + ['value' => 'test', 'label' => __('Test')], + ['value' => 'live', 'label' => __('Live')] + ], $environment->toOptionArray()); } } diff --git a/composer.json b/composer.json index 6f560e15..922f5566 100644 --- a/composer.json +++ b/composer.json @@ -1,32 +1,11 @@ { - "name": "nexi/module-checkout", + "name": "nexi-checkout/adobe-commerce-checkout", "description": "Nets Easy Checkout", - "authors": [ - { - "name": "Solteq Oyj", - "email": "magentoservices@solteq.com", - "homepage": "https://github.com/Solteq", - "role": "Developer" - }, - { - "name": "Konrad Konieczny", - "email": "konrad.konieczny@solteq.com", - "homepage": "https://github.com/bartoszkaluzny-solteq", - "role": "Lead Developer" - }, - { - "name": "Bartosz Kałużny", - "email": "bartosz.kaluzny@solteq.com", - "homepage": "https://github.com/bartoszkaluzny-solteq", - "role": "Developer" - } - ], "require": { "php": "^8.0", "nexi-checkout/php-payment-sdk": "^0.4.1", "magento/framework": ">=102.0.0", - "ext-curl": "*", - "nesbot/carbon": "^2.57.0" + "ext-curl": "*" }, "require-dev": { "magento/magento-coding-standard": "*", diff --git a/etc/di.xml b/etc/di.xml index fbd3a0de..efc36cba 100644 --- a/etc/di.xml +++ b/etc/di.xml @@ -155,13 +155,13 @@ @@ -175,7 +175,7 @@ Magento\Checkout\Model\Session\Proxy - + Magento\Checkout\Model\Session\Proxy diff --git a/view/frontend/web/js/view/payment/method-renderer/nexi-method.js b/view/frontend/web/js/view/payment/method-renderer/nexi-method.js index c43fdadd..93ff865d 100755 --- a/view/frontend/web/js/view/payment/method-renderer/nexi-method.js +++ b/view/frontend/web/js/view/payment/method-renderer/nexi-method.js @@ -20,12 +20,31 @@ define( 'mage/translate', 'Magento_Ui/js/modal/modal' ], - function (ko, $, _, storage, Component, placeOrderAction, selectPaymentMethodAction, additionalValidators, quote, getTotalsAction, urlBuilder, url, fullScreenLoader, customer, checkoutData, totals, messageList, $t, modal) { + function (ko, + $, + _, + storage, + Component, + placeOrderAction, + selectPaymentMethodAction, + additionalValidators, + quote, + getTotalsAction, + urlBuilder, + url, + fullScreenLoader, + customer, + checkoutData, + totals, + messageList, + $t, + modal + ) { 'use strict'; return Component.extend({ defaults: { - template: 'Nexi_Checkout/payment/nexi', + template: window.checkoutConfig.payment.nexi.integrationType ? 'Nexi_Checkout/payment/nexi-hosted' : 'Nexi_Checkout/payment/nexi-embedded.html', config: window.checkoutConfig.payment.nexi }, placeOrder: function (data, event) { @@ -36,13 +55,16 @@ define( }.bind(this)); }, afterPlaceOrder: function (response) { - if (this.config.integrationType === 'HostedPaymentPage') { + if (this.isHosted()) { let redirectUrl = JSON.parse(response).redirect_url; if (redirectUrl) { window.location.href = redirectUrl; } } - } + }, + isHosted: function () { + return this.config.integrationType === 'HostedPaymentPage'; + }, }); } ); diff --git a/view/frontend/web/template/payment/nexi-embedded.html b/view/frontend/web/template/payment/nexi-embedded.html new file mode 100644 index 00000000..21e4ae90 --- /dev/null +++ b/view/frontend/web/template/payment/nexi-embedded.html @@ -0,0 +1,36 @@ + + +
+
+ + +
+
+ + + +
+ + + +
+
+
+ +
+
+
+
diff --git a/view/frontend/web/template/payment/nexi.html b/view/frontend/web/template/payment/nexi-hosted.html similarity index 100% rename from view/frontend/web/template/payment/nexi.html rename to view/frontend/web/template/payment/nexi-hosted.html From e876125cbb2a23639e47dde75f2f5f638cbdbe1f Mon Sep 17 00:00:00 2001 From: Konrad Konieczny Date: Mon, 12 May 2025 20:49:57 +0200 Subject: [PATCH 126/136] Implement authorization check in Webhook and update webhook processor classes --- Controller/Payment/Webhook.php | 10 +++++----- Gateway/Request/CreatePaymentRequestBuilder.php | 1 + Model/Webhook/PaymentCancelCreated.php | 8 +++++--- Model/Webhook/PaymentCancelFailed.php | 8 +++++--- Model/Webhook/PaymentChargeFailed.php | 8 +++++--- Model/Webhook/PaymentRefundFailed.php | 7 +++++-- 6 files changed, 26 insertions(+), 16 deletions(-) diff --git a/Controller/Payment/Webhook.php b/Controller/Payment/Webhook.php index e8c99e92..321004db 100644 --- a/Controller/Payment/Webhook.php +++ b/Controller/Payment/Webhook.php @@ -46,11 +46,11 @@ public function __construct( */ public function execute() { -// if (!$this->isAuthorized()) { -// return $this->_response -// ->setHttpResponseCode(401) -// ->setBody('Unauthorized'); -// } + if (!$this->isAuthorized()) { + return $this->_response + ->setHttpResponseCode(401) + ->setBody('Unauthorized'); + } try { $content = $this->serializer->unserialize($this->getRequest()->getContent()); diff --git a/Gateway/Request/CreatePaymentRequestBuilder.php b/Gateway/Request/CreatePaymentRequestBuilder.php index 60b5b954..bf778c17 100644 --- a/Gateway/Request/CreatePaymentRequestBuilder.php +++ b/Gateway/Request/CreatePaymentRequestBuilder.php @@ -37,6 +37,7 @@ class CreatePaymentRequestBuilder implements BuilderInterface * @param CountryInformationAcquirerInterface $countryInformationAcquirer * @param EncryptorInterface $encryptor * @param WebhookHandler $webhookHandler + * @param AmountConverter $amountConverter */ public function __construct( private readonly UrlInterface $url, diff --git a/Model/Webhook/PaymentCancelCreated.php b/Model/Webhook/PaymentCancelCreated.php index 9fcc3792..9586784c 100644 --- a/Model/Webhook/PaymentCancelCreated.php +++ b/Model/Webhook/PaymentCancelCreated.php @@ -4,10 +4,12 @@ namespace Nexi\Checkout\Model\Webhook; - -class PaymentCancelCreated +class PaymentCancelCreated implements WebhookProcessorInterface { - public function processWebhook() + /** + * @inheritdoc + */ + public function processWebhook(array $webhookData): void { // TODO: Implement webhook processor logic here } diff --git a/Model/Webhook/PaymentCancelFailed.php b/Model/Webhook/PaymentCancelFailed.php index cb8eb618..eb2cb8d3 100644 --- a/Model/Webhook/PaymentCancelFailed.php +++ b/Model/Webhook/PaymentCancelFailed.php @@ -4,10 +4,12 @@ namespace Nexi\Checkout\Model\Webhook; - -class PaymentCancelFailed +class PaymentCancelFailed implements WebhookProcessorInterface { - public function processWebhook() + /** + * @inheritdoc + */ + public function processWebhook(array $webhookData): void { // TODO: Implement webhook processor logic here } diff --git a/Model/Webhook/PaymentChargeFailed.php b/Model/Webhook/PaymentChargeFailed.php index 42079a9a..351faf01 100644 --- a/Model/Webhook/PaymentChargeFailed.php +++ b/Model/Webhook/PaymentChargeFailed.php @@ -4,10 +4,12 @@ namespace Nexi\Checkout\Model\Webhook; - -class PaymentChargeFailed +class PaymentChargeFailed implements WebhookProcessorInterface { - public function processWebhook() + /** + * @inheritdoc + */ + public function processWebhook(array $webhookData): void { // TODO: Implement webhook processor logic here } diff --git a/Model/Webhook/PaymentRefundFailed.php b/Model/Webhook/PaymentRefundFailed.php index 1cf9ba9e..3c6bf22c 100644 --- a/Model/Webhook/PaymentRefundFailed.php +++ b/Model/Webhook/PaymentRefundFailed.php @@ -5,9 +5,12 @@ namespace Nexi\Checkout\Model\Webhook; -class PaymentRefundFailed +class PaymentRefundFailed implements WebhookProcessorInterface { - public function processWebhook() + /** + * @inheritdoc + */ + public function processWebhook(array $webhookData): void { // TODO: Implement webhook processor logic here } From 8f1a5aba627206c60c9b9a1a242c1db38b4eb44f Mon Sep 17 00:00:00 2001 From: Konrad Konieczny Date: Mon, 12 May 2025 21:22:19 +0200 Subject: [PATCH 127/136] remove merchant_handles_consumer_data configuration and related method; set consumer data handling to true in payment request --- Gateway/Config/Config.php | 10 ---------- Gateway/Request/CreatePaymentRequestBuilder.php | 2 +- etc/config.xml | 1 - 3 files changed, 1 insertion(+), 12 deletions(-) diff --git a/Gateway/Config/Config.php b/Gateway/Config/Config.php index 9bbd68bf..59316262 100644 --- a/Gateway/Config/Config.php +++ b/Gateway/Config/Config.php @@ -148,16 +148,6 @@ public function getPaymentAction(): string MethodInterface::ACTION_AUTHORIZE; } - /** - * Get if the merchant handles consumer data - * - * @return mixed|null - */ - public function getMerchantHandlesConsumerData() - { - return $this->getValue('merchant_handles_consumer_data'); - } - /** * Get the country code * diff --git a/Gateway/Request/CreatePaymentRequestBuilder.php b/Gateway/Request/CreatePaymentRequestBuilder.php index bf778c17..0ae6d637 100644 --- a/Gateway/Request/CreatePaymentRequestBuilder.php +++ b/Gateway/Request/CreatePaymentRequestBuilder.php @@ -194,7 +194,7 @@ public function buildCheckout(Order $order): HostedCheckout|EmbeddedCheckout termsUrl : $this->config->getWebshopTermsAndConditionsUrl(), consumer : $this->buildConsumer($order), isAutoCharge : $this->config->getPaymentAction() == 'authorize_capture', - merchantHandlesConsumerData:(bool)$this->config->getMerchantHandlesConsumerData(), + merchantHandlesConsumerData: true, countryCode : $this->getThreeLetterCountryCode(), ); } diff --git a/etc/config.xml b/etc/config.xml index 04c74dca..7f3953c8 100644 --- a/etc/config.xml +++ b/etc/config.xml @@ -40,7 +40,6 @@ HostedPaymentPage - 1 From 3d027d3f18a4ce26c1731fb2cd68e30211299464 Mon Sep 17 00:00:00 2001 From: Konrad Konieczny Date: Tue, 13 May 2025 08:47:13 +0200 Subject: [PATCH 128/136] Remove merchant_handles_consumer_data from config and set merchantHandlesConsumerData to true in CreatePaymentRequestBuilder --- Gateway/Config/Config.php | 10 ---------- Gateway/Request/CreatePaymentRequestBuilder.php | 2 +- etc/config.xml | 1 - 3 files changed, 1 insertion(+), 12 deletions(-) diff --git a/Gateway/Config/Config.php b/Gateway/Config/Config.php index 9bbd68bf..59316262 100644 --- a/Gateway/Config/Config.php +++ b/Gateway/Config/Config.php @@ -148,16 +148,6 @@ public function getPaymentAction(): string MethodInterface::ACTION_AUTHORIZE; } - /** - * Get if the merchant handles consumer data - * - * @return mixed|null - */ - public function getMerchantHandlesConsumerData() - { - return $this->getValue('merchant_handles_consumer_data'); - } - /** * Get the country code * diff --git a/Gateway/Request/CreatePaymentRequestBuilder.php b/Gateway/Request/CreatePaymentRequestBuilder.php index bf778c17..0ae6d637 100644 --- a/Gateway/Request/CreatePaymentRequestBuilder.php +++ b/Gateway/Request/CreatePaymentRequestBuilder.php @@ -194,7 +194,7 @@ public function buildCheckout(Order $order): HostedCheckout|EmbeddedCheckout termsUrl : $this->config->getWebshopTermsAndConditionsUrl(), consumer : $this->buildConsumer($order), isAutoCharge : $this->config->getPaymentAction() == 'authorize_capture', - merchantHandlesConsumerData:(bool)$this->config->getMerchantHandlesConsumerData(), + merchantHandlesConsumerData: true, countryCode : $this->getThreeLetterCountryCode(), ); } diff --git a/etc/config.xml b/etc/config.xml index 04c74dca..7f3953c8 100644 --- a/etc/config.xml +++ b/etc/config.xml @@ -40,7 +40,6 @@ HostedPaymentPage - 1 From 6a390860ed85356f0ee3da0bef631d9ee565d62f Mon Sep 17 00:00:00 2001 From: Konrad Konieczny Date: Tue, 13 May 2025 10:16:40 +0200 Subject: [PATCH 129/136] Add StringSanitizer for input sanitization in SalesDocumentItemsBuilder and CreatePaymentRequestBuilder --- .../Request/CreatePaymentRequestBuilder.php | 20 +++++++----- .../SalesDocumentItemsBuilder.php | 12 ++++--- Gateway/StringSanitizer.php | 31 +++++++++++++++++++ 3 files changed, 51 insertions(+), 12 deletions(-) create mode 100644 Gateway/StringSanitizer.php diff --git a/Gateway/Request/CreatePaymentRequestBuilder.php b/Gateway/Request/CreatePaymentRequestBuilder.php index 0ae6d637..c96e0358 100644 --- a/Gateway/Request/CreatePaymentRequestBuilder.php +++ b/Gateway/Request/CreatePaymentRequestBuilder.php @@ -13,6 +13,7 @@ use Magento\Sales\Model\Order\Item as OrderItem; use Nexi\Checkout\Gateway\Config\Config; use Nexi\Checkout\Gateway\Request\NexiCheckout\SalesDocumentItemsBuilder; +use Nexi\Checkout\Gateway\StringSanitizer; use Nexi\Checkout\Model\WebhookHandler; use NexiCheckout\Model\Request\Item; use NexiCheckout\Model\Request\Payment; @@ -45,7 +46,8 @@ public function __construct( private readonly CountryInformationAcquirerInterface $countryInformationAcquirer, private readonly EncryptorInterface $encryptor, private readonly WebhookHandler $webhookHandler, - private readonly AmountConverter $amountConverter + private readonly AmountConverter $amountConverter, + private readonly StringSanitizer $stringSanitizer, ) { } @@ -213,22 +215,22 @@ private function buildConsumer(Order $order): Consumer email : $order->getCustomerEmail(), reference : $order->getCustomerId(), shippingAddress: new Address( - addressLine1: $order->getShippingAddress()->getStreetLine(1), - addressLine2: $order->getShippingAddress()->getStreetLine(2), + addressLine1: $this->stringSanitizer->sanitize($order->getShippingAddress()->getStreetLine(1)), + addressLine2: $this->stringSanitizer->sanitize($order->getShippingAddress()->getStreetLine(2)), postalCode : $order->getShippingAddress()->getPostcode(), - city : $order->getShippingAddress()->getCity(), + city : $this->stringSanitizer->sanitize($order->getShippingAddress()->getCity()), country : $this->getThreeLetterCountryCode(), ), billingAddress : new Address( - addressLine1: $order->getBillingAddress()->getStreetLine(1), - addressLine2: $order->getBillingAddress()->getStreetLine(2), + addressLine1: $this->stringSanitizer->sanitize($order->getBillingAddress()->getStreetLine(1)), + addressLine2: $this->stringSanitizer->sanitize($order->getBillingAddress()->getStreetLine(2)), postalCode : $order->getBillingAddress()->getPostcode(), city : $order->getBillingAddress()->getCity(), country : $this->getThreeLetterCountryCode(), ), privatePerson : new PrivatePerson( - firstName: $order->getCustomerFirstname(), - lastName : $order->getCustomerLastname(), + firstName: $this->stringSanitizer->sanitize($order->getCustomerFirstname()), + lastName : $this->stringSanitizer->sanitize($order->getCustomerLastname()), ) ); } @@ -245,4 +247,6 @@ public function getThreeLetterCountryCode(): string $this->config->getCountryCode() )->getThreeLetterAbbreviation(); } + + } diff --git a/Gateway/Request/NexiCheckout/SalesDocumentItemsBuilder.php b/Gateway/Request/NexiCheckout/SalesDocumentItemsBuilder.php index c56620ae..e6deae8b 100644 --- a/Gateway/Request/NexiCheckout/SalesDocumentItemsBuilder.php +++ b/Gateway/Request/NexiCheckout/SalesDocumentItemsBuilder.php @@ -7,6 +7,7 @@ use Magento\Sales\Api\Data\CreditmemoInterface; use Magento\Sales\Api\Data\InvoiceInterface; use Nexi\Checkout\Gateway\AmountConverter; +use Nexi\Checkout\Gateway\StringSanitizer; use NexiCheckout\Model\Request\Item; class SalesDocumentItemsBuilder @@ -16,7 +17,10 @@ class SalesDocumentItemsBuilder /** * @param AmountConverter $amountConverter */ - public function __construct(private readonly AmountConverter $amountConverter) + public function __construct( + private readonly AmountConverter $amountConverter, + private readonly StringSanitizer $stringSanitizer, + ) { } @@ -32,13 +36,13 @@ public function build(CreditmemoInterface|InvoiceInterface $salesObject): array $items = []; foreach ($salesObject->getAllItems() as $item) { $items[] = new Item( - name : $item->getName(), + name : $this->stringSanitizer->sanitize($item->getName()), quantity : (float)$item->getQty(), unit : 'pcs', unitPrice : $this->amountConverter->convertToNexiAmount($item->getPrice()), grossTotalAmount: $this->amountConverter->convertToNexiAmount($item->getRowTotalInclTax()), netTotalAmount : $this->amountConverter->convertToNexiAmount($item->getRowTotal()), - reference : $item->getSku(), + reference : $this->stringSanitizer->sanitize($item->getSku()), taxRate : $this->amountConverter->convertToNexiAmount($item->getTaxPercent()), taxAmount : $this->amountConverter->convertToNexiAmount($item->getTaxAmount()), ); @@ -46,7 +50,7 @@ public function build(CreditmemoInterface|InvoiceInterface $salesObject): array if ($salesObject->getShippingInclTax()) { $items[] = new Item( - name : $salesObject->getOrder()->getShippingDescription(), + name : $this->stringSanitizer->sanitize($salesObject->getOrder()->getShippingDescription()), quantity : 1, unit : 'pcs', unitPrice : $this->amountConverter->convertToNexiAmount($salesObject->getShippingAmount()), diff --git a/Gateway/StringSanitizer.php b/Gateway/StringSanitizer.php new file mode 100644 index 00000000..b01cb77f --- /dev/null +++ b/Gateway/StringSanitizer.php @@ -0,0 +1,31 @@ +, ', ", &, \ + * + * @param string $string + * @param int $maxLength + * + * @return string + */ + public function sanitize(string $string, $maxLength = 128) + { + // replace special chars: The city. Must be between 1 and 128 characters. The following special characters are not supported: <, >, ', ", &, \ + $string = preg_replace('/[<>\'"&\\\\]/', '-', $string); + + if (strlen($string) > $maxLength) { + return substr($string, 0, $maxLength); + } + + return $string; + } +} From afff8d9cd67a99c4556838dfea6e18d887e160bb Mon Sep 17 00:00:00 2001 From: Konrad Konieczny Date: Tue, 13 May 2025 10:43:35 +0200 Subject: [PATCH 130/136] Add StringSanitizer dependency to CreatePaymentRequestBuilder constructor --- Gateway/Request/CreatePaymentRequestBuilder.php | 1 + 1 file changed, 1 insertion(+) diff --git a/Gateway/Request/CreatePaymentRequestBuilder.php b/Gateway/Request/CreatePaymentRequestBuilder.php index c96e0358..8769a467 100644 --- a/Gateway/Request/CreatePaymentRequestBuilder.php +++ b/Gateway/Request/CreatePaymentRequestBuilder.php @@ -39,6 +39,7 @@ class CreatePaymentRequestBuilder implements BuilderInterface * @param EncryptorInterface $encryptor * @param WebhookHandler $webhookHandler * @param AmountConverter $amountConverter + * @param StringSanitizer $stringSanitizer */ public function __construct( private readonly UrlInterface $url, From c1258e38214ef30f34237f9b51179c6cee40e1d7 Mon Sep 17 00:00:00 2001 From: Konrad Konieczny Date: Tue, 13 May 2025 10:49:05 +0200 Subject: [PATCH 131/136] Remove unnecessary comments and whitespace in StringSanitizer and PaymentRefundFailed classes --- Gateway/StringSanitizer.php | 1 - Model/Webhook/PaymentRefundFailed.php | 1 - 2 files changed, 2 deletions(-) diff --git a/Gateway/StringSanitizer.php b/Gateway/StringSanitizer.php index b01cb77f..e1aaeac0 100644 --- a/Gateway/StringSanitizer.php +++ b/Gateway/StringSanitizer.php @@ -19,7 +19,6 @@ class StringSanitizer */ public function sanitize(string $string, $maxLength = 128) { - // replace special chars: The city. Must be between 1 and 128 characters. The following special characters are not supported: <, >, ', ", &, \ $string = preg_replace('/[<>\'"&\\\\]/', '-', $string); if (strlen($string) > $maxLength) { diff --git a/Model/Webhook/PaymentRefundFailed.php b/Model/Webhook/PaymentRefundFailed.php index 3c6bf22c..b8cb634e 100644 --- a/Model/Webhook/PaymentRefundFailed.php +++ b/Model/Webhook/PaymentRefundFailed.php @@ -4,7 +4,6 @@ namespace Nexi\Checkout\Model\Webhook; - class PaymentRefundFailed implements WebhookProcessorInterface { /** From 75aebe31d972af028ee13de1e59c732d1af5a284 Mon Sep 17 00:00:00 2001 From: Konrad Konieczny Date: Tue, 13 May 2025 11:47:43 +0200 Subject: [PATCH 132/136] add phone number parser --- .../Request/CreatePaymentRequestBuilder.php | 23 ++++++++++++++++++- composer.json | 3 ++- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/Gateway/Request/CreatePaymentRequestBuilder.php b/Gateway/Request/CreatePaymentRequestBuilder.php index 8769a467..9d087fcc 100644 --- a/Gateway/Request/CreatePaymentRequestBuilder.php +++ b/Gateway/Request/CreatePaymentRequestBuilder.php @@ -232,7 +232,8 @@ private function buildConsumer(Order $order): Consumer privatePerson : new PrivatePerson( firstName: $this->stringSanitizer->sanitize($order->getCustomerFirstname()), lastName : $this->stringSanitizer->sanitize($order->getCustomerLastname()), - ) + ), + phoneNumber : $this->getNumber($order) ); } @@ -249,5 +250,25 @@ public function getThreeLetterCountryCode(): string )->getThreeLetterAbbreviation(); } + /** + * Build phone number object for the payment + * + * @param Order $order + * + * @return Payment\PhoneNumber + */ + public function getNumber(Order $order): Payment\PhoneNumber + { + $lib = \libphonenumber\PhoneNumberUtil::getInstance(); + + $number = $lib->parse( + $order->getShippingAddress()->getTelephone(), + $order->getShippingAddress()->getCountryId() + ); + return new Payment\PhoneNumber( + prefix: '+' . $number->getCountryCode(), + number: (string)$number->getNationalNumber(), + ); + } } diff --git a/composer.json b/composer.json index 922f5566..1af20453 100644 --- a/composer.json +++ b/composer.json @@ -5,7 +5,8 @@ "php": "^8.0", "nexi-checkout/php-payment-sdk": "^0.4.1", "magento/framework": ">=102.0.0", - "ext-curl": "*" + "ext-curl": "*", + "giggsey/libphonenumber-for-php-lite": "^9.0.5" }, "require-dev": { "magento/magento-coding-standard": "*", From 479f48be00b140957aebeb99e29de71e91d1b7f8 Mon Sep 17 00:00:00 2001 From: Konrad Konieczny Date: Tue, 13 May 2025 11:54:13 +0200 Subject: [PATCH 133/136] Add phone number parsing functionality to CreatePaymentRequestBuilder --- Gateway/Request/CreatePaymentRequestBuilder.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Gateway/Request/CreatePaymentRequestBuilder.php b/Gateway/Request/CreatePaymentRequestBuilder.php index 9d087fcc..497dfdb3 100644 --- a/Gateway/Request/CreatePaymentRequestBuilder.php +++ b/Gateway/Request/CreatePaymentRequestBuilder.php @@ -4,6 +4,8 @@ namespace Nexi\Checkout\Gateway\Request; +use libphonenumber\NumberParseException; +use libphonenumber\PhoneNumberUtil; use Magento\Directory\Api\CountryInformationAcquirerInterface; use Magento\Framework\Encryption\EncryptorInterface; use Magento\Framework\Exception\NoSuchEntityException; @@ -256,10 +258,11 @@ public function getThreeLetterCountryCode(): string * @param Order $order * * @return Payment\PhoneNumber + * @throws NumberParseException */ public function getNumber(Order $order): Payment\PhoneNumber { - $lib = \libphonenumber\PhoneNumberUtil::getInstance(); + $lib = PhoneNumberUtil::getInstance(); $number = $lib->parse( $order->getShippingAddress()->getTelephone(), From 3b6fba3aae5def219c10ed5e8436ad6cc2e60724 Mon Sep 17 00:00:00 2001 From: Konrad Konieczny Date: Tue, 13 May 2025 13:18:25 +0200 Subject: [PATCH 134/136] Add StringSanitizer dependency and improve escaping in testconnection.phtml --- .../NexiCheckout/SalesDocumentItemsBuilder.php | 4 ++-- .../templates/system/config/testconnection.phtml | 11 ++++++----- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/Gateway/Request/NexiCheckout/SalesDocumentItemsBuilder.php b/Gateway/Request/NexiCheckout/SalesDocumentItemsBuilder.php index e6deae8b..d7822fcf 100644 --- a/Gateway/Request/NexiCheckout/SalesDocumentItemsBuilder.php +++ b/Gateway/Request/NexiCheckout/SalesDocumentItemsBuilder.php @@ -16,12 +16,12 @@ class SalesDocumentItemsBuilder /** * @param AmountConverter $amountConverter + * @param StringSanitizer $stringSanitizer */ public function __construct( private readonly AmountConverter $amountConverter, private readonly StringSanitizer $stringSanitizer, - ) - { + ) { } /** diff --git a/view/adminhtml/templates/system/config/testconnection.phtml b/view/adminhtml/templates/system/config/testconnection.phtml index afd06e0a..57a1299e 100644 --- a/view/adminhtml/templates/system/config/testconnection.phtml +++ b/view/adminhtml/templates/system/config/testconnection.phtml @@ -1,13 +1,14 @@ From 19ca7af49571c7336766c3cd6d7585f53931e44f Mon Sep 17 00:00:00 2001 From: Konrad Konieczny Date: Tue, 20 May 2025 14:13:22 +0200 Subject: [PATCH 135/136] Refactor PhoneNumber return type in getNumber method --- Gateway/Request/CreatePaymentRequestBuilder.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Gateway/Request/CreatePaymentRequestBuilder.php b/Gateway/Request/CreatePaymentRequestBuilder.php index 497dfdb3..5224a43f 100644 --- a/Gateway/Request/CreatePaymentRequestBuilder.php +++ b/Gateway/Request/CreatePaymentRequestBuilder.php @@ -25,6 +25,7 @@ use NexiCheckout\Model\Request\Payment\HostedCheckout; use NexiCheckout\Model\Request\Payment\IntegrationTypeEnum; use NexiCheckout\Model\Request\Payment\PrivatePerson; +use NexiCheckout\Model\Request\Payment\PhoneNumber; use NexiCheckout\Model\Request\Shared\Notification; use NexiCheckout\Model\Request\Shared\Notification\Webhook; use NexiCheckout\Model\Request\Shared\Order as NexiRequestOrder; @@ -257,10 +258,10 @@ public function getThreeLetterCountryCode(): string * * @param Order $order * - * @return Payment\PhoneNumber + * @return PhoneNumber * @throws NumberParseException */ - public function getNumber(Order $order): Payment\PhoneNumber + public function getNumber(Order $order): PhoneNumber { $lib = PhoneNumberUtil::getInstance(); @@ -269,7 +270,7 @@ public function getNumber(Order $order): Payment\PhoneNumber $order->getShippingAddress()->getCountryId() ); - return new Payment\PhoneNumber( + return new PhoneNumber( prefix: '+' . $number->getCountryCode(), number: (string)$number->getNationalNumber(), ); From f29f610d00b6dd475bd6da5819ae8bcf818ccf49 Mon Sep 17 00:00:00 2001 From: Konrad Konieczny Date: Tue, 20 May 2025 14:15:04 +0200 Subject: [PATCH 136/136] Update copyright holder in LICENSE file --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index 299e946c..dd19a0f5 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Solteq +Copyright (c) 2025 Nexi | Nets Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal