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) ?: []; } }