commit a9dff0a52a69b068264f7bb1652c5463758a2b8e Author: O K Date: Thu Aug 28 21:44:55 2025 +0300 first commit diff --git a/config_uk.xml b/config_uk.xml new file mode 100644 index 0000000..bd1b99a --- /dev/null +++ b/config_uk.xml @@ -0,0 +1,12 @@ + + + ibanpro + + + + + + + 1 + 0 + \ No newline at end of file diff --git a/controllers/front/validation.php b/controllers/front/validation.php new file mode 100644 index 0000000..1e2c3cc --- /dev/null +++ b/controllers/front/validation.php @@ -0,0 +1,139 @@ +context->cart) || + !Validate::isLoadedObject($this->context->customer) || + !Validate::isLoadedObject($this->context->currency) || + !$this->module->active // Ensure the module is active + ) { + PrestaShopLogger::addLog( + 'banProValidation: Initial validation failed. Cart ID: ' . (int)$this->context->cart->id . + ' Customer ID: ' . (int)$this->context->customer->id . ' Module Active: ' . (int)$this->module->active, + 3 + ); + Tools::redirect('index.php?controller=order&step=1'); + return; + } + + $this->processOrderCreation(); + } + + /** + * Processes the online payment flow. + * This method orchestrates the steps for creating an order and redirecting to LiqPay. + */ + protected function processOrderCreation(): void + { + $this->validateCartAndModuleAccess(); + $order = $this->createOrder(); + + Tools::redirect($this->context->link->getPageLink('order-confirmation', true, $this->context->language->id, [ + 'id_cart' => (int)$this->context->cart->id, + 'id_module' => (int)$this->module->id, + 'id_order' => (int)$order->id, + 'key' => $this->context->customer->secure_key, + ])); + } + + /** + * Validates the cart and checks if the payment module is authorized for the current cart. + * Redirects to the order page if validation fails. + */ + protected function validateCartAndModuleAccess(): void + { + // Check cart validity (redundant if init is robust, but good for defensive programming) + if ( + (int)$this->context->cart->id_customer === 0 || + (int)$this->context->cart->id_address_delivery === 0 || + (int)$this->context->cart->id_address_invoice === 0 || + (int)$this->context->cart->id === 0 // Check for valid cart ID (not null or 0) + ) { + PrestaShopLogger::addLog( + 'IbanProValidation: Invalid cart details during validation. Cart ID: ' . (int)$this->context->cart->id, + 3 + ); + Tools::redirect('index.php?controller=order&step=1'); + } + + // Check that this payment option is still available + $authorized = false; + foreach (Module::getPaymentModules() as $module) { + if ($module['name'] === $this->module->name) { + $authorized = true; + break; + } + } + + if (!$authorized) { + PrestaShopLogger::addLog( + 'IbanProValidation: Payment method is not authorized for cart ' . (int)$this->context->cart->id, + 3 + ); + // Using die() with l() is acceptable for payment module authorization errors. + die($this->module->l('This payment method is not available.', 'validation')); + } + } + + /** + * Creates a new PrestaShop order for the current cart. + * + * @return \Order The newly created order object. + * @throws PrestaShopException If order creation fails. + */ + protected function createOrder(): Order + { + try { + // Validate and create the order + $this->module->validateOrder( + (int)$this->context->cart->id, + Configuration::get('IBANTRANSFER_OS_CREATION'), // Use the appropriate pending status for your module + (float)$this->context->cart->getOrderTotal(true, Cart::BOTH), + $this->module->paymentMethodName, // Payment method name for order history + null, // Message + null, // Extra vars + (int)$this->context->currency->id, + false, // Don't convert currency + $this->context->customer->secure_key + ); + + // After validateOrder, the module's currentOrder property holds the new order ID + $order = new Order((int)$this->module->currentOrder); + + if (!Validate::isLoadedObject($order)) { + throw new PrestaShopException('Failed to load the newly created order.'); + } + return $order; + } catch (Throwable $e) { + PrestaShopLogger::addLog( + 'IbanProValidation: Order creation failed for cart ' . (int)$this->context->cart->id . '. Error: ' . $e->getMessage() . ' Trace: ' . $e->getTraceAsString(), + 4 + ); + Tools::redirect('index.php?controller=order&step=1'); // Redirect to order page on failure + // In a real scenario, you might want to display a more user-friendly error or log to the user. + } + } +} diff --git a/ibanpro.php b/ibanpro.php new file mode 100644 index 0000000..b361b2b --- /dev/null +++ b/ibanpro.php @@ -0,0 +1,369 @@ +name = 'ibanpro'; + + $this->tab = 'advertising_marketing'; + $this->version = '1.0.0'; + $this->author = 'panariga'; + + $this->need_instance = 0; + parent::__construct(); + + $this->displayName = $this->trans('Iban.pro'); + $this->description = $this->trans('Iban.pro module for Ukraine'); + $this->confirmUninstall = $this->trans('Are you sure about removing these details?'); + $this->ps_versions_compliancy = array( + 'min' => '1.7', + 'max' => _PS_VERSION_, + ); + } + + public function install() + { + return parent::install() && + $this->registerHook('displayOrderConfirmation') && $this->registerHook('paymentOptions'); + } + + + public function uninstall() + { + if (!parent::uninstall()) { + return false; + } + + return true; + } + + /** + * Entry point for the module's configuration page. + * Handles form submission and displays the configuration form. + */ + public function getContent() + { + $output = ''; + + // Process form submission if it occurs + if (Tools::isSubmit('submitIbanTransferModule')) { + $output .= $this->postProcess(); + } + + // Display the configuration form + return $output . $this->renderForm(); + } + + /** + * Saves the configuration settings when the form is submitted. + */ + protected function postProcess() + { + if (Tools::isSubmit('submitIbanTransferModule')) { + // A list of configuration keys to update. This makes it easy to add more later. + $configKeys = [ + 'IBANTRANSFER_IBAN', + 'IBANTRANSFER_RECEIVER_NAME', + 'IBANTRANSFER_RECEIVER_CODE', + 'IBANTRANSFER_DESCRIPTION_APPEND', + 'IBANTRANSFER_API_TOKEN', + 'IBANTRANSFER_OS_CREATION', + 'IBANTRANSFER_OS_FULL_PAYMENT', + 'IBANTRANSFER_OS_PARTIAL_PAYMENT', + ]; + + foreach ($configKeys as $key) { + // We use Tools::getValue which sanitizes the input + Configuration::updateValue($key, Tools::getValue($key)); + } + + return $this->displayConfirmation($this->l('Settings have been updated successfully.')); + } + + return ''; + } + + /** + * Renders the configuration form using PrestaShop's HelperForm. + */ + public function renderForm() + { + // Define the structure of the configuration form + $fields_form = []; + + // === Fieldset 1: Bank Account Details === + $fields_form[0]['form'] = [ + 'legend' => [ + 'title' => $this->l('Bank Account Details'), + 'icon' => 'icon-university', // A more appropriate icon + ], + 'input' => [ + [ + 'type' => 'text', + 'label' => $this->l('IBAN Account'), + 'name' => 'IBANTRANSFER_IBAN', + 'required' => true, + 'desc' => $this->l('Enter the full IBAN for receiving payments.'), + 'class' => 'fixed-width-xxl', + ], + [ + 'type' => 'text', + 'label' => $this->l('Receiver Name'), + 'name' => 'IBANTRANSFER_RECEIVER_NAME', + 'required' => true, + 'desc' => $this->l('Enter the full name of the account holder (individual or company).'), + 'class' => 'fixed-width-xl', + ], + [ + 'type' => 'text', + 'label' => $this->l('Receiver ID Code (EDRPOU/TIN)'), + 'name' => 'IBANTRANSFER_RECEIVER_CODE', + 'required' => true, + 'desc' => $this->l('Enter the unique identification code for the receiver.'), + 'class' => 'fixed-width-lg', + ], + [ + 'type' => 'text', + 'label' => $this->l('Append to Payment Description'), + 'name' => 'IBANTRANSFER_DESCRIPTION_APPEND', + 'required' => false, + 'desc' => $this->l('This text will be added after the default description (e.g., "Payment for order #..."). You can use {order_reference} or {cart_id} as placeholders.'), + 'class' => 'fixed-width-xxl', + ], + ], + 'submit' => [ + 'title' => $this->l('Save'), + 'class' => 'btn btn-default pull-right', + ], + ]; + + // === Fieldset 2: Order Statuses Configuration === + $order_states = OrderState::getOrderStates((int)$this->context->language->id); + + $fields_form[1]['form'] = [ + 'legend' => [ + 'title' => $this->l('Order Statuses'), + 'icon' => 'icon-cogs', + ], + 'input' => [ + [ + 'type' => 'select', + 'label' => $this->l('Order Status on Creation'), + 'name' => 'IBANTRANSFER_OS_CREATION', + 'options' => [ + 'query' => $order_states, + 'id' => 'id_order_state', + 'name' => 'name', + ], + 'desc' => $this->l('The status for a new order created with this payment method (e.g., "Awaiting bank wire payment").'), + ], + [ + 'type' => 'select', + 'label' => $this->l('Order Status on Full Payment'), + 'name' => 'IBANTRANSFER_OS_FULL_PAYMENT', + 'options' => [ + 'query' => $order_states, + 'id' => 'id_order_state', + 'name' => 'name', + ], + 'desc' => $this->l('The status when the full payment is confirmed (e.g., "Payment accepted").'), + ], + [ + 'type' => 'select', + 'label' => $this->l('Order Status on Partial Payment'), + 'name' => 'IBANTRANSFER_OS_PARTIAL_PAYMENT', + 'options' => [ + 'query' => $order_states, + 'id' => 'id_order_state', + 'name' => 'name', + ], + 'desc' => $this->l('Optional: The status if a partial payment is received (e.g., "On backorder (paid)").'), + ], + ], + 'submit' => [ + 'title' => $this->l('Save'), + 'class' => 'btn btn-default pull-right', + ], + ]; + + // === Fieldset 3: API Integration === + $fields_form[2]['form'] = [ + 'legend' => [ + 'title' => $this->l('API Integration (iban.pro)'), + 'icon' => 'icon-key', + ], + 'input' => [ + [ + 'type' => 'password', // Using 'password' type hides the token + 'label' => $this->l('iban.pro Access Token'), + 'name' => 'IBANTRANSFER_API_TOKEN', + 'required' => false, + 'desc' => $this->l('Enter your API access token from iban.pro to generate QR codes.'), + 'class' => 'fixed-width-xxl', + ], + ], + 'submit' => [ + 'title' => $this->l('Save'), + 'class' => 'btn btn-default pull-right', + ], + ]; + + // --- HelperForm Boilerplate --- + $helper = new HelperForm(); + $helper->module = $this; + $helper->name_controller = $this->name; + $helper->token = Tools::getAdminTokenLite('AdminModules'); + $helper->currentIndex = AdminController::$currentIndex . '&configure=' . $this->name; + $helper->default_form_language = (int)Configuration::get('PS_LANG_DEFAULT'); + $helper->title = $this->displayName; + $helper->show_toolbar = true; + $helper->toolbar_scroll = true; + $helper->submit_action = 'submitIbanTransferModule'; + + // Load current values from the database + $configFields = [ + 'IBANTRANSFER_IBAN', + 'IBANTRANSFER_RECEIVER_NAME', + 'IBANTRANSFER_RECEIVER_CODE', + 'IBANTRANSFER_DESCRIPTION_APPEND', + 'IBANTRANSFER_API_TOKEN', + 'IBANTRANSFER_OS_CREATION', + 'IBANTRANSFER_OS_FULL_PAYMENT', + 'IBANTRANSFER_OS_PARTIAL_PAYMENT' + ]; + foreach ($configFields as $field) { + $helper->fields_value[$field] = Configuration::get($field); + } + + return $helper->generateForm($fields_form); + } + + + public function hookdisplayOrderConfirmation($params) + { + $order = $params['order']; + if ($order->payment == $this->paymentMethodName) { + return $this->getTPL($params['order']); + } + } + + public function getTPL(Order $order) + { + + $address = new Address($order->id_address_invoice); + $total = $order->total_paid; + $description = $order->reference . '; оплата замовлення #' . $order->id . '; ' . $address->lastname . ' ' . $address->firstname; + $NBULink = $this->getNBUv2($total, $description); + if (trim(shell_exec('command -v qrencode'))) { + // 3. If it exists, generate the QR code. + // CRITICAL: Always use escapeshellarg() on variables passed to shell commands + // to prevent command injection vulnerabilities. + $escapedNBULink = escapeshellarg($NBULink); + $qr = shell_exec("qrencode -o - -s 4 -t SVG $escapedNBULink"); + + // 4. (Optional but recommended) Add a final check in case the command fails for other reasons. + if (empty($qr)) { + $qr = $this->context->smarty->fetch($this->local_path . 'views/templates/front/emptyQR.svg'); + } + } else { + // 5. If 'qrencode' is not found, assign the placeholder. + $qr = $this->context->smarty->fetch($this->local_path . 'views/templates/front/emptyQR.svg'); + } + $this->context->smarty->assign([ + 'reciever_name' => Configuration::get('IBANTRANSFER_RECEIVER_NAME'), + 'iban' => Configuration::get('IBANTRANSFER_IBAN'), + 'reciever_code' => Configuration::get('IBANTRANSFER_RECEIVER_CODE'), + 'description' => $description . ' ' . Configuration::get('IBANTRANSFER_DESCRIPTION_APPEND'), + 'total' => $total, + 'NBULink' => $NBULink, + 'qr' => $qr, + + ]); + + return $this->display(__FILE__, '/views/templates/front/displayOrderConfirmation.tpl'); + } + + public function getNBUv2($amount, $description) + { + + $display = ''; + $ibanSUM = sprintf('UAH%s', number_format((float) $amount, 2, '.', '')); + + $codeContentsV2 = implode("\n", array( + mb_convert_encoding('BCD', 'ASCII'), + mb_convert_encoding(sprintf('%03d', '2'), 'ASCII'), + mb_convert_encoding('2', 'ASCII'), + mb_convert_encoding('UCT', 'ASCII'), + '', + mb_convert_encoding($this->reciever_name, 'windows-1251'), + mb_convert_encoding(Configuration::get('IBANTRANSFER_IBAN'), 'ASCII'), + mb_convert_encoding($ibanSUM, 'ASCII'), + mb_convert_encoding($this->reciever_code, 'windows-1251'), + '', + '', + mb_convert_encoding($description, 'windows-1251'), + $display, + '', + )); + + return 'https://bank.gov.ua/qr/' . $this->base64url_encode($codeContentsV2); + } + + public static function base64url_encode($data) + { + // First of all you should encode $data to Base64 string + $b64 = base64_encode($data); + + // Make sure you get a valid result, otherwise, return FALSE, as the base64_encode() function do + if ($b64 === false) { + return false; + } + + // Convert Base64 to Base64URL by replacing “+” with “-” and “/” with “_” + $url = strtr($b64, '+/', '-_'); + + // Remove padding character from the end of line and return the Base64URL result + return rtrim($url, '='); + } + + + public function hookPaymentOptions($params) + { + echo Configuration::get('IBANTRANSFER_IBAN'); + if (!$this->active) { + return; + } + if (!Configuration::get('IBANTRANSFER_IBAN') || !Configuration::get('IBANTRANSFER_RECEIVER_CODE') || !Configuration::get('IBANTRANSFER_RECEIVER_NAME')) { + return; + } + + $payment_options = [ + + $this->getPaymentOption(), + + ]; + + return $payment_options; + } + + public function getPaymentOption() + { + $externalOption = new \PrestaShop\PrestaShop\Core\Payment\PaymentOption(); + $externalOption->setCallToActionText($this->l('Оплата переказом за реквізитами IBAN')) + ->setAction($this->context->link->getModuleLink($this->name, 'validation', ['methodPayment' => 'onlinePayment'], true)) + // ->setInputs([ 'onlinePayment' => true, ]) + ->setAdditionalInformation($this->context->smarty->fetch('module:' . $this->name . '/views/templates/front/payment_info.tpl')) + // ->setLogo(Media::getMediaPath(_PS_MODULE_DIR_ . $this->name . '/payment.jpg')) + ; + + return $externalOption; + } +} diff --git a/views/templates/front/displayOrderConfirmation.tpl b/views/templates/front/displayOrderConfirmation.tpl new file mode 100644 index 0000000..5d1dedd --- /dev/null +++ b/views/templates/front/displayOrderConfirmation.tpl @@ -0,0 +1,323 @@ +
+ +
+
+ account_balance +
Реквізити для оплати замовлення
+
+
+ + + + + +
+ +

Відскануйте QR-код у банківському додатку на телефоні. Усі реквізити та сума будуть + заповнені автоматично.

+
+ {$qr nofilter} +
+
+ + + {*
+
+
Найшвидший спосіб на телефоні
+

Оберіть ваш банк для автоматичного відкриття додатку з реквізитами.

+
+ +
*} + + + {*
+
+
+

+ +

+
+
+

Ці посилання призначені для швидкої оплати на смартфоні. Ви можете надіслати їх + собі на телефон.

+ +
+
+
+
+
*} + + + + +
+{*
+ + + Посилання НБУ open_in_new + + +
*} +
+ + + Посилання НБУ open_in_new + + +
+
+ +
+ +
+ Отримувач: +
+ {$reciever_name} + +
+
+
+ Код отримувача (ЄДРПОУ): +
+ {$reciever_code} + +
+
+
+ IBAN: +
+ {$iban} + +
+
+
+ Призначення платежу: +
+ + {$description} + +
+
+
+ Сума до сплати: +
+ {$total|round:2} ₴ + +
+
+
+
+ +
+
+ +{* --- CSS and JavaScript --- *} + + + + + + + \ No newline at end of file diff --git a/views/templates/front/emptyQR.svg b/views/templates/front/emptyQR.svg new file mode 100644 index 0000000..c003019 --- /dev/null +++ b/views/templates/front/emptyQR.svg @@ -0,0 +1,4 @@ + + QR-код + недоступний + \ No newline at end of file diff --git a/views/templates/front/payment_info.tpl b/views/templates/front/payment_info.tpl new file mode 100644 index 0000000..3e5bbf1 --- /dev/null +++ b/views/templates/front/payment_info.tpl @@ -0,0 +1,5 @@ + +