From 59d2ce3cd125235005d116ca132a326b72394ca5 Mon Sep 17 00:00:00 2001 From: O K Date: Fri, 12 Dec 2025 10:46:12 +0200 Subject: [PATCH] fixes --- .gitignore | 2 + admin/controller/payment/hutko.php | 205 +++++++++++------- admin/language/uk-ua/payment/hutko.php | 16 -- admin/view/template/payment/hutko.twig | 86 +++++++- .../payment/hutko_order_info_panel.twig | 51 +++-- catalog/controller/payment/hutko.php | 185 +++++++++++++--- 6 files changed, 386 insertions(+), 159 deletions(-) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0f2fe10 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +llmdumper.php +.llmdump \ No newline at end of file diff --git a/admin/controller/payment/hutko.php b/admin/controller/payment/hutko.php index 0b0b708..da11582 100644 --- a/admin/controller/payment/hutko.php +++ b/admin/controller/payment/hutko.php @@ -7,7 +7,6 @@ class Hutko extends \Opencart\System\Engine\Controller { public function index(): void { $this->load->language('extension/hutko/payment/hutko'); - $this->document->setTitle($this->language->get('heading_title')); $data['breadcrumbs'] = []; @@ -24,28 +23,16 @@ class Hutko extends \Opencart\System\Engine\Controller { 'href' => $this->url->link('extension/hutko/payment/hutko', 'user_token=' . $this->session->data['user_token']) ]; - // Save action $data['save'] = $this->url->link('extension/hutko/payment/hutko.save', 'user_token=' . $this->session->data['user_token']); $data['back'] = $this->url->link('marketplace/extension', 'user_token=' . $this->session->data['user_token'] . '&type=payment'); - // Configuration Fields + // Config fields $fields = [ - 'payment_hutko_merchant_id', - 'payment_hutko_secret_key', - 'payment_hutko_shipping_include', - 'payment_hutko_shipping_product_name', - 'payment_hutko_shipping_product_code', - 'payment_hutko_new_order_status_id', - 'payment_hutko_success_status_id', - 'payment_hutko_declined_status_id', - 'payment_hutko_expired_status_id', - 'payment_hutko_refunded_status_id', - 'payment_hutko_include_discount_to_total', - 'payment_hutko_status', - 'payment_hutko_sort_order', - 'payment_hutko_geo_zone_id', - 'payment_hutko_total', - 'payment_hutko_save_logs' + 'payment_hutko_merchant_id', 'payment_hutko_secret_key', 'payment_hutko_shipping_include', + 'payment_hutko_shipping_product_name', 'payment_hutko_shipping_product_code', + 'payment_hutko_new_order_status_id', 'payment_hutko_success_status_id', 'payment_hutko_declined_status_id', + 'payment_hutko_expired_status_id', 'payment_hutko_refunded_status_id', 'payment_hutko_include_discount_to_total', + 'payment_hutko_status', 'payment_hutko_sort_order', 'payment_hutko_geo_zone_id', 'payment_hutko_total', 'payment_hutko_save_logs' ]; foreach ($fields as $field) { @@ -53,9 +40,11 @@ class Hutko extends \Opencart\System\Engine\Controller { } // Defaults - if (is_null($data['payment_hutko_shipping_product_name'])) $data['payment_hutko_shipping_product_name'] = 'Package material'; - if (is_null($data['payment_hutko_shipping_product_code'])) $data['payment_hutko_shipping_product_code'] = '0_0_1'; + if (is_null($data['payment_hutko_shipping_product_name'])) $data['payment_hutko_shipping_product_name'] = 'Shipping'; + if (is_null($data['payment_hutko_shipping_product_code'])) $data['payment_hutko_shipping_product_code'] = 'SHIPPING_001'; if (is_null($data['payment_hutko_total'])) $data['payment_hutko_total'] = '0.01'; + if (is_null($data['payment_hutko_shipping_include'])) $data['payment_hutko_shipping_include'] = 1; + if (is_null($data['payment_hutko_include_discount_to_total'])) $data['payment_hutko_include_discount_to_total'] = 1; $this->load->model('localisation/order_status'); $data['order_statuses'] = $this->model_localisation_order_status->getOrderStatuses(); @@ -64,7 +53,7 @@ class Hutko extends \Opencart\System\Engine\Controller { $data['geo_zones'] = $this->model_localisation_geo_zone->getGeoZones(); $data['log_content'] = $this->displayLastDayLog(); - + $data['header'] = $this->load->controller('common/header'); $data['column_left'] = $this->load->controller('common/column_left'); $data['footer'] = $this->load->controller('common/footer'); @@ -74,14 +63,12 @@ class Hutko extends \Opencart\System\Engine\Controller { public function save(): void { $this->load->language('extension/hutko/payment/hutko'); - $json = []; if (!$this->user->hasPermission('modify', 'extension/hutko/payment/hutko')) { $json['error']['warning'] = $this->language->get('error_permission'); } - // Validation if (empty($this->request->post['payment_hutko_merchant_id']) || !is_numeric($this->request->post['payment_hutko_merchant_id'])) { $json['error']['payment_hutko_merchant_id'] = $this->language->get('error_merchant_id_numeric'); } @@ -104,83 +91,128 @@ class Hutko extends \Opencart\System\Engine\Controller { public function install(): void { $this->load->model('extension/hutko/payment/hutko'); $this->model_extension_hutko_payment_hutko->install(); - - // OC4 Event Registration + + // Register Event $this->load->model('setting/event'); - $event_code = 'hutko_order_info'; - $event_trigger = 'admin/view/sale/order_info/after'; - $event_action = 'extension/hutko/payment/hutko.order_info'; + // Remove if exists to prevent duplicates + $this->model_setting_event->deleteEventByCode('hutko_order_info'); - // OC 4.0.2.0 introduced the array signature for addEvent - if (version_compare(VERSION, '4.0.2.0', '>=')) { - $this->model_setting_event->addEvent([ - 'code' => $event_code, - 'description' => 'Hutko Payment Info Panel', - 'trigger' => $event_trigger, - 'action' => $event_action, - 'status' => 1, - 'sort_order' => 0 - ]); - } else { - // Legacy argument style for 4.0.0.0 - 4.0.1.x - $this->model_setting_event->addEvent($event_code, $event_trigger, $event_action, 1, 0); + $event_data = [ + 'code' => 'hutko_order_info', + 'description' => 'Hutko Payment Info Panel', + 'trigger' => 'admin/view/sale/order_info/after', + 'action' => 'extension/hutko/payment/hutko.order_info', + 'status' => 1, + 'sort_order' => 1 + ]; + + if (version_compare(VERSION, '4.0.0.0', '>=')) { + // OC 4.0.2.0+ uses array, older 4.0.x uses params. + // We try array first (modern way). + try { + $this->model_setting_event->addEvent($event_data); + } catch (\Exception $e) { + // Fallback for older 4.0.0.0 versions + $this->model_setting_event->addEvent('hutko_order_info', 'admin/view/sale/order_info/after', 'extension/hutko/payment/hutko.order_info', 1, 1); + } } } public function uninstall(): void { - $this->load->model('extension/hutko/payment/hutko'); - $this->model_extension_hutko_payment_hutko->uninstall(); - $this->load->model('setting/event'); $this->model_setting_event->deleteEventByCode('hutko_order_info'); } - // Event Handler for Admin Order View + /** + * Event handler: Injects the Hutko panel into Order Info page + */ public function order_info(string &$route, array &$args, string &$output): void { - if (!isset($args['order_id'])) return; + $order_id = isset($args['order_id']) ? (int)$args['order_id'] : 0; + if (!$order_id) return; $this->load->model('sale/order'); - $order_info = $this->model_sale_order->getOrder((int)$args['order_id']); + $order_info = $this->model_sale_order->getOrder($order_id); + + // Check if payment method is Hutko (code can vary slightly depending on how it was saved) + if ($order_info && isset($order_info['payment_code']) && + ($order_info['payment_code'] == 'hutko' || $order_info['payment_code'] == 'hutko.hutko')) { - // FIX: Check if payment_code exists and matches either 'hutko' or 'hutko.hutko' - if ($order_info && isset($order_info['payment_code']) && ($order_info['payment_code'] == 'hutko' || $order_info['payment_code'] == 'hutko.hutko')) { $this->load->language('extension/hutko/payment/hutko'); $this->load->model('extension/hutko/payment/hutko'); - $hutko_order = $this->model_extension_hutko_payment_hutko->getHutkoOrder((int)$args['order_id']); + $hutko_order = $this->model_extension_hutko_payment_hutko->getHutkoOrder($order_id); - $data['hutko_transaction_ref'] = $hutko_order['hutko_transaction_ref'] ?? ''; - $data['order_id'] = (int)$args['order_id']; + // Prepare Data $data['user_token'] = $this->session->data['user_token']; + $data['order_id'] = $order_id; + $data['hutko_transaction_ref'] = $hutko_order['hutko_transaction_ref'] ?? ''; - // URLs for AJAX actions - $data['refund_url'] = $this->url->link('extension/hutko/payment/hutko.refund', 'user_token=' . $this->session->data['user_token']); + // URLs + $data['refund_url'] = $this->url->link('extension/hutko/payment/hutko.refund', 'user_token=' . $this->session->data['user_token'] . '&order_id=' . $order_id); $data['status_url'] = $this->url->link('extension/hutko/payment/hutko.status', 'user_token=' . $this->session->data['user_token']); - // Language Data + // Translations $data['text_payment_information'] = $this->language->get('text_payment_information'); $data['text_hutko_transaction_ref_label'] = $this->language->get('text_hutko_transaction_ref_label'); - $data['hutko_transaction_ref_display'] = $data['hutko_transaction_ref'] ?: $this->language->get('text_not_available'); - $data['text_not_available'] = $this->language->get('text_not_available'); - $data['text_hutko_refund_title'] = $this->language->get('text_hutko_refund_title'); + $data['text_hutko_status_title'] = $this->language->get('text_hutko_status_title'); $data['entry_refund_amount'] = $this->language->get('entry_refund_amount'); $data['entry_refund_comment'] = $this->language->get('entry_refund_comment'); $data['button_hutko_refund'] = $this->language->get('button_hutko_refund'); - $data['text_confirm_refund'] = $this->language->get('text_confirm_refund'); - - $data['text_hutko_status_title'] = $this->language->get('text_hutko_status_title'); $data['button_hutko_status_check'] = $this->language->get('button_hutko_status_check'); + $data['text_confirm_refund'] = $this->language->get('text_confirm_refund'); + $data['text_not_available'] = $this->language->get('text_not_available'); + $data['text_loading'] = $this->language->get('text_loading'); - $content = $this->load->view('extension/hutko/payment/hutko_order', $data); + $data['hutko_transaction_ref_display'] = $data['hutko_transaction_ref'] ?: $data['text_not_available']; - // Inject content before the History tab/card - $pos = strpos($output, '
load->view('extension/hutko/payment/hutko_order_info_panel', $data); + + // Injection Logic: Try to place it before the History card + // We look for the "Order History" text or the history div ID. + // OC4 typically uses id="history" for the history list, but we want to be above the card containing it. + + $markers = [ + '
' // Generic card start, risky but fallback + ]; + + $injected = false; + + // 1. Try to find the specific "History" ID and inject BEFORE the container card usually wrapping it + // Regex looks for the card containing id="history" + // This is complex, so let's try a simpler reliable marker: The closing of the previous card? + + // 2. Best bet: Inject before the div that contains the history load logic + if (strpos($output, 'id="history"') !== false) { + // Attempt to find the CARD that holds the history. + // Usually:
...History...
+ + // Let's just prepend it to the history div itself for simplicity, ensuring it renders. + // Or better, find the header "text_history" usually rendered. + + $search = '
' . $panel_html . '
Order History
' . $search, $output); + + // Note: The replace above assumes a specific structure which might break layout. + // Safer approach: Append to the "Payment Details" tab if it exists, or just prepend to the whole output? No. + + // SAFE INJECTION: Look for the closing of the "Order Details" card (usually the first big card) + // Or just search for the specific history ID and prepend. + + // Let's go with a simpler replace: + $output = str_replace('
language->get('text_refund_success'); - $msg = sprintf($this->language->get('text_refund_success_comment'), $hutko_order['hutko_transaction_ref'], $amount, $comment); + $rev_amt = isset($response['response']['reversal_amount']) ? $response['response']['reversal_amount']/100 : $amount; + + $msg = sprintf($this->language->get('text_refund_success_comment'), + $hutko_order['hutko_transaction_ref'], + $this->currency->format($rev_amt, $order_info['currency_code'], $order_info['currency_value']), + $comment + ); + $this->model_sale_order->addHistory($order_id, $this->config->get('payment_hutko_refunded_status_id'), $msg, true); } else { - $json['error'] = $response['response']['error_message'] ?? 'Unknown API Error'; + $err = $response['response']['error_message'] ?? 'Unknown Error'; + $json['error'] = sprintf($this->language->get('text_refund_api_error'), $err); $this->logOC("Refund Failed: " . json_encode($response)); } } else { @@ -243,11 +283,11 @@ class Hutko extends \Opencart\System\Engine\Controller { if (($response['response']['response_status'] ?? '') === 'success') { $json['success'] = $this->language->get('text_status_success'); - unset($response['response']['response_signature_string'], $response['response']['signature']); $json['data'] = $response['response']; } else { - $json['error'] = $response['response']['error_message'] ?? 'API Error'; + $err = $response['response']['error_message'] ?? 'Unknown Error'; + $json['error'] = sprintf($this->language->get('text_status_api_error'), $err); } } else { $json['error'] = $this->language->get('error_missing_params'); @@ -257,7 +297,6 @@ class Hutko extends \Opencart\System\Engine\Controller { $this->response->setOutput(json_encode($json)); } - // Helpers private function sign(array $data): string { $key = $this->config->get('payment_hutko_secret_key'); $filtered = array_filter($data, function ($v) { return $v !== '' && $v !== null; }); @@ -276,24 +315,28 @@ class Hutko extends \Opencart\System\Engine\Controller { curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']); $res = curl_exec($ch); - // curl_close($ch); + $error = curl_error($ch); + curl_close($ch); + + if ($this->config->get('payment_hutko_save_logs')) { + $this->logOC('Res: ' . $res); + if ($error) $this->logOC('CURL: ' . $error); + } - if ($this->config->get('payment_hutko_save_logs')) $this->logOC('Res: ' . $res); return json_decode($res, true) ?: []; } private function displayLastDayLog() { - if (!$this->config->get('payment_hutko_save_logs')) return 'Logging Disabled'; + if (!$this->config->get('payment_hutko_save_logs')) return $this->language->get('text_logs_disabled'); $file = DIR_LOGS . 'error.log'; - if (!file_exists($file)) return 'Log empty'; + if (!file_exists($file)) return sprintf($this->language->get('text_log_file_not_found'), 'error.log'); $lines = file($file); $output = []; - // Get last 50 lines that match "Hutko" for ($i = count($lines) - 1; $i >= 0 && count($output) < 50; $i--) { - if (strpos($lines[$i], 'Hutko') !== false) $output[] = htmlspecialchars($lines[$i], ENT_QUOTES, 'UTF-8'); + if (strpos($lines[$i], 'Hutko Payment') !== false) $output[] = htmlspecialchars($lines[$i], ENT_QUOTES, 'UTF-8'); } - return implode('
', $output); + return empty($output) ? $this->language->get('text_no_logs_found') : implode('
', $output); } private function logOC($message) { diff --git a/admin/language/uk-ua/payment/hutko.php b/admin/language/uk-ua/payment/hutko.php index aca10f1..19bd903 100644 --- a/admin/language/uk-ua/payment/hutko.php +++ b/admin/language/uk-ua/payment/hutko.php @@ -94,22 +94,6 @@ $_['error_payment_data_build'] = 'Ошибка: не удалось подгот $_['error_api_communication'] = 'Ошибка: не удалось связаться с платежным шлюзом. Повторите попытку.'; $_['text_redirecting_comment'] = 'Перенаправление на Hutko. Идентификатор заказа Hutko: %s. URL: %s'; -// Для обратного вызова -$_['text_payment_approved'] = 'Платеж одобрен Hutko.'; -$_['text_payment_declined'] = 'Платеж отклонен Hutko.'; -$_['text_payment_expired'] = 'Срок платежа истек в Hutko.'; -$_['text_payment_processing'] = 'Платеж обрабатывается в Hutko.'; -$_['text_confirm_refund'] = 'Вы уверены, что хотите возместить оплату через Hutko? Это действие нельзя отменить.'; -$_['text_loading'] = 'Загрузка...'; -$_['error_order_not_found'] = 'Ошибка: заказ не найден.'; - - - - - - - - diff --git a/admin/view/template/payment/hutko.twig b/admin/view/template/payment/hutko.twig index a16b672..058a8bb 100644 --- a/admin/view/template/payment/hutko.twig +++ b/admin/view/template/payment/hutko.twig @@ -35,7 +35,7 @@ {{ tab_order_statuses }}
-{{ footer }} +{{ footer }} \ No newline at end of file diff --git a/admin/view/template/payment/hutko_order_info_panel.twig b/admin/view/template/payment/hutko_order_info_panel.twig index fb36aa0..c40cc55 100644 --- a/admin/view/template/payment/hutko_order_info_panel.twig +++ b/admin/view/template/payment/hutko_order_info_panel.twig @@ -5,31 +5,33 @@
- +
{{ text_hutko_transaction_ref_label }}{{ text_hutko_transaction_ref_label }} {{ hutko_transaction_ref_display }}
{% if hutko_transaction_ref_display != text_not_available %} -
-
{{ text_hutko_refund_title }}
-
-
- -
-
- -
-
- +
+
{{ text_hutko_refund_title }}
+
+
+ +
+
+ +
+
+ +
+
-
-
-
{{ text_hutko_status_title }}
- -
+
+
{{ text_hutko_status_title }}
+ +
+
{% endif %}
@@ -40,7 +42,7 @@ $('#button-hutko-refund').on('click', function () { var btn = $(this); $.ajax({ - url: '{{ hutko_refund_action_url|raw }}', + url: '{{ refund_url|raw }}', type: 'post', dataType: 'json', data: { @@ -49,11 +51,11 @@ $('#button-hutko-refund').on('click', function () { 'order_id': {{ order_id }} }, beforeSend: function () { - btn.prop('disabled', true); + btn.prop('disabled', true).text('{{ text_loading }}'); $('#hutko-refund-response').html(''); }, complete: function () { - btn.prop('disabled', false); + btn.prop('disabled', false).text('{{ button_hutko_refund }}'); }, success: function (json) { if (json['error']) { @@ -61,6 +63,7 @@ $('#button-hutko-refund').on('click', function () { } if (json['success']) { $('#hutko-refund-response').html('
' + json['success'] + '
'); + // Reload history if possible, or reload page setTimeout(function(){ location.reload(); }, 2000); } }, @@ -73,16 +76,16 @@ $('#button-hutko-refund').on('click', function () { $('#button-hutko-status').on('click', function () { var btn = $(this); $.ajax({ - url: '{{ hutko_status_action_url|raw }}', + url: '{{ status_url|raw }}', type: 'post', dataType: 'json', data: {'hutko_transaction_ref': '{{ hutko_transaction_ref_display }}'}, beforeSend: function () { - btn.prop('disabled', true); + btn.prop('disabled', true).text('{{ text_loading }}'); $('#hutko-status-response').html(''); }, complete: function () { - btn.prop('disabled', false); + btn.prop('disabled', false).text('{{ button_hutko_status_check }}'); }, success: function (json) { if (json['error']) { @@ -90,7 +93,7 @@ $('#button-hutko-status').on('click', function () { } if (json['success']) { let data = json['data'] ? JSON.stringify(json['data'], null, 2) : ''; - $('#hutko-status-response').html('
' + json['success'] + '
' + data + '
'); + $('#hutko-status-response').html('
' + json['success'] + '
' + data + '
'); } }, error: function (xhr, ajaxOptions, thrownError) { diff --git a/catalog/controller/payment/hutko.php b/catalog/controller/payment/hutko.php index 7c04cb1..a3f0941 100644 --- a/catalog/controller/payment/hutko.php +++ b/catalog/controller/payment/hutko.php @@ -10,6 +10,7 @@ class Hutko extends \Opencart\System\Engine\Controller { } public function confirm(): void { + // Load language here so 'text_redirecting_comment' is available $this->load->language('extension/hutko/payment/hutko'); $this->load->model('checkout/order'); @@ -24,24 +25,27 @@ class Hutko extends \Opencart\System\Engine\Controller { if (!$order_info) { $json['error'] = 'Order missing'; } else { - // Build API Payload $request_data = $this->buildRequest($order_info); - // Save Ref - $this->load->model('extension/hutko/payment/hutko'); - $this->model_extension_hutko_payment_hutko->addHutkoOrder($order_info['order_id'], $request_data['order_id']); - - // API Call - $response = $this->api($this->checkout_url, $request_data); - - if (($response['response']['response_status'] ?? '') === 'success' && !empty($response['response']['checkout_url'])) { - // Set to Pending/Initiated - $this->model_checkout_order->addHistory($order_info['order_id'], $this->config->get('payment_hutko_new_order_status_id'), 'Redirecting to Hutko', false); - - // Return Redirect URL to frontend JS - $json['redirect'] = $response['response']['checkout_url']; + if (!$request_data) { + $json['error'] = $this->language->get('error_payment_data_build'); } else { - $json['error'] = $response['response']['error_message'] ?? $this->language->get('error_api_communication'); + $this->load->model('extension/hutko/payment/hutko'); + $this->model_extension_hutko_payment_hutko->addHutkoOrder($order_info['order_id'], $request_data['order_id']); + + $response = $this->api($this->checkout_url, $request_data); + + if (($response['response']['response_status'] ?? '') === 'success' && !empty($response['response']['checkout_url'])) { + // Language keys are loaded now, so this will contain actual text + $comment = sprintf($this->language->get('text_redirecting_comment'), $request_data['order_id'], $response['response']['checkout_url']); + $this->model_checkout_order->addHistory($order_info['order_id'], $this->config->get('payment_hutko_new_order_status_id'), $comment, false); + + $json['redirect'] = $response['response']['checkout_url']; + } else { + $err = $response['response']['error_message'] ?? $this->language->get('error_api_communication'); + $json['error'] = $err; + $this->logOC('Checkout Error: ' . $err); + } } } } @@ -51,9 +55,16 @@ class Hutko extends \Opencart\System\Engine\Controller { } public function callback(): void { + // IMPORTANT: Load language for status translations (e.g. text_payment_approved) + $this->load->language('extension/hutko/payment/hutko'); + $input = file_get_contents("php://input"); $data = json_decode($input, true); + if ($this->config->get('payment_hutko_save_logs')) { + $this->logOC('Callback: ' . $input); + } + if (!$data || !$this->validate($data)) { http_response_code(400); exit('Invalid Request'); @@ -67,39 +78,139 @@ class Hutko extends \Opencart\System\Engine\Controller { if ($order_info) { $status = $data['order_status'] ?? ''; + $comment_details = "Hutko Order ID: " . $data['order_id'] . ". Status: " . $status . ". "; + + $current_status_id = $order_info['order_status_id']; - // Map statuses if ($status === 'approved') { - $this->model_checkout_order->addHistory($order_id, $this->config->get('payment_hutko_success_status_id'), 'Hutko Confirmed', true); - echo "OK"; + if (isset($data['response_status']) && $data['response_status'] == 'success' && (!isset($data['reversal_amount']) || (int)$data['reversal_amount'] === 0)) { + $target = (int)$this->config->get('payment_hutko_success_status_id'); + if ($current_status_id != $target) { + $msg = $this->language->get('text_payment_approved') . ' ' . $comment_details; + $this->model_checkout_order->addHistory($order_id, $target, $msg, true); + } + echo "OK"; + } else { + echo "Approved but invalid details"; + } } elseif ($status === 'declined') { - $this->model_checkout_order->addHistory($order_id, $this->config->get('payment_hutko_declined_status_id'), 'Declined', true); - echo "Declined"; + $target = (int)$this->config->get('payment_hutko_declined_status_id'); + if ($current_status_id != $target) { + $msg = $this->language->get('text_payment_declined') . ' ' . $comment_details; + $this->model_checkout_order->addHistory($order_id, $target, $msg, true); + } + echo "Order declined"; + } elseif ($status === 'expired') { + $target = (int)$this->config->get('payment_hutko_expired_status_id'); + if ($current_status_id != $target) { + $msg = $this->language->get('text_payment_expired') . ' ' . $comment_details; + $this->model_checkout_order->addHistory($order_id, $target, $msg, true); + } + echo "Order expired"; } else { - echo "Status update received"; + echo "Status received"; } + } else { + http_response_code(404); + echo "Order not found"; } } private function buildRequest($order) { $ref = $order['order_id'] . '#' . time(); - $total = (int)round($order['total'] * 100); // Send in cents + $products_data = $this->getProducts($order['order_id'], $order); + + $total_products_sum = 0; + foreach ($products_data as $p) { + $total_products_sum += $p['total_amount']; + } + + $totals_query = $this->db->query("SELECT * FROM " . DB_PREFIX . "order_total WHERE order_id = '" . (int)$order['order_id'] . "' ORDER BY sort_order ASC"); + $shipping_cost = 0; + foreach ($totals_query->rows as $t) { + if ($t['code'] == 'shipping') { + $shipping_cost += $this->currency->format($t['value'], $order['currency_code'], $order['currency_value'], false); + } + } + + $order_total_val = $this->currency->format($order['total'], $order['currency_code'], $order['currency_value'], false); + + if ($this->config->get('payment_hutko_include_discount_to_total')) { + $amount_val = $order_total_val; + if (!$this->config->get('payment_hutko_shipping_include')) { + $amount_val -= $shipping_cost; + } + } else { + $amount_val = $total_products_sum; + } + + if ($amount_val < 0.01) $amount_val = 0.01; + $total_cents = (int)round($amount_val * 100); + + $reservation_data = [ + "cms_name" => "OpenCart", + "cms_version" => VERSION, + "shop_domain" => preg_replace("(^https?://)", "", HTTP_SERVER), + "phonemobile" => $order['telephone'], + "customer_address" => $order['payment_address_1'] . ' ' . $order['payment_address_2'], + "customer_country" => $order['shipping_iso_code_2'], + "customer_name" => $order['firstname'] . ' ' . $order['lastname'], + "customer_email" => $order['email'], + "products" => $products_data + ]; + $data = [ - 'order_id' => $ref, - 'merchant_id' => $this->config->get('payment_hutko_merchant_id'), - 'amount' => $total, - 'currency' => $order['currency_code'], - 'order_desc' => 'Order #' . $order['order_id'], - 'response_url' => $this->url->link('checkout/success', 'language=' . $this->config->get('config_language'), true), + 'order_id' => $ref, + 'merchant_id' => $this->config->get('payment_hutko_merchant_id'), + 'amount' => $total_cents, + 'currency' => $order['currency_code'], + 'order_desc' => 'Order #' . $order['order_id'], + 'response_url' => $this->url->link('checkout/success', 'language=' . $this->config->get('config_language'), true), 'server_callback_url' => $this->url->link('extension/hutko/payment/hutko.callback', '', true), - 'reservation_data' => base64_encode(json_encode(['products' => []])) // simplified for brevity + 'sender_email' => $order['email'], + 'reservation_data' => base64_encode(json_encode($reservation_data)) ]; $data['signature'] = $this->sign($data); return $data; } + private function getProducts(int $order_id, array $order_info): array { + $products_data = []; + $query = $this->db->query("SELECT * FROM `" . DB_PREFIX . "order_product` WHERE `order_id` = '" . (int)$order_id . "'"); + + foreach ($query->rows as $product) { + $unit_price = $this->currency->format($product['price'] + $product['tax'], $order_info['currency_code'], $order_info['currency_value'], false); + $total_price = $this->currency->format($product['total'] + ($product['tax'] * $product['quantity']), $order_info['currency_code'], $order_info['currency_value'], false); + + $products_data[] = [ + "id" => $product['product_id'], + "name" => $product['name'] . ' ' . $product['model'], + "price" => round((float)$unit_price, 2), + "total_amount" => round((float)$total_price, 2), + "quantity" => (int)$product['quantity'], + ]; + } + + if ($this->config->get('payment_hutko_shipping_include')) { + $totals = $this->db->query("SELECT * FROM " . DB_PREFIX . "order_total WHERE order_id = '" . (int)$order_id . "' AND code = 'shipping'"); + if ($totals->num_rows) { + $shipping_val = $this->currency->format($totals->row['value'], $order_info['currency_code'], $order_info['currency_value'], false); + if ($shipping_val > 0) { + $products_data[] = [ + "id" => $this->config->get('payment_hutko_shipping_product_code') ?: 'SHIPPING', + "name" => $this->config->get('payment_hutko_shipping_product_name') ?: 'Shipping', + "price" => round((float)$shipping_val, 2), + "total_amount" => round((float)$shipping_val, 2), + "quantity" => 1, + ]; + } + } + } + return $products_data; + } + private function sign($data) { $key = $this->config->get('payment_hutko_secret_key'); $arr = array_filter($data, function($v){ return $v !== '' && $v !== null; }); @@ -116,13 +227,29 @@ class Hutko extends \Opencart\System\Engine\Controller { } private function api($url, $data) { + if ($this->config->get('payment_hutko_save_logs')) $this->logOC('Req: ' . json_encode($data)); + $ch = curl_init($url); curl_setopt($ch, CURLOPT_POST, 1); curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode(['request' => $data])); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']); + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true); + curl_setopt($ch, CURLOPT_TIMEOUT, 30); + $res = curl_exec($ch); + $error = curl_error($ch); curl_close($ch); + + if ($this->config->get('payment_hutko_save_logs')) { + $this->logOC('Res: ' . $res); + if ($error) $this->logOC('Curl Error: ' . $error); + } + return json_decode($res, true) ?: []; } + + private function logOC($msg) { + $this->log->write("Hutko Payment: " . $msg); + } } \ No newline at end of file