first commit
This commit is contained in:
302
admin/controller/payment/hutko.php
Normal file
302
admin/controller/payment/hutko.php
Normal file
@@ -0,0 +1,302 @@
|
|||||||
|
<?php
|
||||||
|
namespace Opencart\Admin\Controller\Extension\Hutko\Payment;
|
||||||
|
|
||||||
|
class Hutko extends \Opencart\System\Engine\Controller {
|
||||||
|
private $refund_url = 'https://pay.hutko.org/api/reverse/order_id';
|
||||||
|
private $status_url = 'https://pay.hutko.org/api/status/order_id';
|
||||||
|
|
||||||
|
public function index(): void {
|
||||||
|
$this->load->language('extension/hutko/payment/hutko');
|
||||||
|
|
||||||
|
$this->document->setTitle($this->language->get('heading_title'));
|
||||||
|
|
||||||
|
$data['breadcrumbs'] = [];
|
||||||
|
$data['breadcrumbs'][] = [
|
||||||
|
'text' => $this->language->get('text_home'),
|
||||||
|
'href' => $this->url->link('common/dashboard', 'user_token=' . $this->session->data['user_token'])
|
||||||
|
];
|
||||||
|
$data['breadcrumbs'][] = [
|
||||||
|
'text' => $this->language->get('text_extension'),
|
||||||
|
'href' => $this->url->link('marketplace/extension', 'user_token=' . $this->session->data['user_token'] . '&type=payment')
|
||||||
|
];
|
||||||
|
$data['breadcrumbs'][] = [
|
||||||
|
'text' => $this->language->get('heading_title'),
|
||||||
|
'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
|
||||||
|
$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'
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach ($fields as $field) {
|
||||||
|
$data[$field] = $this->config->get($field);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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_total'])) $data['payment_hutko_total'] = '0.01';
|
||||||
|
|
||||||
|
$this->load->model('localisation/order_status');
|
||||||
|
$data['order_statuses'] = $this->model_localisation_order_status->getOrderStatuses();
|
||||||
|
|
||||||
|
$this->load->model('localisation/geo_zone');
|
||||||
|
$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');
|
||||||
|
|
||||||
|
$this->response->setOutput($this->load->view('extension/hutko/payment/hutko', $data));
|
||||||
|
}
|
||||||
|
|
||||||
|
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');
|
||||||
|
}
|
||||||
|
|
||||||
|
$key = $this->request->post['payment_hutko_secret_key'] ?? '';
|
||||||
|
if (empty($key) || ($key != 'test' && (strlen($key) < 10 || is_numeric($key)))) {
|
||||||
|
$json['error']['payment_hutko_secret_key'] = $this->language->get('error_secret_key_invalid');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$json) {
|
||||||
|
$this->load->model('setting/setting');
|
||||||
|
$this->model_setting_setting->editSetting('payment_hutko', $this->request->post);
|
||||||
|
$json['success'] = $this->language->get('text_success');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->response->addHeader('Content-Type: application/json');
|
||||||
|
$this->response->setOutput(json_encode($json));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function install(): void {
|
||||||
|
$this->load->model('extension/hutko/payment/hutko');
|
||||||
|
$this->model_extension_hutko_payment_hutko->install();
|
||||||
|
|
||||||
|
// OC4 Event Registration
|
||||||
|
$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';
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
public function order_info(string &$route, array &$args, string &$output): void {
|
||||||
|
if (!isset($args['order_id'])) return;
|
||||||
|
|
||||||
|
$this->load->model('sale/order');
|
||||||
|
$order_info = $this->model_sale_order->getOrder((int)$args['order_id']);
|
||||||
|
|
||||||
|
// 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']);
|
||||||
|
|
||||||
|
$data['hutko_transaction_ref'] = $hutko_order['hutko_transaction_ref'] ?? '';
|
||||||
|
$data['order_id'] = (int)$args['order_id'];
|
||||||
|
$data['user_token'] = $this->session->data['user_token'];
|
||||||
|
|
||||||
|
// URLs for AJAX actions
|
||||||
|
$data['refund_url'] = $this->url->link('extension/hutko/payment/hutko.refund', 'user_token=' . $this->session->data['user_token']);
|
||||||
|
$data['status_url'] = $this->url->link('extension/hutko/payment/hutko.status', 'user_token=' . $this->session->data['user_token']);
|
||||||
|
|
||||||
|
// Language Data
|
||||||
|
$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['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');
|
||||||
|
|
||||||
|
$content = $this->load->view('extension/hutko/payment/hutko_order', $data);
|
||||||
|
|
||||||
|
// Inject content before the History tab/card
|
||||||
|
$pos = strpos($output, '<div id="history"');
|
||||||
|
if ($pos !== false) {
|
||||||
|
$output = substr_replace($output, $content, $pos, 0);
|
||||||
|
} else {
|
||||||
|
$output .= $content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function refund(): void {
|
||||||
|
$this->load->language('extension/hutko/payment/hutko');
|
||||||
|
$this->load->model('extension/hutko/payment/hutko');
|
||||||
|
$this->load->model('sale/order');
|
||||||
|
|
||||||
|
$json = [];
|
||||||
|
$order_id = (int)($this->request->post['order_id'] ?? 0);
|
||||||
|
$amount = (float)($this->request->post['refund_amount'] ?? 0);
|
||||||
|
$comment = (string)($this->request->post['refund_comment'] ?? '');
|
||||||
|
|
||||||
|
$hutko_order = $this->model_extension_hutko_payment_hutko->getHutkoOrder($order_id);
|
||||||
|
$order_info = $this->model_sale_order->getOrder($order_id);
|
||||||
|
|
||||||
|
if ($hutko_order && $order_info && $amount > 0) {
|
||||||
|
$data = [
|
||||||
|
'order_id' => $hutko_order['hutko_transaction_ref'],
|
||||||
|
'merchant_id' => $this->config->get('payment_hutko_merchant_id'),
|
||||||
|
'version' => '1.0',
|
||||||
|
'amount' => round($amount * 100),
|
||||||
|
'currency' => $order_info['currency_code'],
|
||||||
|
'comment' => $comment
|
||||||
|
];
|
||||||
|
$data['signature'] = $this->sign($data);
|
||||||
|
|
||||||
|
$response = $this->api($this->refund_url, $data);
|
||||||
|
|
||||||
|
if (($response['response']['reverse_status'] ?? '') === 'approved') {
|
||||||
|
$json['success'] = $this->language->get('text_refund_success');
|
||||||
|
$msg = sprintf($this->language->get('text_refund_success_comment'), $hutko_order['hutko_transaction_ref'], $amount, $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';
|
||||||
|
$this->logOC("Refund Failed: " . json_encode($response));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$json['error'] = $this->language->get('error_invalid_request');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->response->addHeader('Content-Type: application/json');
|
||||||
|
$this->response->setOutput(json_encode($json));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function status(): void {
|
||||||
|
$this->load->language('extension/hutko/payment/hutko');
|
||||||
|
$json = [];
|
||||||
|
|
||||||
|
$ref = $this->request->post['hutko_transaction_ref'] ?? '';
|
||||||
|
if ($ref) {
|
||||||
|
$data = [
|
||||||
|
'order_id' => $ref,
|
||||||
|
'merchant_id' => $this->config->get('payment_hutko_merchant_id'),
|
||||||
|
'version' => '1.0',
|
||||||
|
];
|
||||||
|
$data['signature'] = $this->sign($data);
|
||||||
|
$response = $this->api($this->status_url, $data);
|
||||||
|
|
||||||
|
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';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$json['error'] = $this->language->get('error_missing_params');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->response->addHeader('Content-Type: application/json');
|
||||||
|
$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; });
|
||||||
|
ksort($filtered);
|
||||||
|
$str = $key;
|
||||||
|
foreach ($filtered as $v) $str .= '|' . $v;
|
||||||
|
return sha1($str);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function api(string $url, array $data): array {
|
||||||
|
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']);
|
||||||
|
$res = curl_exec($ch);
|
||||||
|
// curl_close($ch);
|
||||||
|
|
||||||
|
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';
|
||||||
|
$file = DIR_LOGS . 'error.log';
|
||||||
|
if (!file_exists($file)) return 'Log empty';
|
||||||
|
|
||||||
|
$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');
|
||||||
|
}
|
||||||
|
return implode('<br>', $output);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function logOC($message) {
|
||||||
|
$this->log->write('Hutko Payment: ' . $message);
|
||||||
|
}
|
||||||
|
}
|
||||||
114
admin/language/en-gb/payment/hutko.php
Normal file
114
admin/language/en-gb/payment/hutko.php
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
<?php
|
||||||
|
// Hutko translation file
|
||||||
|
// Heading
|
||||||
|
$_['heading_title'] = 'Hutko Payments';
|
||||||
|
|
||||||
|
// Text
|
||||||
|
$_['text_extension'] = 'Extensions';
|
||||||
|
$_['text_success'] = 'Success: You have modified Hutko payment module settings!';
|
||||||
|
$_['text_edit'] = 'Edit Hutko Payments';
|
||||||
|
$_['text_hutko'] = '<a href="https://hutko.org/" target="_blank"><img src="view/image/payment/hutko.png" alt="Hutko" title="Hutko" style="border: 1px solid #EEEEEE; max-height:25px;" /></a>'; // You'll need a hutko.png in admin/view/image/payment/
|
||||||
|
$_['text_enabled'] = 'Enabled';
|
||||||
|
$_['text_disabled'] = 'Disabled';
|
||||||
|
$_['text_yes'] = 'Yes';
|
||||||
|
$_['text_no'] = 'No';
|
||||||
|
$_['text_info_merchant'] = 'Use "1700243" for test setup.';
|
||||||
|
$_['text_info_secret'] = 'Use "test" for test setup.';
|
||||||
|
$_['text_logs_disabled'] = 'Logging is currently disabled. Enable "Save Logs" to see logs.';
|
||||||
|
$_['text_no_logs_found'] = 'No Hutko specific log entries found in the main log file for today, or logging is disabled.';
|
||||||
|
$_['text_log_file_not_found'] = 'Log file (%s) not found.';
|
||||||
|
$_['text_refund_success_comment'] = 'Refund Successful for ID: %s. Amount: %s. Comment: %s';
|
||||||
|
$_['text_refund_failed_comment'] = 'Refund Attempt Failed for ID: %s. Reason: %s';
|
||||||
|
$_['text_refund_success'] = 'Refund processed successfully via Hutko.';
|
||||||
|
$_['text_refund_api_error'] = 'Hutko API Error: %s';
|
||||||
|
$_['text_status_success'] = 'Status retrieved successfully from Hutko.';
|
||||||
|
$_['text_status_api_error'] = 'Hutko API Error fetching status: %s';
|
||||||
|
$_['text_unknown_error'] = 'An unknown error occurred.';
|
||||||
|
|
||||||
|
|
||||||
|
// Entry
|
||||||
|
$_['entry_merchant_id'] = 'Merchant ID';
|
||||||
|
$_['entry_secret_key'] = 'Secret Key';
|
||||||
|
$_['entry_new_order_status'] = 'New Order Status';
|
||||||
|
$_['entry_success_status'] = 'Successful Payment Status';
|
||||||
|
$_['entry_declined_status'] = 'Declined Payment Status';
|
||||||
|
$_['entry_expired_status'] = 'Expired Payment Status';
|
||||||
|
$_['entry_refunded_status'] = 'Refunded Payment Status';
|
||||||
|
$_['entry_shipping_include'] = 'Include Shipping Cost';
|
||||||
|
$_['entry_shipping_product_name'] = 'Shipping Fiscal Name';
|
||||||
|
$_['entry_shipping_product_code'] = 'Shipping Fiscal Code';
|
||||||
|
$_['entry_show_cards_logo'] = 'Show Visa/MasterCard Logo';
|
||||||
|
$_['entry_save_logs'] = 'Save Logs';
|
||||||
|
$_['entry_include_discount_to_total'] = 'Include Discounts in Total (for API)';
|
||||||
|
$_['entry_total'] = 'Minimum Order Total';
|
||||||
|
$_['entry_geo_zone'] = 'Geo Zone';
|
||||||
|
$_['entry_status'] = 'Status';
|
||||||
|
$_['entry_sort_order'] = 'Sort Order';
|
||||||
|
|
||||||
|
// Help
|
||||||
|
$_['help_total'] = 'The checkout total the order must reach before this payment method becomes active.';
|
||||||
|
$_['help_new_order_status'] = 'Status for new orders before payment redirection.';
|
||||||
|
$_['help_success_status'] = 'Status for successfully paid orders.';
|
||||||
|
$_['help_shipping_include'] = 'Include shipping cost as a separate item in the payment request details.';
|
||||||
|
$_['help_shipping_product_name'] = 'Name of product/service to use in fiscalization for shipping amount.';
|
||||||
|
$_['help_shipping_product_code'] = 'Code of product/service to use in fiscalization for shipping amount.';
|
||||||
|
$_['help_show_cards_logo'] = 'Display Visa/MasterCard logos next to the payment method name on checkout.';
|
||||||
|
$_['help_save_logs'] = 'Log API communication and callbacks to the system log file.';
|
||||||
|
$_['help_include_discount_to_total'] = 'If Yes, order discounts will be subtracted from the payment total, this may prevent fiscalization.';
|
||||||
|
|
||||||
|
|
||||||
|
// Error
|
||||||
|
$_['error_permission'] = 'Warning: You do not have permission to modify Hutko payment module!';
|
||||||
|
$_['error_merchant_id_required'] = 'Merchant ID is required!';
|
||||||
|
$_['error_merchant_id_numeric'] = 'Merchant ID must be numeric!';
|
||||||
|
$_['error_secret_key_required'] = 'Secret Key is required!';
|
||||||
|
$_['error_secret_key_invalid'] = 'Secret key must be "test" or at least 10 characters long and not entirely numeric.';
|
||||||
|
$_['error_invalid_request'] = 'Invalid request data for refund/status.';
|
||||||
|
$_['error_missing_params'] = 'Missing required parameters for refund/status.';
|
||||||
|
|
||||||
|
|
||||||
|
// Tab
|
||||||
|
$_['tab_general'] = 'General';
|
||||||
|
$_['tab_order_statuses'] = 'Order Statuses';
|
||||||
|
$_['tab_fiscalization'] = 'Fiscalization';
|
||||||
|
$_['tab_advanced'] = 'Advanced';
|
||||||
|
$_['tab_logs'] = 'Logs';
|
||||||
|
$_['text_payment_information'] = 'Payments History';
|
||||||
|
|
||||||
|
|
||||||
|
$_['text_not_available'] = 'N/A';
|
||||||
|
$_['text_hutko_transaction_ref_label'] = 'Hutko Transaction ID';
|
||||||
|
$_['text_hutko_refund_title'] = 'Hutko Refund';
|
||||||
|
$_['text_hutko_status_title'] = 'Hutko Status Check';
|
||||||
|
$_['button_hutko_refund'] = 'Process Hutko Refund';
|
||||||
|
$_['button_hutko_status_check'] = 'Check Hutko Payment Status';
|
||||||
|
$_['entry_refund_amount'] = 'Refund Amount';
|
||||||
|
$_['entry_refund_comment'] = 'Refund Comment (optional)';
|
||||||
|
|
||||||
|
$_['text_refund_success_comment'] = 'Refund for Hutko ID %s successful. Amount: %s. Comment: %s';
|
||||||
|
$_['text_refund_failed_comment'] = 'Refund attempt for Hutko ID %s failed. Gateway error: %s';
|
||||||
|
$_['text_refund_api_error'] = 'Hutko Refund API Error: %s';
|
||||||
|
$_['text_status_api_error'] = 'Hutko Status API Error: %s';
|
||||||
|
$_['text_unknown_error'] = 'An unknown error occurred with the API.';
|
||||||
|
|
||||||
|
|
||||||
|
$_['error_missing_order_id'] = 'Error: Order ID is missing from the request.';
|
||||||
|
$_['error_hutko_transaction_ref_not_found_db'] = 'Error: Hutko Transaction ID not found in database for this order.';
|
||||||
|
$_['error_hutko_transaction_ref_missing'] = 'Error: Hutko Transaction ID is required for this operation.';
|
||||||
|
$_['error_invalid_refund_amount'] = 'Error: Invalid refund amount. Must be greater than 0.';
|
||||||
|
$_['error_missing_refund_amount'] = 'Error: Refund amount is required.';
|
||||||
|
|
||||||
|
// For catalog side (checkout process)
|
||||||
|
$_['error_payment_data_build'] = 'Error: Could not prepare payment data. Please try again or contact support.';
|
||||||
|
$_['error_api_communication'] = 'Error: Could not communicate with the payment gateway. Please try again.';
|
||||||
|
$_['text_redirecting_comment'] = 'Redirecting to Hutko. Hutko Order ID: %s. URL: %s';
|
||||||
|
|
||||||
|
// For callback
|
||||||
|
$_['text_payment_approved'] = 'Payment Approved by Hutko.';
|
||||||
|
$_['text_payment_declined'] = 'Payment Declined by Hutko.';
|
||||||
|
$_['text_payment_expired'] = 'Payment Expired at Hutko.';
|
||||||
|
$_['text_payment_processing'] = 'Payment is Processing at Hutko.';
|
||||||
|
$_['text_confirm_refund'] = 'Are you sure you want to refund this transaction via Hutko? This action cannot be undone.';
|
||||||
|
|
||||||
|
$_['text_loading'] = 'Loading...';
|
||||||
|
$_['error_order_not_found'] = 'Error: Order not found.';
|
||||||
104
admin/language/ru-ru/payment/hutko.php
Normal file
104
admin/language/ru-ru/payment/hutko.php
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
<?php
|
||||||
|
// Hutko translation file
|
||||||
|
// Heading
|
||||||
|
$_['heading_title'] = 'Платежи Hutko';
|
||||||
|
|
||||||
|
|
||||||
|
$_['text_extension'] = 'Расширения';
|
||||||
|
$_['text_success'] = 'Успех: Вы изменили настройки модуля оплаты Hutko!';
|
||||||
|
$_['text_edit'] = 'Изменить настройки Hutko';
|
||||||
|
|
||||||
|
$_['text_hutko'] = '<a href="https://hutko.org/" target="_blank"><img src="view/image/payment/hutko.png" alt="Hutko" title="Hutko" style="border: 1px solid #EEEEEE; max-height:25px;" /></a>'; // Вам понадобится hutko.png в admin/view/image/payment/
|
||||||
|
$_['text_enabled'] = 'Включено';
|
||||||
|
$_['text_disabled'] = 'Отключено';
|
||||||
|
$_['text_yes'] = 'Да';
|
||||||
|
$_['text_no'] = 'Нет';
|
||||||
|
$_['text_info_merchant'] = 'Используйте "1700243" для теста.';
|
||||||
|
$_['text_info_secret'] = 'Используйте "test" для теста.';
|
||||||
|
$_['text_logs_disabled'] = 'Ведение журнала в настоящее время отключено. Включите "Сохранить журналы", чтобы увидеть журналы.';
|
||||||
|
$_['text_no_logs_found'] = 'В главном файле журнала на сегодня не найдено никаких записей журнала Hutko, или ведение журнала отключено.';
|
||||||
|
$_['text_log_file_not_found'] = 'Файл журнала (%s) не найден.';
|
||||||
|
$_['text_refund_success_comment'] = 'Возмещение выполнен успешно для ID: %s. Сумма: %s. Комментарий: %s';
|
||||||
|
$_['text_refund_failed_comment'] = 'Попытка возмещения не удалась для ID: %s. Причина: %s';
|
||||||
|
$_['text_refund_success'] = 'Возмещение успешно обработано через Hutko.';
|
||||||
|
$_['text_refund_api_error'] = 'Ошибка API Hutko: %s';
|
||||||
|
$_['text_status_success'] = 'Статус успешно получен от Hutko.';
|
||||||
|
$_['text_status_api_error'] = 'Ошибка API Hutko при получении статуса: %s';
|
||||||
|
$_['text_unknown_error'] = 'Произошла неизвестная ошибка.';
|
||||||
|
|
||||||
|
$_['entry_merchant_id'] = 'ID продавца';
|
||||||
|
$_['entry_secret_key'] = 'Секретный ключ';
|
||||||
|
$_['entry_new_order_status'] = 'Статус нового заказа';
|
||||||
|
$_['entry_success_status'] = 'Статус при успешном платеже';
|
||||||
|
$_['entry_declined_status'] = 'Статус при отклоненном платеже';
|
||||||
|
$_['entry_expired_status'] = 'Статус при просроченном платеже';
|
||||||
|
$_['entry_refunded_status'] = 'Статус при возмещении платеже';
|
||||||
|
$_['entry_shipping_include'] = 'Включить стоимость доставки';
|
||||||
|
$_['entry_shipping_product_name'] = 'Наименование доставки в фискальном чеке';
|
||||||
|
$_['entry_shipping_product_code'] = 'Код доставки в фискальном чеке';
|
||||||
|
$_['entry_show_cards_logo'] = 'Показать логотип Visa/MasterCard';
|
||||||
|
$_['entry_save_logs'] = 'Сохранять журналы';
|
||||||
|
$_['entry_include_discount_to_total'] = 'Включить скидки в общую сумму (для API)';
|
||||||
|
$_['entry_total'] = 'Минимальная сумма заказа';
|
||||||
|
$_['entry_geo_zone'] = 'Геозона';
|
||||||
|
$_['entry_status'] = 'Статус';
|
||||||
|
$_['entry_sort_order'] = 'Порядок сортировки';
|
||||||
|
|
||||||
|
|
||||||
|
$_['help_total'] = 'Сумма, которую должен достичь заказ, прежде чем этот способ оплаты станет активным.';
|
||||||
|
$_['help_new_order_status'] = 'Статус для новых заказов до получения платежа.';
|
||||||
|
$_['help_success_status'] = 'Статус для успешно оплаченных заказов.';
|
||||||
|
$_['help_shipping_include'] = 'Включить стоимость доставки в суму платежа.';
|
||||||
|
$_['help_shipping_product_name'] = 'Название продукта/услуги для использования при фискализации для суммы доставки.';
|
||||||
|
$_['help_shipping_product_code'] = 'Код продукта/услуги для использования при фискализации для суммы доставки.';
|
||||||
|
$_['help_show_cards_logo'] = 'Отображать логотипы Visa/MasterCard рядом с названием способа оплаты при оформлении заказа.';
|
||||||
|
$_['help_save_logs'] = 'Записывать коммуникацию API и обратные вызовы в системный файл журнала.';
|
||||||
|
$_['help_include_discount_to_total'] = 'Если да, скидки по заказу будут вычтены из общей суммы платежа, это может помешать фискализации.';
|
||||||
|
|
||||||
|
|
||||||
|
$_['error_permission'] = 'Внимание: у вас нет разрешения на изменение платежного модуля Hutko!';
|
||||||
|
$_['error_merchant_id_required'] = 'Требуется идентификатор продавца!';
|
||||||
|
$_['error_merchant_id_numeric'] = 'Идентификатор продавца должен быть числовым!';
|
||||||
|
$_['error_secret_key_required'] = 'Требуется секретный ключ!';
|
||||||
|
$_['error_secret_key_invalid'] = 'Секретный ключ должен быть "test" или содержать не менее 10 символов и не состоять полностью из цифр.';
|
||||||
|
$_['error_invalid_request'] = 'Недопустимые данные запроса на возмещения/статус.';
|
||||||
|
$_['error_missing_params'] = 'Отсутствуют обязательные параметры для возмещения/статуса.';
|
||||||
|
|
||||||
|
|
||||||
|
$_['tab_general'] = 'Общие';
|
||||||
|
$_['tab_order_statuses'] = 'Статусы заказов';
|
||||||
|
$_['tab_fiscalization'] = 'Фискализация';
|
||||||
|
$_['tab_advanced'] = 'Дополнительно';
|
||||||
|
$_['tab_logs'] = 'Журналы';
|
||||||
|
$_['text_payment_information'] = 'История платежей';
|
||||||
|
$_['text_not_available'] = 'Н/Д';
|
||||||
|
$_['text_hutko_transaction_ref_label'] = 'Идентификатор заказа в Hutko';
|
||||||
|
$_['text_hutko_refund_title'] = 'Возмещение Hutko';
|
||||||
|
$_['text_hutko_status_title'] = 'Проверка статуса Hutko';
|
||||||
|
$_['button_hutko_refund'] = 'Обработать возмещение через Hutko';
|
||||||
|
$_['button_hutko_status_check'] = 'Проверить статус платежа Hutko';
|
||||||
|
$_['entry_refund_amount'] = 'Сумма возмещения';
|
||||||
|
$_['entry_refund_comment'] = 'Комментарий к возмещению (необязательно)';
|
||||||
|
$_['text_refund_success_comment'] = 'Возмещение средств по ID %s успешно. Сумма: %s. Комментарий: %s';
|
||||||
|
$_['text_refund_failed_comment'] = 'Попытка возмещения средств по ID %s не удалась. Ошибка шлюза: %s';
|
||||||
|
$_['text_refund_api_error'] = 'Ошибка API возмещения Hutko: %s';
|
||||||
|
$_['text_status_api_error'] = 'Ошибка API статуса Hutko: %s';
|
||||||
|
$_['text_unknown_error'] = 'Произошла неизвестная ошибка API.';
|
||||||
|
$_['error_missing_order_id'] = 'Ошибка: в запросе отсутствует идентификатор заказа.';
|
||||||
|
$_['error_hutko_transaction_ref_not_found_db'] = 'Ошибка: идентификатор заказа Hutko не найден в базе данных для этого заказа.';
|
||||||
|
$_['error_hutko_transaction_ref_missing'] = 'Ошибка: идентификатор заказа Hutko требуется для этой операции.';
|
||||||
|
$_['error_invalid_refund_amount'] = 'Ошибка: недопустимая сумма возврата. Должна быть больше 0.';
|
||||||
|
$_['error_missing_refund_amount'] = 'Ошибка: требуется сумма возврата.';
|
||||||
|
|
||||||
|
$_['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'] = 'Ошибка: заказ не найден.';
|
||||||
217
admin/language/uk-ua/payment/hutko.php
Normal file
217
admin/language/uk-ua/payment/hutko.php
Normal file
@@ -0,0 +1,217 @@
|
|||||||
|
<?php
|
||||||
|
// Hutko translation file
|
||||||
|
// Heading
|
||||||
|
$_['heading_title'] = 'Платежи Hutko';
|
||||||
|
|
||||||
|
|
||||||
|
$_['text_extension'] = 'Расширения';
|
||||||
|
$_['text_success'] = 'Успех: Вы изменили настройки модуля оплаты Hutko!';
|
||||||
|
$_['text_edit'] = 'Изменить настройки Hutko';
|
||||||
|
|
||||||
|
$_['text_hutko'] = '<a href="https://hutko.org/" target="_blank"><img src="view/image/payment/hutko.png" alt="Hutko" title="Hutko" style="border: 1px solid #EEEEEE; max-height:25px;" /></a>'; // Вам понадобится hutko.png в admin/view/image/payment/
|
||||||
|
$_['text_enabled'] = 'Включено';
|
||||||
|
$_['text_disabled'] = 'Отключено';
|
||||||
|
$_['text_yes'] = 'Да';
|
||||||
|
$_['text_no'] = 'Нет';
|
||||||
|
$_['text_info_merchant'] = 'Используйте "1700243" для теста.';
|
||||||
|
$_['text_info_secret'] = 'Используйте "test" для теста.';
|
||||||
|
$_['text_logs_disabled'] = 'Ведение журнала в настоящее время отключено. Включите "Сохранить журналы", чтобы увидеть журналы.';
|
||||||
|
$_['text_no_logs_found'] = 'В главном файле журнала на сегодня не найдено никаких записей журнала Hutko, или ведение журнала отключено.';
|
||||||
|
$_['text_log_file_not_found'] = 'Файл журнала (%s) не найден.';
|
||||||
|
$_['text_refund_success_comment'] = 'Возмещение выполнен успешно для ID: %s. Сумма: %s. Комментарий: %s';
|
||||||
|
$_['text_refund_failed_comment'] = 'Попытка возмещения не удалась для ID: %s. Причина: %s';
|
||||||
|
$_['text_refund_success'] = 'Возмещение успешно обработано через Hutko.';
|
||||||
|
$_['text_refund_api_error'] = 'Ошибка API Hutko: %s';
|
||||||
|
$_['text_status_success'] = 'Статус успешно получен от Hutko.';
|
||||||
|
$_['text_status_api_error'] = 'Ошибка API Hutko при получении статуса: %s';
|
||||||
|
$_['text_unknown_error'] = 'Произошла неизвестная ошибка.';
|
||||||
|
|
||||||
|
$_['entry_merchant_id'] = 'ID продавца';
|
||||||
|
$_['entry_secret_key'] = 'Секретный ключ';
|
||||||
|
$_['entry_new_order_status'] = 'Статус нового заказа';
|
||||||
|
$_['entry_success_status'] = 'Статус при успешном платеже';
|
||||||
|
$_['entry_declined_status'] = 'Статус при отклоненном платеже';
|
||||||
|
$_['entry_expired_status'] = 'Статус при просроченном платеже';
|
||||||
|
$_['entry_refunded_status'] = 'Статус при возмещении платеже';
|
||||||
|
$_['entry_shipping_include'] = 'Включить стоимость доставки';
|
||||||
|
$_['entry_shipping_product_name'] = 'Наименование доставки в фискальном чеке';
|
||||||
|
$_['entry_shipping_product_code'] = 'Код доставки в фискальном чеке';
|
||||||
|
$_['entry_show_cards_logo'] = 'Показать логотип Visa/MasterCard';
|
||||||
|
$_['entry_save_logs'] = 'Сохранять журналы';
|
||||||
|
$_['entry_include_discount_to_total'] = 'Включить скидки в общую сумму (для API)';
|
||||||
|
$_['entry_total'] = 'Минимальная сумма заказа';
|
||||||
|
$_['entry_geo_zone'] = 'Геозона';
|
||||||
|
$_['entry_status'] = 'Статус';
|
||||||
|
$_['entry_sort_order'] = 'Порядок сортировки';
|
||||||
|
|
||||||
|
|
||||||
|
$_['help_total'] = 'Сумма, которую должен достичь заказ, прежде чем этот способ оплаты станет активным.';
|
||||||
|
$_['help_new_order_status'] = 'Статус для новых заказов до получения платежа.';
|
||||||
|
$_['help_success_status'] = 'Статус для успешно оплаченных заказов.';
|
||||||
|
$_['help_shipping_include'] = 'Включить стоимость доставки в суму платежа.';
|
||||||
|
$_['help_shipping_product_name'] = 'Название продукта/услуги для использования при фискализации для суммы доставки.';
|
||||||
|
$_['help_shipping_product_code'] = 'Код продукта/услуги для использования при фискализации для суммы доставки.';
|
||||||
|
$_['help_show_cards_logo'] = 'Отображать логотипы Visa/MasterCard рядом с названием способа оплаты при оформлении заказа.';
|
||||||
|
$_['help_save_logs'] = 'Записывать коммуникацию API и обратные вызовы в системный файл журнала.';
|
||||||
|
$_['help_include_discount_to_total'] = 'Если да, скидки по заказу будут вычтены из общей суммы платежа, это может помешать фискализации.';
|
||||||
|
|
||||||
|
|
||||||
|
$_['error_permission'] = 'Внимание: у вас нет разрешения на изменение платежного модуля Hutko!';
|
||||||
|
$_['error_merchant_id_required'] = 'Требуется идентификатор продавца!';
|
||||||
|
$_['error_merchant_id_numeric'] = 'Идентификатор продавца должен быть числовым!';
|
||||||
|
$_['error_secret_key_required'] = 'Требуется секретный ключ!';
|
||||||
|
$_['error_secret_key_invalid'] = 'Секретный ключ должен быть "test" или содержать не менее 10 символов и не состоять полностью из цифр.';
|
||||||
|
$_['error_invalid_request'] = 'Недопустимые данные запроса на возмещения/статус.';
|
||||||
|
$_['error_missing_params'] = 'Отсутствуют обязательные параметры для возмещения/статуса.';
|
||||||
|
|
||||||
|
|
||||||
|
$_['tab_general'] = 'Общие';
|
||||||
|
$_['tab_order_statuses'] = 'Статусы заказов';
|
||||||
|
$_['tab_fiscalization'] = 'Фискализация';
|
||||||
|
$_['tab_advanced'] = 'Дополнительно';
|
||||||
|
$_['tab_logs'] = 'Журналы';
|
||||||
|
$_['text_payment_information'] = 'История платежей';
|
||||||
|
$_['text_not_available'] = 'Н/Д';
|
||||||
|
$_['text_hutko_transaction_ref_label'] = 'Идентификатор заказа в Hutko';
|
||||||
|
$_['text_hutko_refund_title'] = 'Возмещение Hutko';
|
||||||
|
$_['text_hutko_status_title'] = 'Проверка статуса Hutko';
|
||||||
|
$_['button_hutko_refund'] = 'Обработать возмещение через Hutko';
|
||||||
|
$_['button_hutko_status_check'] = 'Проверить статус платежа Hutko';
|
||||||
|
$_['entry_refund_amount'] = 'Сумма возмещения';
|
||||||
|
$_['entry_refund_comment'] = 'Комментарий к возмещению (необязательно)';
|
||||||
|
$_['text_refund_success_comment'] = 'Возмещение средств по ID %s успешно. Сумма: %s. Комментарий: %s';
|
||||||
|
$_['text_refund_failed_comment'] = 'Попытка возмещения средств по ID %s не удалась. Ошибка шлюза: %s';
|
||||||
|
$_['text_refund_api_error'] = 'Ошибка API возмещения Hutko: %s';
|
||||||
|
$_['text_status_api_error'] = 'Ошибка API статуса Hutko: %s';
|
||||||
|
$_['text_unknown_error'] = 'Произошла неизвестная ошибка API.';
|
||||||
|
$_['error_missing_order_id'] = 'Ошибка: в запросе отсутствует идентификатор заказа.';
|
||||||
|
$_['error_hutko_transaction_ref_not_found_db'] = 'Ошибка: идентификатор заказа Hutko не найден в базе данных для этого заказа.';
|
||||||
|
$_['error_hutko_transaction_ref_missing'] = 'Ошибка: идентификатор заказа Hutko требуется для этой операции.';
|
||||||
|
$_['error_invalid_refund_amount'] = 'Ошибка: недопустимая сумма возврата. Должна быть больше 0.';
|
||||||
|
$_['error_missing_refund_amount'] = 'Ошибка: требуется сумма возврата.';
|
||||||
|
|
||||||
|
$_['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'] = 'Ошибка: заказ не найден.';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
$_['heading_title'] = 'Платежі Hutko';
|
||||||
|
|
||||||
|
|
||||||
|
$_['text_extension'] = 'Розширення';
|
||||||
|
$_['text_success'] = 'Успішно: Ви змінили налаштування модуля оплати Hutko!';
|
||||||
|
$_['text_edit'] = 'Змінити налаштуваннь Hutko';
|
||||||
|
|
||||||
|
$_['text_hutko'] = '<a href="https://hutko.org/" target="_blank"><img src="view/image/payment/hutko.png" alt="Hutko" title="Hutko" style="border: 1px solid #EEEEEE; max-he; /></a>';
|
||||||
|
$_['text_enabled'] = 'Увімкнено';
|
||||||
|
$_['text_disabled'] = 'Вимкнено';
|
||||||
|
$_['text_yes'] = 'Так';
|
||||||
|
$_['text_no'] = 'Ні';
|
||||||
|
$_['text_info_merchant'] = 'Використовуйте "1700243" для тестування.';
|
||||||
|
$_['text_info_secret'] = 'Використовуйте "test" для тестування.';
|
||||||
|
$_['text_logs_disabled'] = 'Ведення журналу в даний час вимкнено. Увімкніть "Зберегти журнали", щоб побачити журнали.';
|
||||||
|
$_['text_no_logs_found'] = 'У головному файлі журналу на сьогодні не знайдено жодних записів журналу Hutko, або ведення журналу вимкнено.';
|
||||||
|
$_['text_log_file_not_found'] = 'Файл журналу (%s) не знайдено.';
|
||||||
|
$_['text_refund_success_comment'] = 'Відшкодування успішно виконано для ID: %s. Сума: %s. Коментар: %s';
|
||||||
|
$_['text_refund_failed_comment'] = 'Спроба відшкодування не вдалася для ID: %s. Причина: %s';
|
||||||
|
$_['text_refund_success'] = 'Відшкодування успішно оброблено через Hutko.';
|
||||||
|
$_['text_refund_api_error'] = 'Помилка API Hutko: %s';
|
||||||
|
$_['text_status_success'] = 'Статус успішно отримано від Hutko.';
|
||||||
|
$_['text_status_api_error'] = 'Помилка API Hutko при отриманні статусу: %s';
|
||||||
|
$_['text_unknown_error'] = 'Відбулася невідома помилка.';
|
||||||
|
|
||||||
|
$_['entry_merchant_id'] = 'ID продавця';
|
||||||
|
$_['entry_secret_key'] = 'Секретний ключ';
|
||||||
|
$_['entry_new_order_status'] = 'Статус нового замовлення';
|
||||||
|
$_['entry_success_status'] = 'Статус за успішного платежу';
|
||||||
|
$_['entry_declined_status'] = 'Статус при відхиленому платежі';
|
||||||
|
$_['entry_expired_status'] = 'Статус при простроченому платежі';
|
||||||
|
$_['entry_refunded_status'] = 'Статус при відшкодуванні платежу';
|
||||||
|
$_['entry_shipping_include'] = 'Включати вартість доставки';
|
||||||
|
$_['entry_shipping_product_name'] = 'Найменування доставки у фіскальному чеку';
|
||||||
|
$_['entry_shipping_product_code'] = 'Код доставки у фіскальному чеку';
|
||||||
|
$_['entry_show_cards_logo'] = 'Показати логотип Visa/MasterCard';
|
||||||
|
$_['entry_save_logs'] = 'Зберігати журнали';
|
||||||
|
$_['entry_include_discount_to_total'] = 'Включити фіксовані знижки в загальну суму оплати';
|
||||||
|
$_['entry_total'] = 'Мінімальна сума замовлення';
|
||||||
|
$_['entry_geo_zone'] = 'Геозона';
|
||||||
|
$_['entry_status'] = 'Статус';
|
||||||
|
$_['entry_sort_order'] = 'Порядок сортування';
|
||||||
|
|
||||||
|
|
||||||
|
$_['help_total'] = 'Сума, яку має досягти замовлення, перш ніж цей спосіб оплати стане активним.';
|
||||||
|
$_['help_new_order_status'] = 'Статус для нових замовлень до отримання платежу.';
|
||||||
|
$_['help_success_status'] = 'Статус для успішно оплачених замовлень.';
|
||||||
|
$_['help_shipping_include'] = 'Включити вартість доставки до суми платежу.';
|
||||||
|
$_['help_shipping_product_name'] = 'Назва продукту/послуги для використання при фіскалізації для суми доставки.';
|
||||||
|
$_['help_shipping_product_code'] = 'Код продукту/послуги для використання при фіскалізації для суми доставки.';
|
||||||
|
$_['help_show_cards_logo'] = 'Відображати логотипи Visa/MasterCard поруч із назвою способу оплати при оформленні замовлення.';
|
||||||
|
$_['help_save_logs'] = 'Записувати комунікацію API та зворотні виклики до системного файлу журналу.';
|
||||||
|
$_['help_include_discount_to_total'] = 'Якщо так, знижки на замовлення будуть вираховані із загальної суми платежу, це може перешкодити фіскалізації.';
|
||||||
|
|
||||||
|
|
||||||
|
$_['error_permission'] = 'Увага: у вас немає дозволу на зміну платіжного модуля Hutko!';
|
||||||
|
$_['error_merchant_id_required'] = 'Потрібен ідентифікатор продавця!';
|
||||||
|
$_['error_merchant_id_numeric'] = 'Ідентифікатор продавця має бути числовим!';
|
||||||
|
$_['error_secret_key_required'] = 'Потрібен секретний ключ!';
|
||||||
|
$_['error_secret_key_invalid'] = 'Секретний ключ повинен бути "test" або містити не менше 10 символів і не складатися повністю із цифр.';
|
||||||
|
$_['error_invalid_request'] = 'Неприпустимі дані запиту на відшкодування/статус.';
|
||||||
|
$_['error_missing_params'] = 'Відсутні обов\'язкові параметри для відшкодування/статусу.';
|
||||||
|
|
||||||
|
|
||||||
|
$_['tab_general'] = 'Загальні';
|
||||||
|
$_['tab_order_statuses'] = 'Статуси замовлень';
|
||||||
|
$_['tab_fiscalization'] = 'Фіскалізація';
|
||||||
|
$_['tab_advanced'] = 'Додатково';
|
||||||
|
$_['tab_logs'] = 'Журнали';
|
||||||
|
$_['text_payment_information'] = 'Історія платежів';
|
||||||
|
$_['text_not_available'] = 'Н/Д';
|
||||||
|
$_['text_hutko_transaction_ref_label'] = 'Ідентифікатор замовлення в Hutko';
|
||||||
|
$_['text_hutko_refund_title'] = 'Відшкодування Hutko';
|
||||||
|
$_['text_hutko_status_title'] = 'Перевірка статусу Hutko';
|
||||||
|
$_['button_hutko_refund'] = 'Обробити відшкодування через Hutko';
|
||||||
|
$_['button_hutko_status_check'] = 'Перевірити статус платежу Hutko';
|
||||||
|
$_['entry_refund_amount'] = 'Сума відшкодування';
|
||||||
|
$_['entry_refund_comment'] = 'Коментар до відшкодування (необов\'язково)';
|
||||||
|
$_['text_refund_success_comment'] = 'Відшкодування коштів за ID %s успішно. Сума: %s. Коментар: %s';
|
||||||
|
$_['text_refund_failed_comment'] = 'Спроба відшкодування коштів за ID %s не вдалася. Помилка шлюзу: %s';
|
||||||
|
$_['text_refund_api_error'] = 'Помилка API відшкодування Hutko: %s';
|
||||||
|
$_['text_status_api_error'] = 'Помилка API статусу Hutko: %s';
|
||||||
|
$_['text_unknown_error'] = 'Відбулася невідома помилка API.';
|
||||||
|
$_['error_missing_order_id'] = 'Помилка: у запиті відсутній ідентифікатор замовлення.';
|
||||||
|
$_['error_hutko_transaction_ref_not_found_db'] = 'Помилка: ідентифікатор замовлення Hutko не знайдений у базі даних для цього замовлення.';
|
||||||
|
$_['error_hutko_transaction_ref_missing'] = 'Помилка: ідентифікатор замовлення Hutko потрібний для цієї операції.';
|
||||||
|
$_['error_invalid_refund_amount'] = 'Помилка: неприпустима сума повернення. Має бути більше 0.';
|
||||||
|
$_['error_missing_refund_amount'] = 'Помилка: потрібна сума повернення.';
|
||||||
|
|
||||||
|
$_['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'] = 'Помилка: замовлення не знайдено.';
|
||||||
26
admin/model/payment/hutko.php
Normal file
26
admin/model/payment/hutko.php
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
<?php
|
||||||
|
namespace Opencart\Admin\Model\Extension\Hutko\Payment;
|
||||||
|
|
||||||
|
class Hutko extends \Opencart\System\Engine\Model {
|
||||||
|
public function install(): void {
|
||||||
|
$this->db->query("
|
||||||
|
CREATE TABLE IF NOT EXISTS `" . DB_PREFIX . "hutko_order` (
|
||||||
|
`hutko_order_pk_id` INT(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`order_id` INT(11) NOT NULL,
|
||||||
|
`hutko_transaction_ref` VARCHAR(255) NOT NULL,
|
||||||
|
`date_added` DATETIME NOT NULL,
|
||||||
|
PRIMARY KEY (`hutko_order_pk_id`),
|
||||||
|
UNIQUE KEY `idx_order_id` (`order_id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||||
|
");
|
||||||
|
}
|
||||||
|
|
||||||
|
public function uninstall(): void {
|
||||||
|
// table drop optional
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getHutkoOrder(int $order_id): array {
|
||||||
|
$query = $this->db->query("SELECT * FROM `" . DB_PREFIX . "hutko_order` WHERE `order_id` = '" . (int)$order_id . "'");
|
||||||
|
return $query->row;
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
admin/view/image/payment/hutko.png
Normal file
BIN
admin/view/image/payment/hutko.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.4 KiB |
151
admin/view/template/payment/hutko.twig
Normal file
151
admin/view/template/payment/hutko.twig
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
{{ header }}{{ column_left }}
|
||||||
|
<div id="content">
|
||||||
|
<div class="page-header">
|
||||||
|
<div class="container-fluid">
|
||||||
|
<div class="float-end">
|
||||||
|
<button type="submit" form="form-payment" data-bs-toggle="tooltip" title="{{ button_save }}" class="btn btn-primary">
|
||||||
|
<i class="fa-solid fa-save"></i>
|
||||||
|
</button>
|
||||||
|
<a href="{{ back }}" data-bs-toggle="tooltip" title="{{ button_back }}" class="btn btn-light">
|
||||||
|
<i class="fa-solid fa-reply"></i>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<h1>{{ heading_title }}</h1>
|
||||||
|
<ol class="breadcrumb">
|
||||||
|
{% for breadcrumb in breadcrumbs %}
|
||||||
|
<li class="breadcrumb-item">
|
||||||
|
<a href="{{ breadcrumb.href }}">{{ breadcrumb.text }}</a>
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="container-fluid">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<i class="fa-solid fa-pencil"></i>
|
||||||
|
{{ text_edit }}</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<form id="form-payment" action="{{ save }}" method="post" data-oc-toggle="ajax">
|
||||||
|
<ul class="nav nav-tabs">
|
||||||
|
<li class="nav-item">
|
||||||
|
<a href="#tab-general" data-bs-toggle="tab" class="nav-link active">{{ tab_general }}</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a href="#tab-status" data-bs-toggle="tab" class="nav-link">{{ tab_order_statuses }}</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a href="#tab-advanced" data-bs-toggle="tab" class="nav-link">{{ tab_advanced }}</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a href="#tab-logs" data-bs-toggle="tab" class="nav-link">{{ tab_logs }}</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div class="tab-content">
|
||||||
|
<div id="tab-general" class="tab-pane active">
|
||||||
|
<div class="row mb-3 required">
|
||||||
|
<label for="input-merchant-id" class="col-sm-2 col-form-label">{{ entry_merchant_id }}</label>
|
||||||
|
<div class="col-sm-10">
|
||||||
|
<input type="text" name="payment_hutko_merchant_id" value="{{ payment_hutko_merchant_id }}" placeholder="{{ entry_merchant_id }}" id="input-payment-hutko-merchant-id" class="form-control"/>
|
||||||
|
<div id="error-payment-hutko-merchant-id" class="invalid-feedback"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-3 required">
|
||||||
|
<label for="input-secret-key" class="col-sm-2 col-form-label">{{ entry_secret_key }}</label>
|
||||||
|
<div class="col-sm-10">
|
||||||
|
<input type="text" name="payment_hutko_secret_key" value="{{ payment_hutko_secret_key }}" placeholder="{{ entry_secret_key }}" id="input-payment-hutko-secret-key" class="form-control"/>
|
||||||
|
<div id="error-payment-hutko-secret-key" class="invalid-feedback"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-3">
|
||||||
|
<label for="input-status" class="col-sm-2 col-form-label">{{ entry_status }}</label>
|
||||||
|
<div class="col-sm-10">
|
||||||
|
<div class="form-check form-switch form-switch-lg">
|
||||||
|
<input type="hidden" name="payment_hutko_status" value="0"/>
|
||||||
|
<input type="checkbox" name="payment_hutko_status" value="1" id="input-status" class="form-check-input" {% if payment_hutko_status %} checked {% endif %}/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-3">
|
||||||
|
<label for="input-sort-order" class="col-sm-2 col-form-label">{{ entry_sort_order }}</label>
|
||||||
|
<div class="col-sm-10">
|
||||||
|
<input type="text" name="payment_hutko_sort_order" value="{{ payment_hutko_sort_order }}" id="input-sort-order" class="form-control"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-3">
|
||||||
|
<label class="col-sm-2 col-form-label">{{ entry_geo_zone }}</label>
|
||||||
|
<div class="col-sm-10">
|
||||||
|
<select name="payment_hutko_geo_zone_id" id="input-geo-zone" class="form-select">
|
||||||
|
<option value="0">{{ text_all_zones }}</option>
|
||||||
|
{% for geo_zone in geo_zones %}
|
||||||
|
<option value="{{ geo_zone.geo_zone_id }}" {% if geo_zone.geo_zone_id == payment_hutko_geo_zone_id %} selected {% endif %}>{{ geo_zone.name }}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row mb-3">
|
||||||
|
<label class="col-sm-2 col-form-label">{{ entry_total }}</label>
|
||||||
|
<div class="col-sm-10">
|
||||||
|
<input type="text" name="payment_hutko_total" value="{{ payment_hutko_total }}" placeholder="{{ entry_total }}" id="input-total" class="form-control"/>
|
||||||
|
<div class="form-text">{{ help_total }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="tab-status" class="tab-pane">
|
||||||
|
<div class="row mb-3">
|
||||||
|
<label class="col-sm-2 col-form-label">{{ entry_new_order_status }}</label>
|
||||||
|
<div class="col-sm-10">
|
||||||
|
<select name="payment_hutko_new_order_status_id" class="form-select">
|
||||||
|
{% for status in order_statuses %}
|
||||||
|
<option value="{{ status.order_status_id }}" {% if status.order_status_id == payment_hutko_new_order_status_id %} selected {% endif %}>{{ status.name }}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Repeat logic for other statuses -->
|
||||||
|
<div class="row mb-3">
|
||||||
|
<label class="col-sm-2 col-form-label">{{ entry_success_status }}</label>
|
||||||
|
<div class="col-sm-10">
|
||||||
|
<select name="payment_hutko_success_status_id" class="form-select">
|
||||||
|
{% for status in order_statuses %}
|
||||||
|
<option value="{{ status.order_status_id }}" {% if status.order_status_id == payment_hutko_success_status_id %} selected {% endif %}>{{ status.name }}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="tab-advanced" class="tab-pane">
|
||||||
|
<div class="row mb-3">
|
||||||
|
<label class="col-sm-2 col-form-label">{{ entry_shipping_include }}</label>
|
||||||
|
<div class="col-sm-10">
|
||||||
|
<div class="form-check form-switch">
|
||||||
|
<input type="hidden" name="payment_hutko_shipping_include" value="0"/>
|
||||||
|
<input type="checkbox" name="payment_hutko_shipping_include" value="1" class="form-check-input" {% if payment_hutko_shipping_include %} checked {% endif %}/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-3">
|
||||||
|
<label class="col-sm-2 col-form-label">{{ entry_save_logs }}</label>
|
||||||
|
<div class="col-sm-10">
|
||||||
|
<div class="form-check form-switch">
|
||||||
|
<input type="hidden" name="payment_hutko_save_logs" value="0"/>
|
||||||
|
<input type="checkbox" name="payment_hutko_save_logs" value="1" class="form-check-input" {% if payment_hutko_save_logs %} checked {% endif %}/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="tab-logs" class="tab-pane">
|
||||||
|
<pre class="bg-light p-3 border">{{ log_content }}</pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{ footer }}
|
||||||
101
admin/view/template/payment/hutko_order_info_panel.twig
Normal file
101
admin/view/template/payment/hutko_order_info_panel.twig
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
<div class="card mb-3">
|
||||||
|
<div class="card-header">
|
||||||
|
<i class="fa-solid fa-credit-card"></i> {{ text_payment_information }} (Hutko)
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<table class="table table-bordered">
|
||||||
|
<tr>
|
||||||
|
<td>{{ text_hutko_transaction_ref_label }}</td>
|
||||||
|
<td>{{ hutko_transaction_ref_display }}</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
{% if hutko_transaction_ref_display != text_not_available %}
|
||||||
|
<hr>
|
||||||
|
<h5>{{ text_hutko_refund_title }}</h5>
|
||||||
|
<div class="row g-3 align-items-center mb-3">
|
||||||
|
<div class="col-auto">
|
||||||
|
<input type="text" id="input-refund-amount" class="form-control" placeholder="{{ entry_refund_amount }}">
|
||||||
|
</div>
|
||||||
|
<div class="col-auto">
|
||||||
|
<input type="text" id="input-refund-comment" class="form-control" placeholder="{{ entry_refund_comment }}">
|
||||||
|
</div>
|
||||||
|
<div class="col-auto">
|
||||||
|
<button type="button" id="button-hutko-refund" class="btn btn-warning">{{ button_hutko_refund }}</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="hutko-refund-response"></div>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
<h5>{{ text_hutko_status_title }}</h5>
|
||||||
|
<button type="button" id="button-hutko-status" class="btn btn-info text-white">{{ button_hutko_status_check }}</button>
|
||||||
|
<div id="hutko-status-response" class="mt-2"></div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
$('#button-hutko-refund').on('click', function () {
|
||||||
|
if (!confirm('{{ text_confirm_refund }}')) return;
|
||||||
|
|
||||||
|
var btn = $(this);
|
||||||
|
$.ajax({
|
||||||
|
url: '{{ hutko_refund_action_url|raw }}',
|
||||||
|
type: 'post',
|
||||||
|
dataType: 'json',
|
||||||
|
data: {
|
||||||
|
'refund_amount': $('#input-refund-amount').val(),
|
||||||
|
'refund_comment': $('#input-refund-comment').val(),
|
||||||
|
'order_id': {{ order_id }}
|
||||||
|
},
|
||||||
|
beforeSend: function () {
|
||||||
|
btn.prop('disabled', true);
|
||||||
|
$('#hutko-refund-response').html('');
|
||||||
|
},
|
||||||
|
complete: function () {
|
||||||
|
btn.prop('disabled', false);
|
||||||
|
},
|
||||||
|
success: function (json) {
|
||||||
|
if (json['error']) {
|
||||||
|
$('#hutko-refund-response').html('<div class="alert alert-danger">' + json['error'] + '</div>');
|
||||||
|
}
|
||||||
|
if (json['success']) {
|
||||||
|
$('#hutko-refund-response').html('<div class="alert alert-success">' + json['success'] + '</div>');
|
||||||
|
setTimeout(function(){ location.reload(); }, 2000);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function (xhr, ajaxOptions, thrownError) {
|
||||||
|
alert(thrownError + "\r\n" + xhr.statusText + "\r\n" + xhr.responseText);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#button-hutko-status').on('click', function () {
|
||||||
|
var btn = $(this);
|
||||||
|
$.ajax({
|
||||||
|
url: '{{ hutko_status_action_url|raw }}',
|
||||||
|
type: 'post',
|
||||||
|
dataType: 'json',
|
||||||
|
data: {'hutko_transaction_ref': '{{ hutko_transaction_ref_display }}'},
|
||||||
|
beforeSend: function () {
|
||||||
|
btn.prop('disabled', true);
|
||||||
|
$('#hutko-status-response').html('');
|
||||||
|
},
|
||||||
|
complete: function () {
|
||||||
|
btn.prop('disabled', false);
|
||||||
|
},
|
||||||
|
success: function (json) {
|
||||||
|
if (json['error']) {
|
||||||
|
$('#hutko-status-response').html('<div class="alert alert-danger">' + json['error'] + '</div>');
|
||||||
|
}
|
||||||
|
if (json['success']) {
|
||||||
|
let data = json['data'] ? JSON.stringify(json['data'], null, 2) : '';
|
||||||
|
$('#hutko-status-response').html('<div class="alert alert-success">' + json['success'] + '<pre class="mt-2 bg-light p-2">' + data + '</pre></div>');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function (xhr, ajaxOptions, thrownError) {
|
||||||
|
alert(thrownError + "\r\n" + xhr.statusText + "\r\n" + xhr.responseText);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
128
catalog/controller/payment/hutko.php
Normal file
128
catalog/controller/payment/hutko.php
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
<?php
|
||||||
|
namespace Opencart\Catalog\Controller\Extension\Hutko\Payment;
|
||||||
|
|
||||||
|
class Hutko extends \Opencart\System\Engine\Controller {
|
||||||
|
private $checkout_url = 'https://pay.hutko.org/api/checkout/url/';
|
||||||
|
|
||||||
|
public function index(): string {
|
||||||
|
$this->load->language('extension/hutko/payment/hutko');
|
||||||
|
return $this->load->view('extension/hutko/payment/hutko', ['language' => $this->config->get('config_language')]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function confirm(): void {
|
||||||
|
$this->load->language('extension/hutko/payment/hutko');
|
||||||
|
$this->load->model('checkout/order');
|
||||||
|
|
||||||
|
$json = [];
|
||||||
|
|
||||||
|
if (!isset($this->session->data['order_id'])) {
|
||||||
|
$json['error'] = 'Session missing';
|
||||||
|
$json['redirect'] = $this->url->link('checkout/failure', 'language=' . $this->config->get('config_language'), true);
|
||||||
|
} else {
|
||||||
|
$order_info = $this->model_checkout_order->getOrder($this->session->data['order_id']);
|
||||||
|
|
||||||
|
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'];
|
||||||
|
} else {
|
||||||
|
$json['error'] = $response['response']['error_message'] ?? $this->language->get('error_api_communication');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->response->addHeader('Content-Type: application/json');
|
||||||
|
$this->response->setOutput(json_encode($json));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function callback(): void {
|
||||||
|
$input = file_get_contents("php://input");
|
||||||
|
$data = json_decode($input, true);
|
||||||
|
|
||||||
|
if (!$data || !$this->validate($data)) {
|
||||||
|
http_response_code(400);
|
||||||
|
exit('Invalid Request');
|
||||||
|
}
|
||||||
|
|
||||||
|
$parts = explode('#', $data['order_id']);
|
||||||
|
$order_id = (int)$parts[0];
|
||||||
|
|
||||||
|
$this->load->model('checkout/order');
|
||||||
|
$order_info = $this->model_checkout_order->getOrder($order_id);
|
||||||
|
|
||||||
|
if ($order_info) {
|
||||||
|
$status = $data['order_status'] ?? '';
|
||||||
|
|
||||||
|
// 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";
|
||||||
|
} elseif ($status === 'declined') {
|
||||||
|
$this->model_checkout_order->addHistory($order_id, $this->config->get('payment_hutko_declined_status_id'), 'Declined', true);
|
||||||
|
echo "Declined";
|
||||||
|
} else {
|
||||||
|
echo "Status update received";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function buildRequest($order) {
|
||||||
|
$ref = $order['order_id'] . '#' . time();
|
||||||
|
$total = (int)round($order['total'] * 100); // Send in cents
|
||||||
|
|
||||||
|
$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),
|
||||||
|
'server_callback_url' => $this->url->link('extension/hutko/payment/hutko.callback', '', true),
|
||||||
|
'reservation_data' => base64_encode(json_encode(['products' => []])) // simplified for brevity
|
||||||
|
];
|
||||||
|
|
||||||
|
$data['signature'] = $this->sign($data);
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function sign($data) {
|
||||||
|
$key = $this->config->get('payment_hutko_secret_key');
|
||||||
|
$arr = array_filter($data, function($v){ return $v !== '' && $v !== null; });
|
||||||
|
ksort($arr);
|
||||||
|
$str = $key;
|
||||||
|
foreach($arr as $v) $str .= '|' . $v;
|
||||||
|
return sha1($str);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function validate($data) {
|
||||||
|
$sig = $data['signature'] ?? '';
|
||||||
|
unset($data['signature'], $data['response_signature_string']);
|
||||||
|
return hash_equals($this->sign($data), $sig);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function api($url, $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']);
|
||||||
|
$res = curl_exec($ch);
|
||||||
|
curl_close($ch);
|
||||||
|
return json_decode($res, true) ?: [];
|
||||||
|
}
|
||||||
|
}
|
||||||
14
catalog/language/en-gb/payment/hutko.php
Normal file
14
catalog/language/en-gb/payment/hutko.php
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<?php
|
||||||
|
// Text
|
||||||
|
$_['text_title'] = 'Credit Card / Debit Card (Hutko)';
|
||||||
|
$_['text_initiated_payment'] = 'Customer initiated payment via Hutko.';
|
||||||
|
$_['text_order'] = 'Order';
|
||||||
|
$_['text_loading'] = 'Loading';
|
||||||
|
$_['error_payment_data_build'] = 'Error: Could not prepare payment data. Please try again or contact support.';
|
||||||
|
$_['error_api_communication'] = 'Error: Could not communicate with the payment gateway. Please try again.';
|
||||||
|
$_['text_redirecting_comment'] = 'Redirecting to Hutko. Hutko Order ID: %s. URL: %s';
|
||||||
|
// For callback
|
||||||
|
$_['text_payment_approved'] = 'Payment Approved by Hutko.';
|
||||||
|
$_['text_payment_declined'] = 'Payment Declined by Hutko.';
|
||||||
|
$_['text_payment_expired'] = 'Payment Expired at Hutko.';
|
||||||
|
$_['text_payment_processing'] = 'Payment is Processing at Hutko.';
|
||||||
16
catalog/language/ru-ru/payment/hutko.php
Normal file
16
catalog/language/ru-ru/payment/hutko.php
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<?php
|
||||||
|
// Hutko translation file
|
||||||
|
|
||||||
|
|
||||||
|
$_['text_title'] = 'Оплата картой через Hutko';
|
||||||
|
$_['text_initiated_payment'] = 'Клиент инициировал платеж через Hutko.';
|
||||||
|
$_['text_order'] = 'Заказ';
|
||||||
|
$_['text_loading'] = 'Загрузка';
|
||||||
|
$_['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.';
|
||||||
16
catalog/language/uk-ua/payment/hutko.php
Normal file
16
catalog/language/uk-ua/payment/hutko.php
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<?php
|
||||||
|
// Hutko translation file
|
||||||
|
|
||||||
|
|
||||||
|
$_['text_title'] = 'Оплата карткою через Hutko';
|
||||||
|
$_['text_initiated_payment'] = 'Клієнт ініціював платіж через Hutko.';
|
||||||
|
$_['text_order'] = 'Замовлення';
|
||||||
|
$_['text_loading'] = 'Завантаження';
|
||||||
|
$_['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.';
|
||||||
76
catalog/model/payment/hutko.php
Normal file
76
catalog/model/payment/hutko.php
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Opencart\Catalog\Model\Extension\Hutko\Payment;
|
||||||
|
|
||||||
|
class Hutko extends \Opencart\System\Engine\Model
|
||||||
|
{
|
||||||
|
|
||||||
|
public function getMethods(array $address = []): array
|
||||||
|
{
|
||||||
|
$method_data = $this->getMethod($address);
|
||||||
|
|
||||||
|
// Only return the method if it actually has data
|
||||||
|
if ($method_data) {
|
||||||
|
return $method_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getMethod(array $address = []): array
|
||||||
|
{
|
||||||
|
$this->load->language('extension/hutko/payment/hutko');
|
||||||
|
$allowed_currencies = ['UAH', 'USD', 'EUR', 'GBP', 'CZK'];
|
||||||
|
if (!in_array(strtoupper($this->session->data['currency']), $allowed_currencies)) {
|
||||||
|
$status = false;
|
||||||
|
}
|
||||||
|
// 1. Validate Address (Safeguard against undefined keys)
|
||||||
|
$country_id = isset($address['country_id']) ? (int)$address['country_id'] : 0;
|
||||||
|
$zone_id = isset($address['zone_id']) ? (int)$address['zone_id'] : 0;
|
||||||
|
|
||||||
|
$status = true;
|
||||||
|
|
||||||
|
// 2. Check Geo Zone
|
||||||
|
if ($this->config->get('payment_hutko_geo_zone_id')) {
|
||||||
|
$query = $this->db->query("SELECT * FROM " . DB_PREFIX . "zone_to_geo_zone WHERE geo_zone_id = '" . (int)$this->config->get('payment_hutko_geo_zone_id') . "' AND country_id = '" . $country_id . "' AND (zone_id = '" . $zone_id . "' OR zone_id = '0')");
|
||||||
|
|
||||||
|
if (!$query->num_rows) {
|
||||||
|
$status = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Check Order Total
|
||||||
|
if ($this->config->get('payment_hutko_total') > 0 && $this->config->get('payment_hutko_total') > $this->cart->getTotal()) {
|
||||||
|
$status = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Return Data
|
||||||
|
$method_data = [];
|
||||||
|
|
||||||
|
if ($status && $this->config->get('payment_hutko_status')) {
|
||||||
|
$option_data = [];
|
||||||
|
|
||||||
|
$option_data['hutko'] = [
|
||||||
|
'code' => 'hutko.hutko',
|
||||||
|
'name' => $this->language->get('text_title')
|
||||||
|
];
|
||||||
|
|
||||||
|
// FORCE (int) casting and default value to prevent "Undefined array key" error
|
||||||
|
$sort_order = (int)$this->config->get('payment_hutko_sort_order') ?? 1;
|
||||||
|
|
||||||
|
$method_data = [
|
||||||
|
'code' => 'hutko',
|
||||||
|
'name' => $this->language->get('text_title'),
|
||||||
|
'option' => $option_data,
|
||||||
|
'sort_order' => $sort_order
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $method_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addHutkoOrder($order_id, $ref)
|
||||||
|
{
|
||||||
|
$this->db->query("INSERT INTO `" . DB_PREFIX . "hutko_order` SET `order_id` = '" . (int)$order_id . "', `hutko_transaction_ref` = '" . $this->db->escape($ref) . "', `date_added` = NOW() ON DUPLICATE KEY UPDATE `hutko_transaction_ref` = '" . $this->db->escape($ref) . "'");
|
||||||
|
}
|
||||||
|
}
|
||||||
30
catalog/view/template/payment/hutko.twig
Normal file
30
catalog/view/template/payment/hutko.twig
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
<div class="d-inline-block pt-2 pd-2 w-100 text-end">
|
||||||
|
<button type="button" id="button-confirm" class="btn btn-primary">{{ button_confirm }}</button>
|
||||||
|
</div>
|
||||||
|
<script type="text/javascript"><!--
|
||||||
|
$('#button-confirm').on('click', function() {
|
||||||
|
var element = this;
|
||||||
|
$.ajax({
|
||||||
|
url: 'index.php?route=extension/hutko/payment/hutko.confirm&language={{ language }}',
|
||||||
|
type: 'get',
|
||||||
|
dataType: 'json',
|
||||||
|
beforeSend: function() {
|
||||||
|
$(element).prop('disabled', true).addClass('loading');
|
||||||
|
},
|
||||||
|
complete: function() {
|
||||||
|
$(element).prop('disabled', false).removeClass('loading');
|
||||||
|
},
|
||||||
|
success: function(json) {
|
||||||
|
if (json['error']) {
|
||||||
|
alert(json['error']);
|
||||||
|
}
|
||||||
|
if (json['redirect']) {
|
||||||
|
location = json['redirect'];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function(xhr, ajaxOptions, thrownError) {
|
||||||
|
console.log(thrownError + "\r\n" + xhr.statusText + "\r\n" + xhr.responseText);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
//--></script>
|
||||||
8
install.json
Normal file
8
install.json
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"name": "Hutko Payments",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"author": "Hutko",
|
||||||
|
"link": "https://hutko.org",
|
||||||
|
"instruction": "",
|
||||||
|
"code": "hutko"
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user