diff --git a/CHANGELOG.md b/CHANGELOG.md index fdd364a..6ffb387 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,8 @@ # Changelog All notable changes to this project will be documented in this file. +## [3.18] +- Add support for CheckoutSession (MarketPay) to associate a checkout session with the payment request. ## [3.17] - Streamline and simplify management of order lines and amounts. - Support for the new PayPal Integration. diff --git a/admin/controller/altapay/templates/catalog/controller/altapay.twig.php b/admin/controller/altapay/templates/catalog/controller/altapay.twig.php index c89658a..32562e4 100644 --- a/admin/controller/altapay/templates/catalog/controller/altapay.twig.php +++ b/admin/controller/altapay/templates/catalog/controller/altapay.twig.php @@ -6,6 +6,7 @@ use Altapay\Api\Ecommerce\Callback; use Altapay\Api\Ecommerce\PaymentRequest; use Altapay\Api\Payments\CaptureReservation; +use Altapay\Api\Payments\CheckoutSession; use Altapay\Api\Payments\RefundCapturedReservation; use Altapay\Api\Payments\ReleaseReservation; use Altapay\Exceptions\ClientException; @@ -270,7 +271,40 @@ public function confirm() $customerInfo = $this->setCustomer($order_info); + // Stable identifier across order attempts in the same checkout flow. + $checkoutFlowId = 'order_' . (int)$order_info['order_id']; + + $sessionKey = 'altapay_checkout_session_id_' . $checkoutFlowId; + $sessionId = isset($this->session->data[$sessionKey]) ? $this->session->data[$sessionKey] : ''; + + if (empty($sessionId)) { + try { + $activeTerminals = $this->getActiveTerminalNames(); + $sessionId = $this->getHashedCheckoutSessionId($checkoutFlowId); + $marketPaySession = new CheckoutSession($this->getAuth()); + $marketPaySession->setTerminals($activeTerminals) + ->setTerminal($this->terminal_key) + ->setShopOrderId($order_info['order_id']) + ->setAmount((float)$amount) + ->setCurrency($currency) + ->setSessionId($sessionId); + + $checkoutResponse = $marketPaySession->call(); + if (isset($checkoutResponse->Session->Id)) { + $sessionId = $checkoutResponse->Session->Id; + } + $this->session->data[$sessionKey] = $sessionId; + } catch (\Exception $e) { + error_log('AltaPay checkout session creation failed for order ' . $order_info['order_id'] . ': ' . $e->getMessage()); + } + } + $request = new PaymentRequest($this->getAuth()); + + if ( $sessionId ) { + $request->setSessionId( $sessionId ); + } + $request->setTerminal($this->terminal_key) ->setShopOrderId($order_info['order_id']) ->setAmount($totalOrderAmount) @@ -316,6 +350,76 @@ public function confirm() } } + /** + * Build list of active AltaPay terminal names (current terminal first). + * + * @return array + */ + private function getActiveTerminalNames() + { + $activeTerminals = array(); + $currentTerminalName = $this->terminal_key; + if (!empty(trim((string)$currentTerminalName))) { + $activeTerminals[] = $currentTerminalName; + } + + $rows = $this->db->query("SELECT `key`, `value` FROM " . DB_PREFIX . "setting WHERE `key` LIKE 'payment\\_Altapay\\_%\\_status' OR `key` LIKE 'payment\\_Altapay\\_%\\_title'"); + + $statuses = array(); + $titles = array(); + if ($rows->num_rows) { + foreach ($rows->rows as $row) { + if (preg_match('/^payment_Altapay_(.+)_status$/', $row['key'], $m)) { + $statuses[$m[1]] = $row['value']; + } elseif (preg_match('/^payment_Altapay_(.+)_title$/', $row['key'], $m)) { + $titles[$m[1]] = $row['value']; + } + } + } + + foreach ($statuses as $termKey => $status) { + if ((string)$status !== '1' || !isset($titles[$termKey])) { + continue; + } + $name = $titles[$termKey]; + if (!empty(trim((string)$name)) && $name !== $currentTerminalName) { + $activeTerminals[] = $name; + } + } + + return $activeTerminals; + } + + /** + * Generate a non-predictable checkout session id for AltaPay. + * + * Uses the OpenCart checkout session id plus the OpenCart encryption secret + * so the resulting value is stable for the same checkout flow and not guessable. + * + * @param string $checkoutFlowId + * + * @return string + */ + private function getHashedCheckoutSessionId($checkoutFlowId) + { + $salt = $this->config->get('config_encryption'); + $raw = $checkoutFlowId . '|' . $salt; + return substr(hash('sha256', $raw), 0, 30); + } + + /** + * Clear AltaPay checkout session data from OpenCart session. + * + */ + private function clearAltaPayCheckoutSessionData() + { + foreach ($this->session->data as $key => $value) { + if (strpos($key, 'altapay_checkout_session_id_') === 0) { + unset($this->session->data[$key]); + } + } + } + /** * @param array $order_info * @@ -476,6 +580,8 @@ public function accept() $this->model_checkout_order->addOrderHistory($order_id, $this->config->get('payment_Altapay_{key}_order_status_id'), $comment, true); + $this->clearAltaPayCheckoutSessionData(); + // Redirect to order success $this->response->redirect($this->url->link('checkout/success', 'user_token=' . $this->session->data['user_token'], true)); } diff --git a/admin/controller/traits/traits.php b/admin/controller/traits/traits.php index 7db9254..d0bc5e6 100644 --- a/admin/controller/traits/traits.php +++ b/admin/controller/traits/traits.php @@ -13,7 +13,7 @@ public function transactionInfo($transactionInfo = array()) 'ecomPlatform' => 'OpenCart', 'ecomVersion' => VERSION, 'altapayPluginName' => 'AltaPay', - 'altapayPluginVersion' => '3.17', + 'altapayPluginVersion' => '3.18', 'otherInfo' => $otherinfo, ); diff --git a/catalog/traits/traits.php b/catalog/traits/traits.php index b102cf1..574e21d 100644 --- a/catalog/traits/traits.php +++ b/catalog/traits/traits.php @@ -16,7 +16,7 @@ public function transactionInfo($transactionInfo = array()) 'ecomPlatform' => 'OpenCart', 'ecomVersion' => VERSION, 'altapayPluginName' => 'AltaPay', - 'altapayPluginVersion' => '3.17', + 'altapayPluginVersion' => '3.18', 'otherInfo' => $otherinfo, ); diff --git a/composer.json b/composer.json index c8f2aac..7dd80ea 100644 --- a/composer.json +++ b/composer.json @@ -1,6 +1,6 @@ { "require": { - "altapay/api-php": "^3.2" + "altapay/api-php": "^3.6" }, "config": { "vendor-dir": "altapay-libs" diff --git a/composer.lock b/composer.lock index a1ae97a..e5bda3a 100644 --- a/composer.lock +++ b/composer.lock @@ -4,20 +4,20 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "0e8adc3bf77d03f1a4a54ca98525e812", + "content-hash": "fcc08cbe775c21de805a453666048bc9", "packages": [ { "name": "altapay/api-php", - "version": "3.3.5", + "version": "3.6.0", "source": { "type": "git", "url": "https://github.com/AltaPay/api-php.git", - "reference": "a79f23dc7eb2bfc92355b32047e21583b6b5ef02" + "reference": "035407ca40f86a5573da0f32490702388537c989" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/AltaPay/api-php/zipball/a79f23dc7eb2bfc92355b32047e21583b6b5ef02", - "reference": "a79f23dc7eb2bfc92355b32047e21583b6b5ef02", + "url": "https://api.github.com/repos/AltaPay/api-php/zipball/035407ca40f86a5573da0f32490702388537c989", + "reference": "035407ca40f86a5573da0f32490702388537c989", "shasum": "" }, "require": { @@ -29,14 +29,14 @@ "ext-simplexml": "*", "ext-spl": "*", "guzzlehttp/guzzle": "^6.0 || ^7.0", - "php": "^5.6 || ^7.0 || ^8.0", - "symfony/event-dispatcher": "^2.1 || ^3.0 || ^4.0 || ^5.0 || ^6.0", - "symfony/options-resolver": "^2.6 || ^3.0 || ^4.0 || ^5.0 || ^6.0" + "php": "^7.1 || ^8.0", + "symfony/event-dispatcher": "^2.1 || ^3.0 || ^4.0 || ^5.0 || ^6.0 || ^7.0", + "symfony/options-resolver": "^2.6 || ^3.0 || ^4.0 || ^5.0 || ^6.0 || ^7.0" }, "require-dev": { "friendsofphp/php-cs-fixer": "^3.0", "fzaninotto/faker": "^1.6", - "phpstan/phpstan": "^1.8", + "phpstan/phpstan": "^2.1", "phpstan/phpstan-phpunit": "*", "phpstan/phpstan-strict-rules": "*", "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0" @@ -58,9 +58,9 @@ ], "support": { "issues": "https://github.com/AltaPay/api-php/issues", - "source": "https://github.com/AltaPay/api-php/tree/3.3.5" + "source": "https://github.com/AltaPay/api-php/tree/3.6.0" }, - "time": "2023-05-24T05:22:15+00:00" + "time": "2026-05-29T10:24:12+00:00" }, { "name": "guzzlehttp/guzzle", @@ -1107,10 +1107,10 @@ "packages-dev": [], "aliases": [], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, - "platform": [], - "platform-dev": [], + "platform": {}, + "platform-dev": {}, "plugin-api-version": "2.6.0" } diff --git a/tests/integration-tests/cypress/e2e/PageObjects/objects.cy.js b/tests/integration-tests/cypress/e2e/PageObjects/objects.cy.js index 25d8914..0be2492 100644 --- a/tests/integration-tests/cypress/e2e/PageObjects/objects.cy.js +++ b/tests/integration-tests/cypress/e2e/PageObjects/objects.cy.js @@ -48,8 +48,6 @@ class Order { cy.get('#button-payment-method').click() cy.get('#button-confirm').click() cy.get('[id=creditCardNumberInput]').type('4111111111111111') - cy.get('#emonth').type('01') - cy.get('#eyear').type('2023') cy.get('#cvcInput').type('123') cy.get('#cardholderNameInput').type('testname') cy.get('#pensioCreditCardPaymentSubmitButton').click().wait(2000) @@ -90,7 +88,11 @@ class Order { cy.get('#input-username').type(admin.adminUsername) cy.get('#input-password').type(admin.adminPass) cy.get('.btn').click() - cy.get('.close').click() + cy.get('body').then(($body) => { + if ($body.find('.close').length > 0) { + cy.get('.close').click() + } + }) cy.get('h1').should('have.text', 'Dashboard') }) } diff --git a/tests/integration-tests/cypress/integration/PageObjects/objects.js b/tests/integration-tests/cypress/integration/PageObjects/objects.js index 26c8edc..648b8e5 100644 --- a/tests/integration-tests/cypress/integration/PageObjects/objects.js +++ b/tests/integration-tests/cypress/integration/PageObjects/objects.js @@ -48,8 +48,6 @@ class Order { cy.get('#button-payment-method').click() cy.get('#button-confirm').click() cy.get('[id=creditCardNumberInput]').type('4111111111111111') - cy.get('#emonth').type('01') - cy.get('#eyear').type('2023') cy.get('#cvcInput').type('123') cy.get('#cardholderNameInput').type('testname') cy.get('#pensioCreditCardPaymentSubmitButton').click().wait(2000)