From 386023521a2d9d9926d3dc87b2c2282d8d396aeb Mon Sep 17 00:00:00 2001 From: O K Date: Sat, 31 May 2025 15:35:42 +0300 Subject: [PATCH] added refunds --- hutko.php | 362 +++++++++++++++++- logo.png | Bin 7934 -> 1419 bytes .../templates/admin/order_payment_refund.tpl | 123 ++++++ 3 files changed, 467 insertions(+), 18 deletions(-) create mode 100644 views/templates/admin/order_payment_refund.tpl diff --git a/hutko.php b/hutko.php index ec6d66f..ba8776b 100644 --- a/hutko.php +++ b/hutko.php @@ -10,6 +10,7 @@ * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) */ + use PrestaShop\PrestaShop\Core\Payment\PaymentOption; if (!defined('_PS_VERSION_')) { @@ -22,6 +23,9 @@ class Hutko extends PaymentModule public $order_separator = '#'; public $checkout_url = 'https://pay.hutko.org/api/checkout/redirect/'; + public $refund_url = 'https://pay.hutko.org/api/reverse/order_id'; + public $status_url = 'https://pay.hutko.org/api/status/order_id'; + private $settingsList = [ 'HUTKO_MERCHANT', @@ -39,19 +43,19 @@ class Hutko extends PaymentModule $this->version = '1.1.0'; $this->author = 'Hutko'; $this->bootstrap = true; - $this->ps_versions_compliancy = array('min' => '1.7', 'max' => _PS_VERSION_); - $this->is_eu_compatible = 1; - parent::__construct(); - $this->displayName = $this->trans('Hutko Payments', array(), 'Modules.Hutko.Admin'); - $this->description = $this->trans('Hutko is a payment platform whose main function is to provide internet acquiring. - Payment gateway supports EUR, USD, PLN, GBP, UAH, RUB and +100 other currencies.', array(), 'Modules.Hutko.Admin'); + $this->ps_versions_compliancy = array('min' => '1.7', 'max' => _PS_VERSION_); + //Do not translate displayName as it is used for payment identification + $this->displayName = 'Hutko Payments'; + $this->description = $this->trans('Hutko is a payment platform whose main function is to provide internet acquiring.', array(), 'Modules.Hutko.Admin'); } public function install() { return parent::install() - && $this->registerHook('paymentOptions'); + && $this->registerHook('paymentOptions') + && $this->registerHook('displayAdminOrderContentOrder') + && $this->registerHook('actionAdminControllerSetMedia'); } public function uninstall() @@ -213,6 +217,7 @@ class Hutko extends PaymentModule */ protected function postProcess() { + $form_values = $this->getConfigFormValues(); foreach (array_keys($form_values) as $key) { Configuration::updateValue($key, Tools::getValue($key)); @@ -329,7 +334,7 @@ class Hutko extends PaymentModule // 3. Create a description for the order. $orderDescription = $this->trans('Cart pay №', [], 'Modules.Hutko.Admin') . $this->context->cart->id; // 4. Calculate the order amount in the smallest currency unit. - $amount = round($this->context->cart->getOrderTotal() * 100); + $amount = round($this->context->cart->getOrderTotal(true, CART::ONLY_PRODUCTS) * 100); // 5. Get the currency ISO code of the current cart. $currency = $this->context->currency->iso_code; @@ -360,7 +365,7 @@ class Hutko extends PaymentModule ]; // 11. Generate the signature for the data array using the merchant's secret key. - $data['signature'] = $this->getSignature($data, Configuration::get('HUTKO_SECRET_KEY')); + $data['signature'] = $this->getSignature($data); // 12. Return the complete data array including the signature. return $data; @@ -397,7 +402,7 @@ class Hutko extends PaymentModule "cms_version" => _PS_VERSION_, "shop_domain" => Tools::getShopDomainSsl(), "path" => 'https://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'], - "phonemobile" => $address->phone_mobile ?? $address->phone, + "phonemobile" => empty($address->phone_mobile) ? $address->phone : $address->phone_mobile, "customer_address" => $this->getSlug($address->address1), "customer_country" => $this->getSlug($address->country), "customer_state" => $this->getSlug($customerState), @@ -561,8 +566,12 @@ class Hutko extends PaymentModule * or the raw string before encoding (false). * @return string The generated signature (SHA1 hash by default) or the raw string. */ - public function getSignature(array $data, string $password, bool $encoded = true): string + public function getSignature(array $data, bool $encoded = true): string { + $password = Configuration::get('HUTKO_SECRET_KEY'); + if (!$password || empty($password)) { + throw new PrestaShopException('Merchant secret not set'); + } // 1. Filter out empty and null values from the data array. $filteredData = array_filter($data, function ($value) { return $value !== '' && $value !== null; @@ -617,7 +626,7 @@ class Hutko extends PaymentModule unset($response['response_signature_string'], $response['signature']); // 3. Calculate and Compare Signatures - $calculatedSignature = $this->getSignature($response, Configuration::get('HUTKO_SECRET_KEY')); + $calculatedSignature = $this->getSignature($response); return hash_equals($calculatedSignature, $responseSignature); } @@ -670,7 +679,7 @@ class Hutko extends PaymentModule * @param string $message A message to log with the status change. * @return void */ - public function updateOrderStatus(int $orderId, int $newStateId, string $message = ''): void + public function updateOrderStatus(int $orderId, int $newStateId): void { $order = new Order($orderId); // Only update if the order is loaded and the current state is different from the new state. @@ -678,11 +687,328 @@ class Hutko extends PaymentModule $history = new OrderHistory(); $history->id_order = $orderId; $history->changeIdOrderState($newStateId, $orderId); - $history->addWithemail(true, ['order_name' => $orderId]); - // PrestaShopLogger::addLog('Hutko Callback: Order ' . $orderId . ' status changed to ' . $newStateId . '. Message: ' . $message, 1, null, 'Order', $orderId, true); - } else { - // Log if the order was not loaded or already in the target state. - // PrestaShopLogger::addLog('Hutko Callback: Attempted to update order ' . $orderId . ' to state ' . $newStateId . ' but order not loaded or already in target state. Message: ' . $message, 2, null, 'Order', $orderId, true); + $history->addWithemail(); } } + + + /** + * Hook to display content in the admin order page tabs. + * This will add a new tab for "Hutko Payments" or similar. + * + * @param array $params Contains Order 'order' + * @return string + */ + public function hookdisplayAdminOrderContentOrder(array $params): string + { + if (Tools::isSubmit('hutkoRefundsubmit')) { + $this->processRefundForm(); + } + if (Tools::getValue('hutkoOrderStatus')) { + $this->processOrderStatus(Tools::getValue('hutkoOrderStatus')); + } + // This hook is used to render the content of the new tab on the order page. + // We will fetch the payments for this order and pass them to the template. + + $order = $params['order']; + + + // Fetch payments made via Hutko for this order + $hutkoPayments = new PrestaShopCollection('OrderPayment'); + $hutkoPayments->where('order_reference', '=', $order->reference); + $hutkoPayments->where('payment_method', '=', $this->displayName); + + $this->context->smarty->assign([ + 'hutkoPayments' => $hutkoPayments->getAll(), + 'id_order' => $order->id, + ]); + + return $this->display(__FILE__, 'views/templates/admin/order_payment_refund.tpl'); + } + public function processOrderStatus(string $order_id): void + { + $data = [ + 'order_id' => $order_id, + 'merchant_id' => Configuration::get('HUTKO_MERCHANT', null), + 'version' => '1.0', + ]; + $data['signature'] = $this->getSignature($data); + + $response = $this->sendAPICall($this->status_url, $data); + $this->context->controller->informations[] = $this->displayArrayInNotification($response['response']); + } + /** + * Hook to set media (JS/CSS) for admin controllers. + * Used to load our custom JavaScript for the refund modal. + * + * @param array $params + * @return void + */ + public function hookActionAdminControllerSetMedia(array $params): void + { + // Only load our JS on the AdminOrders controller page + if ($this->context->controller->controller_name === 'AdminOrders') { + } + } + public function processRefundForm() + { + $orderPaymentId = (int) Tools::getValue('orderPaymentId'); + $amount = (float) Tools::getValue('refund_amount'); + $comment = mb_substr(Tools::getValue('orderPaymentId', ''), 0, 1024); + $orderId = (int) Tools::getValue('id_order'); + $result = $this->processRefund($orderPaymentId, $orderId, $amount, $comment); + if ($result->error) { + $this->context->controller->errors[] = $result->description; + } + if ($result->success) { + $this->context->controller->informations[] = $result->description; + } + } + + /** + * Processes a payment refund via the Hutko gateway and updates PrestaShop order. + * + * This method initiates a refund request to the Hutko payment gateway for a specific + * order payment. Upon successful refund from Hutko, it creates an OrderSlip (if partial), + * updates the order history, and logs the action. + * + * @param int $orderPaymentId The ID of the OrderPayment record to refund. + * @param int $orderId The ID of the Order to refund. + * @param float $amount The amount to refund. + * @param string $comment A comment or reason for the refund. + * @return stdClass Result description. + * @throws Exception If the OrderPayment is not found, invalid, or refund fails. + */ + public function processRefund(int $orderPaymentId, int $orderId, float $amount, string $comment = ''): stdClass + { + $result = new stdClass(); + $result->error = false; + // 1. Load the OrderPayment object. + $orderPayment = new OrderPayment($orderPaymentId); + $currency = new Currency($orderPayment->id_currency); + if (!Validate::isLoadedObject($orderPayment)) { + PrestaShopLogger::addLog( + 'Hutko Refund: OrderPayment object not found for ID: ' . $orderPaymentId, + 3, // Error + null, + 'OrderPayment', + $orderPaymentId, + true + ); + throw new Exception($this->trans('Order payment not found.', [], 'Modules.Hutko.Admin')); + } + + // 2. Validate the transaction_id format and extract cart ID. + // Assuming transaction_id is in the format "cartID|timestamp" or "cartID-timestamp" + $transactionIdParts = explode($this->order_separator, $orderPayment->transaction_id); + $cartId = (int)$transactionIdParts[0]; + + if (!$cartId) { + PrestaShopLogger::addLog( + 'Hutko Refund: Invalid transaction ID format for OrderPayment ID: ' . $orderPaymentId . ' Transaction ID: ' . $orderPayment->transaction_id, + 3, // Error + null, + 'OrderPayment', + $orderPaymentId, + true + ); + throw new Exception($this->trans('Invalid transaction ID format.', [], 'Modules.Hutko.Admin')); + } + + + $response = $this->refundAPICall($orderPayment->transaction_id, $amount, $currency->iso_code, $comment); + + + + if ($response['response']['response_status'] === 'failure') { + $result->error = true; + $result->description = $response['response']['error_message']; + return $result; + } + if ($response['response']['response_status'] === 'success') { + $result->success = true; + $result->description = $this->trans('Refund success.', [], 'Modules.Hutko.Admin'); + + } + + $order = new Order($orderId); + + // Add a note to the order history. + $this->updateOrderStatus($order->id, (int)Configuration::get('PS_OS_REFUND')); + + // Add a private message to the order for tracking. + $order->addOrderPayment( + -$amount, // Negative amount for refund + $this->displayName, + $orderPayment->transaction_id + ); + + PrestaShopLogger::addLog( + 'Hutko Refund: Successfully processed refund for Order: ' . $orderId . ', Amount: ' . $amount . ', Comment: ' . $comment, + 1, // Info + null, + 'OrderPayment', + $orderPaymentId, + true + ); + + return $result; + } + /** + * Initiates a refund (reverse) request via Hutko API. + * + * @param string $order_id The gateway's order ID to refund. + * @param float $amount The amount to refund (in base units, e.g., 100.50). + * @param string $currency The currency code (e.g., 'UAH'). + * @param string $comment Optional comment for the refund. + * @return array Decoded API response array. Returns an error structure on failure. + */ + public function refundAPICall(string $order_id, float $amount, string $currency, string $comment = ''): array + { + // 1. Prepare the data payload + $data = [ + 'order_id' => $order_id, + // Assuming Configuration::get is available to fetch the merchant ID + 'merchant_id' => Configuration::get('HUTKO_MERCHANT', null), + 'version' => '1.0', + // Amount should be in minor units (cents, kopecks) and converted to string as per API example + 'amount' => (string)round($amount * 100), + 'currency' => $currency, + ]; + + if (!empty($comment)) { + $data['comment'] = $comment; + } + + // 2. Calculate the signature based on the data array *before* wrapping in 'request' + $data['signature'] = $this->getSignature($data); + return $this->sendAPICall($this->refund_url, $data); + } + + /** + * Initiates a request via Hutko API. + * + * @param string $url The gateway's url. + * @param array $data The data. + * @return array Decoded API response array. Returns an error structure on failure. + */ + public function sendAPICall(string $url, array $data, int $timeout = 60): array + { + + + // Wrap the prepared data inside the 'request' key as required by the API + $requestPayload = ['request' => $data]; + + // Convert the payload to JSON string + $jsonPayload = json_encode($requestPayload); + + if ($jsonPayload === false) { + // Handle JSON encoding error + return [ + 'response' => [ + 'response_status' => 'failure', + 'error_message' => 'Failed to encode request data to JSON: ' . json_last_error_msg(), + 'error_code' => 'JSON_ENCODE_ERROR' + ] + ]; + } + + // Initialize CURL + $ch = curl_init(); + + // 4. Set CURL options + curl_setopt($ch, CURLOPT_URL, $url); + curl_setopt($ch, CURLOPT_POST, true); // Use POST method + curl_setopt($ch, CURLOPT_POSTFIELDS, $jsonPayload); // Set the JSON body + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); // Return the response as a string + curl_setopt($ch, CURLOPT_HTTPHEADER, [ + 'Content-Type: application/json', + 'Content-Length: ' . strlen($jsonPayload), // Good practice + ]); + + // Recommended for production: Verify SSL certificate + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true); + curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2); // Verify hostname against certificate + + + curl_setopt($ch, CURLOPT_TIMEOUT, $timeout); // Timeout in seconds + + // Execute the CURL request + $response = curl_exec($ch); + + // Check for CURL errors + $curl_error = curl_error($ch); + $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE); + + if ($curl_error) { + // Log the error or handle it appropriately + curl_close($ch); + return [ + 'response' => [ + 'response_status' => 'failure', + 'error_message' => 'CURL Error: ' . $curl_error, + 'error_code' => 'CURL_' . curl_errno($ch), + 'http_code' => $http_code // Include http code for context + ] + ]; + } + + // Close CURL handle + curl_close($ch); + + // Process the response + // Decode the JSON response into a PHP array + $responseData = json_decode($response, true); + + // Check if JSON decoding failed + if (json_last_error() !== JSON_ERROR_NONE) { + // Log the error or handle it appropriately + return [ + 'response' => [ + 'response_status' => 'failure', + 'error_message' => 'Invalid JSON response from API: ' . json_last_error_msg(), + 'error_code' => 'JSON_DECODE_ERROR', + 'http_code' => $http_code, + 'raw_response' => $response // Include raw response for debugging + ] + ]; + } + return $responseData; + } + + + /** + * Displays an array's contents in a PrestaShop notification box. + * + * This function is intended for debugging or displaying API responses/structured data + * in the PrestaShop back office notifications. + * + * @param array $data The array to display. + */ + protected function displayArrayInNotification(array $data): string + { + if (isset($data['response_signature_string'])) { + unset($data['response_signature_string']); + } + if (isset($data['additional_info'])) { + $data['additional_info_decoded'] = json_decode($data['additional_info'], true); + if (isset($data['additional_info_decoded']['reservation_data'])) { + $data['additional_info_decoded']['reservation_data_decoded'] = json_decode($data['additional_info_decoded']['reservation_data'], true); + unset($data['additional_info_decoded']['reservation_data']); + } + unset($data['additional_info']); + } + $retStr = ''; + return $retStr; + } } diff --git a/logo.png b/logo.png index 8b49bd701aa709ad122f9f08982832403840f8d7..c1e987c7d7ea6ced4f1039afbe145198f86781c1 100644 GIT binary patch delta 1378 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 diff --git a/views/templates/admin/order_payment_refund.tpl b/views/templates/admin/order_payment_refund.tpl new file mode 100644 index 0000000..b6c669a --- /dev/null +++ b/views/templates/admin/order_payment_refund.tpl @@ -0,0 +1,123 @@ +{* +* Hutko PrestaShop Module +* +* This template is used to display the Hutko payment refund tab content +* on the PrestaShop admin order details page. +* It lists all Hutko payments for the order and provides a refund button +* which opens a modal for amount and comment input. +*} +{if $hutkoPayments->count()} + + +
+
+ {l s='Hutko Payments & Refunds' mod='hutko'} +
+ +
+
+
+ + + + + + + + + + + {foreach from=$hutkoPayments item='payment'} + + + + + + + + {/foreach} + +
{l s='Transaction ID' mod='hutko'}{l s='Amount' mod='hutko'}{l s='Payment Date' mod='hutko'}{l s='Actions' mod='hutko'}
{$payment->transaction_id|escape:'htmlall':'UTF-8'}{displayPrice price=Tools::ps_round($payment->amount, 2) currency=$currency->id|floatval} + {$payment->date_add|date_format:'%Y-%m-%d %H:%M:%S'} + {if $payment->amount > 0} + + {/if} + + + +
+
+
+
+ + + + +
+{/if} \ No newline at end of file