From 46e77ae1e7e27ad0779a59cf9bab4b543cb97847 Mon Sep 17 00:00:00 2001 From: Ruslan Mironov Date: Tue, 31 Mar 2026 14:28:08 +0300 Subject: [PATCH 1/5] t --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index beb5063..2b6340f 100644 --- a/README.md +++ b/README.md @@ -21,4 +21,5 @@ «Код проверки целостности данных»: ваш_код -Удачных платежей! \ No newline at end of file +Удачных платежей! +123 From 4a171ffc5f4e508973f9be7237d76d5da654328b Mon Sep 17 00:00:00 2001 From: Ruslan Mironov Date: Tue, 31 Mar 2026 15:58:13 +0300 Subject: [PATCH 2/5] test --- test | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 test diff --git a/test b/test new file mode 100644 index 0000000..e69de29 From 473e28824c4e76172cfa91c0422b3e7d226bafdb Mon Sep 17 00:00:00 2001 From: Ivan Sadovin Date: Mon, 6 Apr 2026 12:11:14 +0300 Subject: [PATCH 3/5] feat: add payurl and checkurl xml response --- README.md | 112 +++- modules/gateways/callback/payanyway.php | 76 +-- modules/gateways/payanyway.php | 784 ++++++++++++++++++++++-- test | 0 4 files changed, 859 insertions(+), 113 deletions(-) delete mode 100644 test diff --git a/README.md b/README.md index 2b6340f..9c377c1 100644 --- a/README.md +++ b/README.md @@ -1,25 +1,91 @@ -Инструкция по установке платженого модуля PayAnyWay для WHMCS - -Для установки платежного модуля PayAnyWay необходимо произвести следующие действия: - -1. Скопировать содержимое архива в корень сайта. - -2. Активируйте модуль PayAnyWay - -3. Измените настройки модуля PayAnyWay: - - Account Number - номер счета в платежной системе PayAnyWay. - Code of data integrity verification - Код проверки целостности данных. - Test Mode - включение тестового режима. - -4. Зайдите в ваш акаунт в платежной системе и перейдите в раздел «Счета» -> «Управление» -> «Редактировать счет» - - «HTTP метод»: GET - «Pay URL»: http://имя_вашего_сайта/modules/gateways/callback/payanyway.php - «Можно переопределять настройки в url»: Да - «Подпись формы оплаты обязательна»: Да - «Код проверки целостности данных»: ваш_код +# 🌟 Платёжный модуль PayAnyWay для WHMCS -Удачных платежей! -123 +![WHMCS](https://img.shields.io/badge/WHMCS-Gateway%20Module-0070C0?logo=php&logoColor=white) +![PHP](https://img.shields.io/badge/PHP-5.6%2B-purple) +![License](https://img.shields.io/badge/License-MIT-green) + +**[WHMCS (Web Host Manager Complete Solution)](https://www.whmcs.com/)** — это универсальная платформа для автоматизации хостингового бизнеса. Она предназначена для управления клиентами, биллингом, технической поддержкой и другими аспектами, связанными с предоставлением IT-услуг. +WHMCS предоставляет мощный функционал, который позволяет интегрировать различные панели управления, платежные системы и сервисы, делая процесс управления хостингом простым и удобным. + +> **ℹ️ Примечание по валюте** +> Данный модуль работает **только с валютой RUB (Российский рубль)**. +> Перед активацией модуля убедитесь, что в WHMCS в качестве базовой валюты установлен RUB, либо у пользователя выбран счёт в RUB. Использование других валют не поддерживается. + +> **📦 Версия модуля: 1.1.0**> + +## 🏗️ Структура модуля + +``` +└── modules + └── gateways + ├── callback + │ └── payanyway.php + └── payanyway.php +``` + +## 🚀 Установка и настройка модуля + +1. 📥 [Скачайте архив модуля](https://www.payanyway.ru/info/public/cms/whmcs.zip) и распакуйте его. +2. 📂 Скопируйте содержимое архива в корень сайта. +3. ⚙️ В списке модулей оплаты WHMCS найдите **PayAnyWay** и активируйте его. +4. ✏️ Заполните соответствующие поля в настройках способа оплаты: + +| Поле | Значение | +|------|----------| +| **Show on Order Form** | ✅ Поставить галочку | +| **Display Name** | `PayAnyWay` | +| **Account Number** | Номер расширенного счета в PayAnyWay (Moneta) | +| **Code of data integrity verification** | Код проверки целостности данных из настроек расширенного счета | +| **Test Mode** | ❌ Галочку **не** ставить | + +--- + +## 🔧 Инструкция по настройке счета PayAnyWay + +1. 📝 [Зарегистрируйтесь](https://payanyway.ru/partnerRegistration.htm) в платёжной системе PayAnyWay и заполните все необходимые данные. Дождитесь проверки аккаунта и создайте **расширенный счет**. + +2. ⚙️ Заполните настройки расширенного счета (раздел **«Мой счет» → «Управление счетами» → «Редактировать счет»**): + +| Параметр | Значение | +|----------|----------| +| **Тестовый режим** | `Нет` | +| **Check URL** | `https://your_site_name/modules/gateways/callback/payanyway.php` | +| **Pay URL** | `https://your_site_name/modules/gateways/callback/payanyway.php` | +| **HTTP method** | `GET` / `POST` | +| **Проверить Check/Pay URL** | `Нет` | +| **Можно переопределять настройки в URL** | `Да` | +| **Подпись формы оплаты обязательна** | `Да` | +| **Код проверки целостности данных** | `ваш_код` (произвольный набор символов) | +| **Success URL** | Оставить пустым | +| **Fail URL** | Оставить пустым | +| **InProgress URL** | Оставить пустым | +| **Return URL** | Оставить пустым | + +> ⚠️ **Важно!** Для кириллического домена PayURL и CheckURL должны быть указаны в кодировке [Punycode](https://2ip.ru/punycode/). + +--- + +## 🔌 Реализованные функции WHMCS + +| Функция | Назначение | +|---------|------------| +| `payanyway_MetaData()` | Возвращает метаданные модуля (название, версию API, настройки) | +| `payanyway_config()` | Определяет поля конфигурации в панели администратора | +| `payanyway_link()` | Отображает кнопку оплаты и информационное сообщение на странице счета | + +--- + +## 📖 Документация WHMCS + +Ознакомьтесь с руководством по установке и настройке [WHMCS Panel](https://help.whmcs.com/m/setup/l/1075240-configuring-your-first-payment-gateway) + +## 📚 Полезные ресурсы + +* [WHMCS: Developer Documentation](https://developers.whmcs.com/) +* [WHMCS: API Documentation](https://developers.whmcs.com/api/) +* [WHMCS: Payment Gateways](https://developers.whmcs.com/payment-gateways/) + +--- + +**✅ Модуль настроен, приятных платежей!** 💰🎉 \ No newline at end of file diff --git a/modules/gateways/callback/payanyway.php b/modules/gateways/callback/payanyway.php index 2d3e57e..ae53ae9 100644 --- a/modules/gateways/callback/payanyway.php +++ b/modules/gateways/callback/payanyway.php @@ -1,51 +1,41 @@ getMessage()); + payanyway_sendResponse('FAIL', 500); +} catch (\Exception $e) { + logTransaction($gatewayModuleName, $requestData, 'Error: ' . $e->getMessage()); + payanyway_sendResponse('FAIL', 500); +} \ No newline at end of file diff --git a/modules/gateways/payanyway.php b/modules/gateways/payanyway.php index aa70ce6..af946ea 100644 --- a/modules/gateways/payanyway.php +++ b/modules/gateways/payanyway.php @@ -1,60 +1,750 @@ array("Type" => "System", "Value"=>"PayAnyWay"), - "mnt_id" => array("FriendlyName" => "Account Number", "Type" => "text", "Size" => "20", ), - "mnt_dataintegrity_code" => array("FriendlyName" => "Code of data integrity verification", "Type" => "text", "Size" => "20", ), - "mnt_test_mode" => array("FriendlyName" => "Test Mode", "Type" => "yesno" ), +const PAYANYWAY_PAYMENT_GATEWAY_MODULE_VERSION = 'PAYANYWAY_1.1.0'; +const PAYANYWAY_API_URL_PROD = 'https://www.payanyway.ru/assistant.htm'; +const PAYANYWAY_API_URL_DEMO = 'https://demo.moneta.ru/assistant.htm'; +const PAYANYWAY_SUPPORTED_CURRENCIES = ['RUB']; +const PAYANYWAY_DESCRIPTION_MAX_LENGTH = 500; +const PAYANYWAY_INVENTORY_ITEM_NAME_MAX_LENGTH = 128; +const PAYANYWAY_INVENTORY_ITEM_DEFAULT_PAYMENT_METHOD = 'full_payment'; +const PAYANYWAY_INVENTORY_ITEM_DEFAULT_PAYMENT_OBJECT = 'service'; +const PAYANYWAY_INVENTORY_ITEM_DEFAULT_QUANTITY = 1; +const PAYANYWAY_INVENTORY_ITEM_DEFAULT_MEASURE = 'unit'; +const PAYANYWAY_VAT_TAG_NONE = '1105'; // none +const PAYANYWAY_PAYMENT_FEE = 0.0; + +if (!defined('WHMCS')) { + die('This file cannot be accessed directly'); +} + +/** + * Module metadata for WHMCS gateway registration. + * + * @return array + */ +function payanyway_MetaData() +{ + return [ + 'DisplayName' => 'PayAnyWay', + 'APIVersion' => '1.1', + 'DisableLocalCreditCardInput' => true, + 'TokenisedStorage' => false, + ]; +} + +/** + * Define gateway configuration options. + * + * The fields you define here determine the configuration options that are + * presented to administrator users when activating and configuring your + * payment gateway module for use. + * + * @return array + */ +function payanyway_config() +{ + return [ + 'FriendlyName' => [ + 'Type' => 'System', + 'Value' => 'PayAnyWay', + ], + 'mnt_id' => [ + 'FriendlyName' => 'Account Number', + 'Type' => 'text', + 'Size' => '20', + 'Default' => '', + 'Description' => 'Enter your account number provided by PayAnyWay ', + ], + 'mnt_dataintegrity_code' => [ + 'FriendlyName' => 'Code of data integrity verification', + 'Type' => 'text', + 'Size' => '20', + 'Default' => '', + 'Description' => 'Enter your integrity code provided by PayAnyWay', + ], + 'mnt_test_mode' => [ + 'FriendlyName' => 'Test Mode', + 'Type' => 'yesno', + 'Description' => 'Tick to enable test mode', + ], + ]; +} + +/** + * Generate a PayAnyWay payment link for a WHMCS invoice. + * + * Creates a payment preference via PayAnyWay and returns an + * anchor tag () pointing to the hosted checkout page. WHMCS will render + * this HTML inside the invoice payment section. + * + * @param array $params Standard WHMCS gateway params array. + * See: https://developers.whmcs.com/payment-gateways/third-party-gateway/ + * @return string HTML output to display on the invoice page. + */ +function payanyway_link($params) +{ + try { + // Gateway Configuration Parameters + $mntId = payanyway_getPayAnyWayMntIdOrFail($params); + $testMode = !empty($params['mnt_test_mode']) ? '1' : '0'; + $integrityCode = payanyway_getPayAnyWayIntegrityCodeOrFail($params); + + // Invoice Parameters + $invoiceId = payanyway_getWhmcsInvoiceIdOrFail($params); + $amount = payanyway_getWhmcsInvoiceAmountOrFail($params); + $formatAmount = payanyway_formatAmount($amount); + $currency = payanyway_getWhmcsInvoiceCurrencyOrFail($params); + } catch (\PayAnyWayException $e) { + return '

Error: ' + . htmlspecialchars($e->getMessage(), ENT_QUOTES | ENT_HTML5, 'UTF-8') . '

'; + } + + // System Parameters + $langPayNow = payanyway_getOptionalStringWithDefault($params, 'langpaynow', 'Pay Now'); + $whmcsVersionNumber = payanyway_getOptionalString($params, 'whmcsVersion'); + $whmcsVersion = isset($whmcsVersionNumber) ? 'WHMCS_' . $whmcsVersionNumber : 'WHMCS_unknown_version'; + + $url = ('1' === $testMode) ? PAYANYWAY_API_URL_DEMO : PAYANYWAY_API_URL_PROD; + + $subscriberId = payanyway_getSubscriberId($params); + $signature = md5($mntId . $invoiceId . $formatAmount . $currency . $subscriberId . $testMode . $integrityCode); + + $postFields = [ + 'MNT_ID' => $mntId, + 'MNT_TRANSACTION_ID' => $invoiceId, + 'MNT_AMOUNT' => $formatAmount, + 'MNT_CURRENCY_CODE' => $currency, + 'MNT_TEST_MODE' => $testMode, + 'MNT_DESCRIPTION' => payanyway_getDescription($invoiceId, $params), + 'MNT_SUBSCRIBER_ID' => $subscriberId, + 'MNT_SIGNATURE' => $signature, + 'MNT_CMS' => $whmcsVersion . '|' . PAYANYWAY_PAYMENT_GATEWAY_MODULE_VERSION, + ]; + + $htmlOutput = '
'; + foreach ($postFields as $key => $value) { + $value = htmlspecialchars($value, ENT_QUOTES | ENT_HTML5, 'UTF-8'); + $htmlOutput .= ''; + } + + $htmlOutput .= ''; + $htmlOutput .= '
'; + + return $htmlOutput; +} + +/** + * WHMCS PayAnyWay Payment Gateway Helper Functions + */ + +/** + * @param array $params + * @return string + * @throws PayAnyWayException + */ +function payanyway_getPayAnyWayMntIdOrFail($params) +{ + $mntId = payanyway_getOptionalString($params, 'mnt_id'); + if (null === $mntId) { + throw new \PayAnyWayException( + 'PayAnyWay module is not configured properly. ' + . 'Please set Account Number in WHMCS Admin → Payment Gateways.' + ); + } + + return $mntId; +} + +/** + * @param array $params + * @return string + * @throws PayAnyWayException + */ +function payanyway_getPayAnyWayIntegrityCodeOrFail($params) +{ + $integrityCode = payanyway_getOptionalString($params, 'mnt_dataintegrity_code'); + if (null === $integrityCode) { + throw new \PayAnyWayException( + 'PayAnyWay module is not configured properly. ' + . 'Please set Code of Data Integrity Verification in WHMCS Admin → Payment Gateways.' + ); + } + + return $integrityCode; +} + +/** + * @param array $params + * @return int + * @throws PayAnyWayException + */ +function payanyway_getWhmcsInvoiceIdOrFail($params) +{ + $invoiceId = payanyway_getOptionalInt($params, 'invoiceid'); + if (null === $invoiceId) { + throw new \PayAnyWayException('Missing or invalid invoice ID (integer required)'); + } + + return $invoiceId; +} + +/** + * @param array $params + * @return float + * @throws PayAnyWayException + */ +function payanyway_getWhmcsInvoiceAmountOrFail($params) +{ + $amount = payanyway_getOptionalFloat($params, 'amount'); + if (null === $amount) { + throw new \PayAnyWayException('Missing or invalid invoice amount (float required).'); + } + + return $amount; +} + +/** + * @param array $params + * @return non-empty-string + * @throws PayAnyWayException + */ +function payanyway_getWhmcsInvoiceCurrencyOrFail($params) +{ + $currency = payanyway_getOptionalString($params, 'currency'); + if (null === $currency) { + $supported = implode(', ', PAYANYWAY_SUPPORTED_CURRENCIES); + throw new \PayAnyWayException( + 'Missing currency code. Module supports only: ' . $supported + ); + } + + $currency = strtoupper($currency); + if (!in_array($currency, PAYANYWAY_SUPPORTED_CURRENCIES, true)) { + $supported = implode(', ', PAYANYWAY_SUPPORTED_CURRENCIES); + throw new \PayAnyWayException( + "Unsupported currency code: '{$currency}'. Module supports only: {$supported}" + ); + } + + return $currency; +} + +/** + * @param array $params + * @return string + * @throws PayAnyWayException + */ +function payanyway_getWhmcsVariableOrFail($params, $name) +{ + $var = payanyway_getOptionalString($params, $name); + if (null === $var) { + throw new \PayAnyWayException(sprintf('Whmcs variable $params[\'%s\'] is not set.', $name)); + } + + return $var; +} + +/** + * @param array $array + * @param string $key + * @return int|null + */ +function payanyway_getOptionalInt($array, $key) +{ + if (!isset($array[$key]) || !is_numeric($array[$key])) { + return null; + } + + return (int)$array[$key]; +} + +/** + * @param array $array + * @param string $key + * @return float|null + */ +function payanyway_getOptionalFloat($array, $key) +{ + if (!isset($array[$key]) || !is_numeric($array[$key])) { + return null; + } + + return (float)$array[$key]; +} + +/** + * @param array $array + * @param string $key + * @return float + * @throws PayAnyWayException + */ +function payanyway_getRequiredFloat($array, $key) +{ + if (!isset($array[$key])) { + throw new \PayAnyWayException('Missing required field ' . $key); + } + + $value = $array[$key]; + if (!is_numeric($value)) { + throw new \PayAnyWayException( + sprintf('Field "%s" must be a number, %s given', $key, gettype($value)) + ); + } + + return (float)$value; +} + +/** + * @param array $array + * @param string $key + * @return string + * @throws PayAnyWayException + */ +function payanyway_getRequiredString($array, $key) +{ + if (!isset($array[$key])) { + throw new \PayAnyWayException('Missing required field ' . $key); + } + + $value = $array[$key]; + if (!is_string($value)) { + throw new \PayAnyWayException( + sprintf('Field "%s" must be a string, %s given', $key, gettype($value)) + ); + } + + if ('' === $value) { + throw new \PayAnyWayException(sprintf('Field "%s" must be a non-empty string', $key)); + } + + return $value; +} + +/** + * @param array $array + * @param string $key + * @return string|null + */ +function payanyway_getOptionalString($array, $key) +{ + if (!isset($array[$key])) { + return null; + } + + $value = $array[$key]; + + return (is_string($value) && ('' !== trim($value))) ? $value : null; +} + +/** + * @param array $params + * @param non-empty-string $key + * @param string $default + * @return string + */ +function payanyway_getOptionalStringWithDefault($params, $key, $default = '') +{ + $value = payanyway_getOptionalString($params, $key); + + return (null !== $value) ? $value : $default; +} + +/** + * @param float $amount + * @return non-empty-string + */ +function payanyway_formatAmount($amount) +{ + return number_format($amount, 2, '.', ''); +} + +/** + * @param array $params + * @return string + */ +function payanyway_getSubscriberId($params) +{ + $email = isset($params['clientdetails']['email']) + ? filter_var($params['clientdetails']['email'], FILTER_SANITIZE_EMAIL) + : ''; + $phoneNumber = isset($params['clientdetails']['phonenumber']) + ? preg_replace('/[^0-9+]/', '', $params['clientdetails']['phonenumber']) + : ''; + + return $email ?: $phoneNumber; +} + +/** + * @param int $invoiceId + * @param array $params + * @return non-empty-string + */ +function payanyway_getDescription($invoiceId, $params) +{ + $clientFirstName = isset($params['clientdetails']['firstname']) ? $params['clientdetails']['firstname'] : ''; + $clientLastName = isset($params['clientdetails']['lastname']) ? $params['clientdetails']['lastname'] : ''; + $clientName = trim($clientFirstName . ' ' . $clientLastName); + + $description = "Payment for Invoice #{$invoiceId}"; + + if ('' !== $clientName) { + $description .= " from {$clientName}"; + } + + $originalDescription = isset($params['description']) ? trim($params['description']) : ''; + if ('' !== $originalDescription) { + $description .= ': ' . $originalDescription; + } + + if (mb_strlen($description, 'UTF-8') > PAYANYWAY_DESCRIPTION_MAX_LENGTH) { + $description = mb_substr($description, 0, 497, 'UTF-8') . '...'; + } + + return $description; +} + +/** + * @param array $requestData + * @return array + * @throws PayAnyWayException + */ +function payanyway_getCallbackData($requestData) +{ + return [ + 'MNT_COMMAND' => payanyway_getOptionalStringWithDefault($requestData, 'MNT_COMMAND'), + 'MNT_ID' => payanyway_getRequiredString($requestData, 'MNT_ID'), + 'MNT_TRANSACTION_ID' => payanyway_getRequiredString($requestData, 'MNT_TRANSACTION_ID'), + 'MNT_OPERATION_ID' => payanyway_getOptionalStringWithDefault($requestData, 'MNT_OPERATION_ID'), + 'MNT_AMOUNT' => payanyway_getOptionalStringWithDefault($requestData, 'MNT_AMOUNT'), + 'MNT_CURRENCY_CODE' => payanyway_getRequiredString($requestData, 'MNT_CURRENCY_CODE'), + 'MNT_SUBSCRIBER_ID' => payanyway_getOptionalStringWithDefault($requestData, 'MNT_SUBSCRIBER_ID'), + 'MNT_TEST_MODE' => payanyway_getRequiredString($requestData, 'MNT_TEST_MODE'), + 'MNT_SIGNATURE' => payanyway_getRequiredString($requestData, 'MNT_SIGNATURE'), + ]; +} + +/** + * @param array $callbackData + * @param non-empty-string $gatewayName + * @param non-empty-string $integrityCode + * @return void + * @throws DOMException + */ +function payanyway_handleCheckCallback($callbackData, $gatewayName, $integrityCode) +{ + if (!payanyway_checkSignature($callbackData, $integrityCode, $callbackData['MNT_COMMAND'])) { + payanyway_sendResponse('FAIL', 500); + } + + $rawInvoiceId = (int)$callbackData['MNT_TRANSACTION_ID']; + $invoiceId = checkCbInvoiceID($rawInvoiceId, $gatewayName); + $invoiceDetails = payanyway_getInvoiceDetails($invoiceId); + if (null === $invoiceDetails) { + payanyway_sendResponse('FAIL', 500); + } + + $invoiceItems = payanyway_getInvoiceItems($invoiceDetails); + $invoiceTotal = payanyway_formatAmount($invoiceDetails['total']); + $invoicePaymentStatus = $invoiceDetails['status']; + + $xmlData = [ + 'MNT_ID' => $callbackData['MNT_ID'], + 'MNT_TRANSACTION_ID' => $callbackData['MNT_TRANSACTION_ID'], + 'MNT_AMOUNT' => $invoiceTotal, + 'MNT_CURRENCY_CODE' => $callbackData['MNT_CURRENCY_CODE'], + 'inventory' => payanyway_getInventoryJson($invoiceItems) ?: null, + 'client' => $callbackData['MNT_SUBSCRIBER_ID'], + 'sno' => null, + 'delivery' => $invoiceTotal, + ]; + + switch (true) { + case empty($callbackData['MNT_AMOUNT']): + $xmlData['MNT_RESULT_CODE'] = 100; + $xmlData['MNT_DESCRIPTION'] = 'Invoice created, but amount not set'; + break; + case ('Paid' === $invoicePaymentStatus): + $xmlData['MNT_RESULT_CODE'] = 200; + $xmlData['MNT_DESCRIPTION'] = 'Invoice Paid'; + break; + default: + $xmlData['MNT_RESULT_CODE'] = 402; + $xmlData['MNT_DESCRIPTION'] = 'Invoice Unpaid'; + } + + $xmlData['MNT_SIGNATURE'] = md5( + $xmlData['MNT_RESULT_CODE'] + . $xmlData['MNT_ID'] + . $xmlData['MNT_TRANSACTION_ID'] + . $integrityCode ); - return $configarray; + + payanyway_sendResponse(payanyway_buildXMLResponse($xmlData), 200); } -function payanyway_link($params) { +/** + * @param array $callbackData + * @param non-empty-string $gatewayName + * @param non-empty-string $integrityCode + * @param non-empty-string $gatewayModuleName + * @return void + * @throws DOMException + */ +function payanyway_handlePayCallback($callbackData, $gatewayName, $integrityCode, $gatewayModuleName) +{ + if (!payanyway_checkSignature($callbackData, $integrityCode)) { + payanyway_sendResponse('FAIL', 500); + } - # Gateway Specific Variables - $gatewayusername = $params['mnt_id']; - $gatewaytestmode = intval($params['mnt_test_mode']); - $gatewaykey = $params['mnt_dataintegrity_code']; + $rawInvoiceId = $callbackData['MNT_TRANSACTION_ID']; + $invoiceId = checkCbInvoiceID($rawInvoiceId, $gatewayName); + $paidAmount = $callbackData['MNT_AMOUNT']; - # Invoice Variables - $invoiceid = $params['invoiceid']; - $description = $params["description"]; - $amount = $params['amount']; # Format: ##.## - $currency = $params['currency']; # Currency Code + $invoiceDetails = payanyway_getInvoiceDetailsOrFail($invoiceId); + if (null === $invoiceDetails) { + payanyway_sendResponse('FAIL', 500); + } - # Client Variables - $firstname = $params['clientdetails']['firstname']; - $lastname = $params['clientdetails']['lastname']; - $email = $params['clientdetails']['email']; - $address1 = $params['clientdetails']['address1']; - $address2 = $params['clientdetails']['address2']; - $city = $params['clientdetails']['city']; - $state = $params['clientdetails']['state']; - $postcode = $params['clientdetails']['postcode']; - $country = $params['clientdetails']['country']; - $phone = $params['clientdetails']['phonenumber']; + $invoiceItems = payanyway_getInvoiceItems($invoiceDetails); + $invoicePaymentStatus = $invoiceDetails['status']; - # System Variables - $companyname = $params['companyname']; - $systemurl = $params['systemurl']; - $currency = $params['currency']; + $resultCode = 200; + $xmlData = [ + 'MNT_ID' => $callbackData['MNT_ID'], + 'MNT_TRANSACTION_ID' => $callbackData['MNT_TRANSACTION_ID'], + 'MNT_RESULT_CODE' => $resultCode, + 'MNT_SIGNATURE' => md5( + $resultCode . $callbackData['MNT_ID'] . $callbackData['MNT_TRANSACTION_ID'] . $integrityCode + ), + 'MNT_AMOUNT' => $paidAmount, + 'MNT_CURRENCY_CODE' => $callbackData['MNT_CURRENCY_CODE'], + 'MNT_DESCRIPTION' => ('Paid' !== $invoicePaymentStatus) + ? 'Invoice success paid' + : 'Invoice already paid', + 'inventory' => payanyway_getInventoryJson($invoiceItems), + 'client' => $callbackData['MNT_SUBSCRIBER_ID'], + 'sno' => null, + 'delivery' => $paidAmount, + ]; - # Enter your code submit to the gateway... - $signature = md5($gatewayusername . $invoiceid . $amount . $currency . $gatewaytestmode . $gatewaykey); + if ('Paid' !== $invoicePaymentStatus) { + $transactionId = $callbackData['MNT_OPERATION_ID']; + checkCbTransID($transactionId); // if $transactionId exist, script die() + if (!payanyway_existInvoiceTransaction($invoiceId, $transactionId)) { + addInvoicePayment($invoiceId, $transactionId, $paidAmount, PAYANYWAY_PAYMENT_FEE, $gatewayModuleName); + logTransaction($gatewayName, $callbackData, 'Success'); + } + } - $code = '
- - - - - - - - -
'; - - return $code; + payanyway_sendResponse(payanyway_buildXMLResponse($xmlData), 200); } -?> \ No newline at end of file +/** + * @param array $callbackData + * @param string $integrityCode + * @param non-empty-string|null $command + * @return bool + */ +function payanyway_checkSignature($callbackData, $integrityCode, $command = null) +{ + $baseFields = [ + 'MNT_ID', + 'MNT_TRANSACTION_ID', + 'MNT_OPERATION_ID', + 'MNT_AMOUNT', + 'MNT_CURRENCY_CODE', + 'MNT_SUBSCRIBER_ID', + 'MNT_TEST_MODE', + ]; + + $fields = ('CHECK' === $command) ? array_merge(['MNT_COMMAND'], $baseFields) : $baseFields; + + $signatureString = array_reduce($fields, static function ($carry, $field) use ($callbackData) { + return $carry . $callbackData[$field]; + }, '') . $integrityCode; + + return hash_equals(md5($signatureString), $callbackData['MNT_SIGNATURE']); +} + +/** + * @see https://developers.whmcs.com/api-reference/getinvoice/ + * @param int $invoiceId + * @return array|null + */ +function payanyway_getInvoiceDetails($invoiceId) +{ + $command = 'GetInvoice'; + $postData = [ + 'invoiceid' => $invoiceId, + ]; + + $results = localAPI($command, $postData); + if ($results['result'] !== 'success') { + return null; + } + + return $results; +} + +/** + * @param array $invoiceDetails + * @return array|null + */ +function payanyway_getInvoiceItems($invoiceDetails) +{ + return (isset($invoiceDetails['items']['item']) && is_array($invoiceDetails['items']['item'])) + ? $invoiceDetails['items']['item'] + : null; +} + +/** + * @see https://developers.whmcs.com/api-reference/gettransactions/ + * @param int $invoiceId + * @param non-empty-string $transactionId + * @return bool + */ +function payanyway_existInvoiceTransaction($invoiceId, $transactionId) +{ + $results = localAPI('GetTransactions', [ + 'invoiceid' => $invoiceId, + 'transid' => $transactionId, + ]); + + if ($results['result'] !== 'success') { + return false; + } + + $transactions = isset($results['transactions']['transaction']) ? $results['transactions']['transaction'] : []; + if ([] === $transactions) { + return false; + } + + foreach ($transactions as $transaction) { + if (isset($transaction['transid']) && ($transaction['transid'] === $transactionId)) { + return true; + } + } + + return false; +} + +/** + * @param array $items + * @return false|string + */ +function payanyway_getInventoryJson($items) +{ + $inventory = []; + foreach ($items as $item) { + $description = html_entity_decode($item['description'], ENT_QUOTES | ENT_HTML5, 'UTF-8'); + $positionName = trim(preg_replace('/[^\p{L}\p{N}\s\-]/u', '', $description)); + if (empty($positionName)) { + $positionName = 'Service'; + } + + $inventory[] = [ + 'name' => mb_substr($positionName, 0, PAYANYWAY_INVENTORY_ITEM_NAME_MAX_LENGTH, 'UTF-8'), + 'price' => (float)payanyway_formatAmount($item['amount']), + 'quantity' => PAYANYWAY_INVENTORY_ITEM_DEFAULT_QUANTITY, + 'vatTag' => PAYANYWAY_VAT_TAG_NONE, + 'pm' => PAYANYWAY_INVENTORY_ITEM_DEFAULT_PAYMENT_METHOD, + 'po' => PAYANYWAY_INVENTORY_ITEM_DEFAULT_PAYMENT_OBJECT, + 'measure' => PAYANYWAY_INVENTORY_ITEM_DEFAULT_MEASURE, + ]; + } + + return json_encode($inventory, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); +} + +/** + * @param array $data + * @return false|string + * @throws DOMException + */ +function payanyway_buildXMLResponse($data) +{ + $dom = new DOMDocument('1.0', 'UTF-8'); + $dom->formatOutput = false; + + $root = $dom->createElement('MNT_RESPONSE'); + $dom->appendChild($root); + + $requiredFields = [ + 'MNT_ID', + 'MNT_TRANSACTION_ID', + 'MNT_RESULT_CODE', + 'MNT_DESCRIPTION', + 'MNT_AMOUNT', + 'MNT_CURRENCY_CODE', + 'MNT_SIGNATURE', + ]; + + foreach ($requiredFields as $field) { + if (isset($data[$field])) { + $element = $dom->createElement($field, $data[$field]); + $root->appendChild($element); + } + } + + $attributes = $dom->createElement('MNT_ATTRIBUTES'); + $root->appendChild($attributes); + + $addAttribute = static function ($key, $value) use ($dom, $attributes) { + if (!empty($value)) { + $attrElement = $dom->createElement('ATTRIBUTE'); + $keyElement = $dom->createElement('KEY', $key); + $valueElement = $dom->createElement('VALUE', $value); + + $attrElement->appendChild($keyElement); + $attrElement->appendChild($valueElement); + $attributes->appendChild($attrElement); + } + }; + + $addAttribute('INVENTORY', json_encode($data['inventory'], JSON_UNESCAPED_UNICODE)); + $addAttribute('CLIENT', $data['client']); + + if (!empty($data['sno'])) { + $addAttribute('SNO', $data['sno']); + } + + if (!empty($data['delivery'])) { + $addAttribute('DELIVERY', $data['delivery']); + } + + return $dom->saveXML(); +} + +/** + * @param string $response + * @param int $statusCode + * @return void + */ +function payanyway_sendResponse($response, $statusCode) +{ + http_response_code($statusCode); + + if (strpos($response, ' Date: Mon, 6 Apr 2026 12:17:41 +0300 Subject: [PATCH 4/5] docs: remove extra '>' character from version line --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9c377c1..c6b650f 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ WHMCS предоставляет мощный функционал, которы > Данный модуль работает **только с валютой RUB (Российский рубль)**. > Перед активацией модуля убедитесь, что в WHMCS в качестве базовой валюты установлен RUB, либо у пользователя выбран счёт в RUB. Использование других валют не поддерживается. -> **📦 Версия модуля: 1.1.0**> +> **📦 Версия модуля: 1.1.0** ## 🏗️ Структура модуля From 7a17770f7965beaff8c38274a1ba6b9bbeca04c7 Mon Sep 17 00:00:00 2001 From: Ivan Sadovin Date: Wed, 6 May 2026 16:08:34 +0300 Subject: [PATCH 5/5] feat: add PHP 5.6 compatibility --- README.md | 56 +- modules/gateways/callback/payanyway.php | 22 +- modules/gateways/payanyway.php | 653 +++++++++++++----------- 3 files changed, 399 insertions(+), 332 deletions(-) diff --git a/README.md b/README.md index c6b650f..54df9ab 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,5 @@ # 🌟 Платёжный модуль PayAnyWay для WHMCS - ![WHMCS](https://img.shields.io/badge/WHMCS-Gateway%20Module-0070C0?logo=php&logoColor=white) ![PHP](https://img.shields.io/badge/PHP-5.6%2B-purple) ![License](https://img.shields.io/badge/License-MIT-green) @@ -12,32 +11,33 @@ WHMCS предоставляет мощный функционал, которы > Данный модуль работает **только с валютой RUB (Российский рубль)**. > Перед активацией модуля убедитесь, что в WHMCS в качестве базовой валюты установлен RUB, либо у пользователя выбран счёт в RUB. Использование других валют не поддерживается. -> **📦 Версия модуля: 1.1.0** +> **📦 Версия модуля: 1.0.0** ## 🏗️ Структура модуля ``` -└── modules - └── gateways - ├── callback - │ └── payanyway.php - └── payanyway.php +├── modules +│ └── gateways +│ ├── callback +│ │ └── payanyway.php +│ └── payanyway.php +└── README.md ``` ## 🚀 Установка и настройка модуля -1. 📥 [Скачайте архив модуля](https://www.payanyway.ru/info/public/cms/whmcs.zip) и распакуйте его. +1. 📥 [Скачайте архив модуля](https://git.pub.moneta.ru/CMS/WHMCS/releases) и распакуйте его. 2. 📂 Скопируйте содержимое архива в корень сайта. 3. ⚙️ В списке модулей оплаты WHMCS найдите **PayAnyWay** и активируйте его. 4. ✏️ Заполните соответствующие поля в настройках способа оплаты: -| Поле | Значение | -|------|----------| -| **Show on Order Form** | ✅ Поставить галочку | -| **Display Name** | `PayAnyWay` | -| **Account Number** | Номер расширенного счета в PayAnyWay (Moneta) | +| Поле | Значение | +|-----------------------------------------|----------------------------------------------------------------| +| **Show on Order Form** | ✅ Поставить галочку | +| **Display Name** | `PayAnyWay` | +| **Account Number** | Номер расширенного счета в PayAnyWay (Moneta) | | **Code of data integrity verification** | Код проверки целостности данных из настроек расширенного счета | -| **Test Mode** | ❌ Галочку **не** ставить | +| **Test Mode** | ❌ Галочку **не** ставить | --- @@ -47,20 +47,20 @@ WHMCS предоставляет мощный функционал, которы 2. ⚙️ Заполните настройки расширенного счета (раздел **«Мой счет» → «Управление счетами» → «Редактировать счет»**): -| Параметр | Значение | -|----------|----------| -| **Тестовый режим** | `Нет` | -| **Check URL** | `https://your_site_name/modules/gateways/callback/payanyway.php` | -| **Pay URL** | `https://your_site_name/modules/gateways/callback/payanyway.php` | -| **HTTP method** | `GET` / `POST` | -| **Проверить Check/Pay URL** | `Нет` | -| **Можно переопределять настройки в URL** | `Да` | -| **Подпись формы оплаты обязательна** | `Да` | -| **Код проверки целостности данных** | `ваш_код` (произвольный набор символов) | -| **Success URL** | Оставить пустым | -| **Fail URL** | Оставить пустым | -| **InProgress URL** | Оставить пустым | -| **Return URL** | Оставить пустым | +| Параметр | Значение | +|------------------------------------------|------------------------------------------------------------------| +| **Тестовый режим** | `Нет` | +| **Check URL** | `https://your_site_name/modules/gateways/callback/payanyway.php` | +| **Pay URL** | `https://your_site_name/modules/gateways/callback/payanyway.php` | +| **HTTP method** | `GET` / `POST` | +| **Проверить Check/Pay URL** | `Нет` | +| **Можно переопределять настройки в URL** | `Да` | +| **Подпись формы оплаты обязательна** | `Да` | +| **Код проверки целостности данных** | `ваш_код` (произвольный набор символов) | +| **Success URL** | Оставить пустым | +| **Fail URL** | Оставить пустым | +| **InProgress URL** | Оставить пустым | +| **Return URL** | Оставить пустым | > ⚠️ **Важно!** Для кириллического домена PayURL и CheckURL должны быть указаны в кодировке [Punycode](https://2ip.ru/punycode/). diff --git a/modules/gateways/callback/payanyway.php b/modules/gateways/callback/payanyway.php index ae53ae9..43269ed 100644 --- a/modules/gateways/callback/payanyway.php +++ b/modules/gateways/callback/payanyway.php @@ -16,26 +16,20 @@ require_once __DIR__ . '/../../../includes/invoicefunctions.php'; require_once __DIR__ . '/../payanyway.php'; $requestData = ($_SERVER['REQUEST_METHOD'] === 'POST') ? $_POST : $_GET; +$gatewayModuleName = 'payanyway'; try { - $gatewayModuleName = 'payanyway'; $gatewayParams = getGatewayVariables($gatewayModuleName); - if (!$gatewayParams['type']) { - payanyway_sendResponse('FAIL', 500); // Module Not Activated + if (!$gatewayParams || !isset($gatewayParams['type'])) { + payanyway_sendResponse('FAIL'); } - $gatewayName = payanyway_getWhmcsVariableOrFail($gatewayParams, 'name'); - $integrityCode = payanyway_getPayAnyWayIntegrityCodeOrFail($gatewayParams); - $callbackData = payanyway_getCallbackData($requestData); - ('CHECK' === $callbackData['MNT_COMMAND']) - ? payanyway_handleCheckCallback($callbackData, $gatewayName, $integrityCode) - : payanyway_handlePayCallback($callbackData, $gatewayName, $integrityCode, $gatewayModuleName); -} catch (\PayAnyWayException $e) { - logTransaction($gatewayModuleName, $requestData, 'Error: ' . $e->getMessage()); - payanyway_sendResponse('FAIL', 500); + (isset($callbackData['MNT_COMMAND'])) && ('CHECK' === $callbackData['MNT_COMMAND']) + ? payanyway_handleCheckCallback($callbackData, $gatewayParams) + : payanyway_handlePayCallback($callbackData, $gatewayParams, $gatewayModuleName); } catch (\Exception $e) { logTransaction($gatewayModuleName, $requestData, 'Error: ' . $e->getMessage()); - payanyway_sendResponse('FAIL', 500); -} \ No newline at end of file + payanyway_sendResponse('FAIL'); +} diff --git a/modules/gateways/payanyway.php b/modules/gateways/payanyway.php index af946ea..afeb153 100644 --- a/modules/gateways/payanyway.php +++ b/modules/gateways/payanyway.php @@ -1,4 +1,5 @@ */ function payanyway_MetaData() { @@ -46,7 +54,7 @@ function payanyway_MetaData() * presented to administrator users when activating and configuring your * payment gateway module for use. * - * @return array + * @return array */ function payanyway_config() { @@ -84,11 +92,11 @@ function payanyway_config() * anchor tag (
) pointing to the hosted checkout page. WHMCS will render * this HTML inside the invoice payment section. * - * @param array $params Standard WHMCS gateway params array. - * See: https://developers.whmcs.com/payment-gateways/third-party-gateway/ + * @param array $params Standard WHMCS gateway params array. + * See: https://developers.whmcs.com/payment-gateways/third-party-gateway/ * @return string HTML output to display on the invoice page. */ -function payanyway_link($params) +function payanyway_link(array $params) { try { // Gateway Configuration Parameters @@ -101,43 +109,42 @@ function payanyway_link($params) $amount = payanyway_getWhmcsInvoiceAmountOrFail($params); $formatAmount = payanyway_formatAmount($amount); $currency = payanyway_getWhmcsInvoiceCurrencyOrFail($params); - } catch (\PayAnyWayException $e) { + + // System Parameters + $langPayNow = payanyway_getOptionalStringWithDefault($params, 'langpaynow', 'Pay Now'); + $whmcsVersion = payanyway_getOptionalString($params, 'whmcsVersion'); + + $url = ('1' === $testMode) ? PAYMENT_URL_DEMO : PAYMENT_URL_PROD; + + $subscriberId = payanyway_getSubscriberId($params); + $signature = md5($mntId . $invoiceId . $formatAmount . $currency . $subscriberId . $testMode . $integrityCode); + + $postFields = array( + 'MNT_ID' => $mntId, + 'MNT_TRANSACTION_ID' => payanyway_createTransactionId($invoiceId), + 'MNT_AMOUNT' => $formatAmount, + 'MNT_CURRENCY_CODE' => $currency, + 'MNT_TEST_MODE' => '0', + 'MNT_DESCRIPTION' => payanyway_getDescription($invoiceId, $params), + 'MNT_SUBSCRIBER_ID' => $subscriberId, + 'MNT_SIGNATURE' => $signature, + 'MNT_CMS' => payanyway_getCmsModuleVersion($whmcsVersion), + ); + + $htmlOutput = '
'; + foreach ($postFields as $key => $value) { + $value = htmlspecialchars($value, ENT_QUOTES | ENT_HTML5, 'UTF-8'); + $htmlOutput .= ''; + } + + $htmlOutput .= ''; + $htmlOutput .= '
'; + + return $htmlOutput; + } catch (\Exception $e) { return '

Error: ' . htmlspecialchars($e->getMessage(), ENT_QUOTES | ENT_HTML5, 'UTF-8') . '

'; } - - // System Parameters - $langPayNow = payanyway_getOptionalStringWithDefault($params, 'langpaynow', 'Pay Now'); - $whmcsVersionNumber = payanyway_getOptionalString($params, 'whmcsVersion'); - $whmcsVersion = isset($whmcsVersionNumber) ? 'WHMCS_' . $whmcsVersionNumber : 'WHMCS_unknown_version'; - - $url = ('1' === $testMode) ? PAYANYWAY_API_URL_DEMO : PAYANYWAY_API_URL_PROD; - - $subscriberId = payanyway_getSubscriberId($params); - $signature = md5($mntId . $invoiceId . $formatAmount . $currency . $subscriberId . $testMode . $integrityCode); - - $postFields = [ - 'MNT_ID' => $mntId, - 'MNT_TRANSACTION_ID' => $invoiceId, - 'MNT_AMOUNT' => $formatAmount, - 'MNT_CURRENCY_CODE' => $currency, - 'MNT_TEST_MODE' => $testMode, - 'MNT_DESCRIPTION' => payanyway_getDescription($invoiceId, $params), - 'MNT_SUBSCRIBER_ID' => $subscriberId, - 'MNT_SIGNATURE' => $signature, - 'MNT_CMS' => $whmcsVersion . '|' . PAYANYWAY_PAYMENT_GATEWAY_MODULE_VERSION, - ]; - - $htmlOutput = '
'; - foreach ($postFields as $key => $value) { - $value = htmlspecialchars($value, ENT_QUOTES | ENT_HTML5, 'UTF-8'); - $htmlOutput .= ''; - } - - $htmlOutput .= ''; - $htmlOutput .= '
'; - - return $htmlOutput; } /** @@ -145,15 +152,15 @@ function payanyway_link($params) */ /** - * @param array $params - * @return string - * @throws PayAnyWayException + * @param array $data + * @return int + * @throws \InvalidArgumentException */ -function payanyway_getPayAnyWayMntIdOrFail($params) +function payanyway_getPayAnyWayMntIdOrFail(array $data) { - $mntId = payanyway_getOptionalString($params, 'mnt_id'); + $mntId = payanyway_getOptionalInt($data, 'mnt_id'); if (null === $mntId) { - throw new \PayAnyWayException( + throw new \InvalidArgumentException( 'PayAnyWay module is not configured properly. ' . 'Please set Account Number in WHMCS Admin → Payment Gateways.' ); @@ -163,15 +170,15 @@ function payanyway_getPayAnyWayMntIdOrFail($params) } /** - * @param array $params + * @param array $data * @return string - * @throws PayAnyWayException + * @throws \InvalidArgumentException */ -function payanyway_getPayAnyWayIntegrityCodeOrFail($params) +function payanyway_getPayAnyWayIntegrityCodeOrFail(array $data) { - $integrityCode = payanyway_getOptionalString($params, 'mnt_dataintegrity_code'); + $integrityCode = payanyway_getOptionalString($data, 'mnt_dataintegrity_code'); if (null === $integrityCode) { - throw new \PayAnyWayException( + throw new \InvalidArgumentException( 'PayAnyWay module is not configured properly. ' . 'Please set Code of Data Integrity Verification in WHMCS Admin → Payment Gateways.' ); @@ -181,54 +188,56 @@ function payanyway_getPayAnyWayIntegrityCodeOrFail($params) } /** - * @param array $params + * @param array $data * @return int - * @throws PayAnyWayException + * @throws \InvalidArgumentException */ -function payanyway_getWhmcsInvoiceIdOrFail($params) +function payanyway_getWhmcsInvoiceIdOrFail(array $data) { - $invoiceId = payanyway_getOptionalInt($params, 'invoiceid'); + $invoiceId = payanyway_getOptionalInt($data, 'invoiceid'); if (null === $invoiceId) { - throw new \PayAnyWayException('Missing or invalid invoice ID (integer required)'); + throw new \InvalidArgumentException('Missing or invalid invoice ID (integer required)'); } return $invoiceId; } /** - * @param array $params + * @param array $data * @return float - * @throws PayAnyWayException + * @throws \InvalidArgumentException */ -function payanyway_getWhmcsInvoiceAmountOrFail($params) +function payanyway_getWhmcsInvoiceAmountOrFail(array $data) { - $amount = payanyway_getOptionalFloat($params, 'amount'); + $amount = payanyway_getOptionalFloat($data, 'amount'); if (null === $amount) { - throw new \PayAnyWayException('Missing or invalid invoice amount (float required).'); + throw new \InvalidArgumentException('Missing or invalid invoice amount (float required).'); } return $amount; } /** - * @param array $params - * @return non-empty-string - * @throws PayAnyWayException + * @param array $data + * @return string + * @throws \InvalidArgumentException */ -function payanyway_getWhmcsInvoiceCurrencyOrFail($params) +function payanyway_getWhmcsInvoiceCurrencyOrFail(array $data) { - $currency = payanyway_getOptionalString($params, 'currency'); + $supportedCurrencies = array('RUB'); + + $currency = payanyway_getOptionalString($data, 'currency'); if (null === $currency) { - $supported = implode(', ', PAYANYWAY_SUPPORTED_CURRENCIES); - throw new \PayAnyWayException( + $supported = implode(', ', $supportedCurrencies); + throw new \InvalidArgumentException( 'Missing currency code. Module supports only: ' . $supported ); } $currency = strtoupper($currency); - if (!in_array($currency, PAYANYWAY_SUPPORTED_CURRENCIES, true)) { - $supported = implode(', ', PAYANYWAY_SUPPORTED_CURRENCIES); - throw new \PayAnyWayException( + if (!in_array($currency, $supportedCurrencies, true)) { + $supported = implode(', ', $supportedCurrencies); + throw new \InvalidArgumentException( "Unsupported currency code: '{$currency}'. Module supports only: {$supported}" ); } @@ -237,63 +246,64 @@ function payanyway_getWhmcsInvoiceCurrencyOrFail($params) } /** - * @param array $params + * @param array $data + * @param string $name * @return string - * @throws PayAnyWayException + * @throws \InvalidArgumentException */ -function payanyway_getWhmcsVariableOrFail($params, $name) +function payanyway_getWhmcsVariableOrFail(array $data, $name) { - $var = payanyway_getOptionalString($params, $name); + $var = payanyway_getOptionalString($data, $name); if (null === $var) { - throw new \PayAnyWayException(sprintf('Whmcs variable $params[\'%s\'] is not set.', $name)); + throw new \InvalidArgumentException(sprintf('WHMCS variable $params[\'%s\'] is not set.', $name)); } return $var; } /** - * @param array $array - * @param string $key + * @param array $data + * @param string $key * @return int|null */ -function payanyway_getOptionalInt($array, $key) +function payanyway_getOptionalInt(array $data, $key) { - if (!isset($array[$key]) || !is_numeric($array[$key])) { + if (!isset($data[$key]) || !is_numeric($data[$key])) { return null; } - return (int)$array[$key]; + return (int)$data[$key]; } /** - * @param array $array - * @param string $key + * @param array $data + * @param string $key * @return float|null */ -function payanyway_getOptionalFloat($array, $key) +function payanyway_getOptionalFloat(array $data, $key) { - if (!isset($array[$key]) || !is_numeric($array[$key])) { + if (!isset($data[$key]) || !is_numeric($data[$key])) { return null; } - return (float)$array[$key]; + return (float)$data[$key]; } /** - * @param array $array - * @param string $key + * @param array $data + * @param string $key * @return float - * @throws PayAnyWayException + * @throws \InvalidArgumentException */ -function payanyway_getRequiredFloat($array, $key) +function payanyway_getRequiredFloat(array $data, $key) { - if (!isset($array[$key])) { - throw new \PayAnyWayException('Missing required field ' . $key); + if (!isset($data[$key])) { + throw new \InvalidArgumentException('Missing required field ' . $key); } - $value = $array[$key]; + $value = $data[$key]; if (!is_numeric($value)) { - throw new \PayAnyWayException( + throw new \InvalidArgumentException( sprintf('Field "%s" must be a number, %s given', $key, gettype($value)) ); } @@ -302,63 +312,63 @@ function payanyway_getRequiredFloat($array, $key) } /** - * @param array $array - * @param string $key + * @param array $data + * @param string $key * @return string - * @throws PayAnyWayException + * @throws \InvalidArgumentException */ -function payanyway_getRequiredString($array, $key) +function payanyway_getRequiredString(array $data, $key) { - if (!isset($array[$key])) { - throw new \PayAnyWayException('Missing required field ' . $key); + if (!isset($data[$key])) { + throw new \InvalidArgumentException('Missing required field ' . $key); } - $value = $array[$key]; + $value = $data[$key]; if (!is_string($value)) { - throw new \PayAnyWayException( + throw new \InvalidArgumentException( sprintf('Field "%s" must be a string, %s given', $key, gettype($value)) ); } if ('' === $value) { - throw new \PayAnyWayException(sprintf('Field "%s" must be a non-empty string', $key)); + throw new \InvalidArgumentException(sprintf('Field "%s" must be a non-empty string', $key)); } return $value; } /** - * @param array $array - * @param string $key + * @param array $data + * @param string $key * @return string|null */ -function payanyway_getOptionalString($array, $key) +function payanyway_getOptionalString(array $data, $key) { - if (!isset($array[$key])) { + if (!isset($data[$key])) { return null; } - $value = $array[$key]; + $value = $data[$key]; return (is_string($value) && ('' !== trim($value))) ? $value : null; } /** - * @param array $params - * @param non-empty-string $key - * @param string $default + * @param array $data + * @param string $key + * @param string $default * @return string */ -function payanyway_getOptionalStringWithDefault($params, $key, $default = '') +function payanyway_getOptionalStringWithDefault(array $data, $key, $default = '') { - $value = payanyway_getOptionalString($params, $key); + $value = payanyway_getOptionalString($data, $key); return (null !== $value) ? $value : $default; } /** * @param float $amount - * @return non-empty-string + * @return string */ function payanyway_formatAmount($amount) { @@ -366,58 +376,73 @@ function payanyway_formatAmount($amount) } /** - * @param array $params + * @param array $data * @return string */ -function payanyway_getSubscriberId($params) +function payanyway_getSubscriberId(array $data) { - $email = isset($params['clientdetails']['email']) - ? filter_var($params['clientdetails']['email'], FILTER_SANITIZE_EMAIL) + $email = isset($data['clientdetails']['email']) + ? filter_var($data['clientdetails']['email'], FILTER_SANITIZE_EMAIL) : ''; - $phoneNumber = isset($params['clientdetails']['phonenumber']) - ? preg_replace('/[^0-9+]/', '', $params['clientdetails']['phonenumber']) + $phoneNumber = isset($data['clientdetails']['phonenumber']) + ? preg_replace('/[^0-9+]/', '', $data['clientdetails']['phonenumber']) : ''; return $email ?: $phoneNumber; } /** - * @param int $invoiceId - * @param array $params - * @return non-empty-string + * @param int $invoiceId + * @return string */ -function payanyway_getDescription($invoiceId, $params) +function payanyway_createTransactionId($invoiceId) +{ + $date = date('YmdHis'); + return $invoiceId . TRANSACTION_ID_STRING_DELIMITER . $date; +} + +/** + * @param string $transactionId + * @return int|null + */ +function payanyway_getInvoiceIdFromTransactionId($transactionId) { + $transactionIdData = explode(TRANSACTION_ID_STRING_DELIMITER, $transactionId, 2); + + return isset($transactionIdData[0]) ? (int)$transactionIdData[0] : null; +} + +/** + * @param int $invoiceId + * @param array $params + * @return string + */ +function payanyway_getDescription($invoiceId, array $params) { $clientFirstName = isset($params['clientdetails']['firstname']) ? $params['clientdetails']['firstname'] : ''; $clientLastName = isset($params['clientdetails']['lastname']) ? $params['clientdetails']['lastname'] : ''; $clientName = trim($clientFirstName . ' ' . $clientLastName); - $description = "Payment for Invoice #{$invoiceId}"; + $description = "Payment for Invoice №{$invoiceId}"; if ('' !== $clientName) { $description .= " from {$clientName}"; } - $originalDescription = isset($params['description']) ? trim($params['description']) : ''; + $originalDescription = isset($params['description']) ? $params['description'] : ''; if ('' !== $originalDescription) { - $description .= ': ' . $originalDescription; + $description .= ': ' . htmlspecialchars($originalDescription, ENT_QUOTES, 'UTF-8'); } - if (mb_strlen($description, 'UTF-8') > PAYANYWAY_DESCRIPTION_MAX_LENGTH) { - $description = mb_substr($description, 0, 497, 'UTF-8') . '...'; - } - - return $description; + return payanyway_validateString($description, DESCRIPTION_MAX_LENGTH); } /** - * @param array $requestData - * @return array - * @throws PayAnyWayException + * @param array $requestData + * @return array */ -function payanyway_getCallbackData($requestData) +function payanyway_getCallbackData(array $requestData) { - return [ + return array( 'MNT_COMMAND' => payanyway_getOptionalStringWithDefault($requestData, 'MNT_COMMAND'), 'MNT_ID' => payanyway_getRequiredString($requestData, 'MNT_ID'), 'MNT_TRANSACTION_ID' => payanyway_getRequiredString($requestData, 'MNT_TRANSACTION_ID'), @@ -427,134 +452,156 @@ function payanyway_getCallbackData($requestData) 'MNT_SUBSCRIBER_ID' => payanyway_getOptionalStringWithDefault($requestData, 'MNT_SUBSCRIBER_ID'), 'MNT_TEST_MODE' => payanyway_getRequiredString($requestData, 'MNT_TEST_MODE'), 'MNT_SIGNATURE' => payanyway_getRequiredString($requestData, 'MNT_SIGNATURE'), - ]; + ); } /** - * @param array $callbackData - * @param non-empty-string $gatewayName - * @param non-empty-string $integrityCode + * @param array $callbackData + * @param array $gatewayParams * @return void - * @throws DOMException + * @throws \Exception */ -function payanyway_handleCheckCallback($callbackData, $gatewayName, $integrityCode) +function payanyway_handleCheckCallback(array $callbackData, array $gatewayParams) { - if (!payanyway_checkSignature($callbackData, $integrityCode, $callbackData['MNT_COMMAND'])) { - payanyway_sendResponse('FAIL', 500); + $integrityCode = payanyway_getPayAnyWayIntegrityCodeOrFail($gatewayParams); + if (!payanyway_checkSignature($callbackData, $callbackData['MNT_SIGNATURE'], $integrityCode)) { + payanyway_sendResponse('FAIL'); } - $rawInvoiceId = (int)$callbackData['MNT_TRANSACTION_ID']; + $gatewayName = payanyway_getWhmcsVariableOrFail($gatewayParams, 'name'); + + $rawInvoiceId = payanyway_getInvoiceIdFromTransactionId($callbackData['MNT_TRANSACTION_ID']); + if (null === $rawInvoiceId) { + payanyway_sendResponse('FAIL'); + } $invoiceId = checkCbInvoiceID($rawInvoiceId, $gatewayName); $invoiceDetails = payanyway_getInvoiceDetails($invoiceId); if (null === $invoiceDetails) { - payanyway_sendResponse('FAIL', 500); + payanyway_sendResponse('FAIL'); } $invoiceItems = payanyway_getInvoiceItems($invoiceDetails); - $invoiceTotal = payanyway_formatAmount($invoiceDetails['total']); - $invoicePaymentStatus = $invoiceDetails['status']; + $invoicePaymentStatus = (string)$invoiceDetails['status']; - $xmlData = [ - 'MNT_ID' => $callbackData['MNT_ID'], + $xmlData = array( + 'MNT_ID' => payanyway_getPayAnyWayMntIdOrFail($gatewayParams), 'MNT_TRANSACTION_ID' => $callbackData['MNT_TRANSACTION_ID'], - 'MNT_AMOUNT' => $invoiceTotal, + 'MNT_AMOUNT' => payanyway_formatAmount($invoiceDetails['total']), 'MNT_CURRENCY_CODE' => $callbackData['MNT_CURRENCY_CODE'], 'inventory' => payanyway_getInventoryJson($invoiceItems) ?: null, 'client' => $callbackData['MNT_SUBSCRIBER_ID'], 'sno' => null, - 'delivery' => $invoiceTotal, - ]; - - switch (true) { - case empty($callbackData['MNT_AMOUNT']): - $xmlData['MNT_RESULT_CODE'] = 100; - $xmlData['MNT_DESCRIPTION'] = 'Invoice created, but amount not set'; - break; - case ('Paid' === $invoicePaymentStatus): - $xmlData['MNT_RESULT_CODE'] = 200; - $xmlData['MNT_DESCRIPTION'] = 'Invoice Paid'; - break; - default: - $xmlData['MNT_RESULT_CODE'] = 402; - $xmlData['MNT_DESCRIPTION'] = 'Invoice Unpaid'; - } - - $xmlData['MNT_SIGNATURE'] = md5( - $xmlData['MNT_RESULT_CODE'] - . $xmlData['MNT_ID'] - . $xmlData['MNT_TRANSACTION_ID'] - . $integrityCode + 'delivery' => null, ); - payanyway_sendResponse(payanyway_buildXMLResponse($xmlData), 200); + list($xmlData['MNT_RESULT_CODE'], $xmlData['MNT_DESCRIPTION']) = payanyway_determineCheckResult($callbackData, $invoicePaymentStatus); + + $xmlData['MNT_SIGNATURE'] = md5( + $xmlData['MNT_RESULT_CODE'] . + $xmlData['MNT_ID'] . + $xmlData['MNT_TRANSACTION_ID'] . + payanyway_getPayAnyWayIntegrityCodeOrFail($gatewayParams) + ); + + payanyway_sendResponse(payanyway_buildXMLResponse($xmlData)); } /** - * @param array $callbackData - * @param non-empty-string $gatewayName - * @param non-empty-string $integrityCode - * @param non-empty-string $gatewayModuleName - * @return void - * @throws DOMException + * @param array $callbackData + * @param string $invoicePaymentStatus + * @return array + * @throws \Exception */ -function payanyway_handlePayCallback($callbackData, $gatewayName, $integrityCode, $gatewayModuleName) +function payanyway_determineCheckResult(array $callbackData, $invoicePaymentStatus) { - if (!payanyway_checkSignature($callbackData, $integrityCode)) { - payanyway_sendResponse('FAIL', 500); + if (empty($callbackData['MNT_AMOUNT'])) { + return array(100, "Invoice status is '{$invoicePaymentStatus}'"); } - $rawInvoiceId = $callbackData['MNT_TRANSACTION_ID']; - $invoiceId = checkCbInvoiceID($rawInvoiceId, $gatewayName); - $paidAmount = $callbackData['MNT_AMOUNT']; + if ('Paid' === $invoicePaymentStatus) { + return array(200, 'Invoice Paid'); + } - $invoiceDetails = payanyway_getInvoiceDetailsOrFail($invoiceId); + return array(402, "Invoice status is '{$invoicePaymentStatus}'"); +} + +/** + * @param array $callbackData + * @param array $gatewayParams + * @param string $gatewayModuleName + * @return void + * @throws \Exception + */ +function payanyway_handlePayCallback(array $callbackData, array $gatewayParams, $gatewayModuleName) +{ + $gatewayName = payanyway_getWhmcsVariableOrFail($gatewayParams, 'name'); + + $rawInvoiceId = payanyway_getInvoiceIdFromTransactionId($callbackData['MNT_TRANSACTION_ID']); + if (null === $rawInvoiceId) { + payanyway_sendResponse('FAIL'); + } + $invoiceId = checkCbInvoiceID($rawInvoiceId, $gatewayName); + $invoiceDetails = payanyway_getInvoiceDetails($invoiceId); if (null === $invoiceDetails) { - payanyway_sendResponse('FAIL', 500); + payanyway_sendResponse('FAIL'); + } + $invoiceTotal = payanyway_formatAmount($invoiceDetails['total']); + + $shopData = array( + 'MNT_ID' => payanyway_getPayAnyWayMntIdOrFail($gatewayParams), + 'MNT_TRANSACTION_ID' => $callbackData['MNT_TRANSACTION_ID'], + 'MNT_OPERATION_ID' => $callbackData['MNT_OPERATION_ID'], + 'MNT_AMOUNT' => $invoiceTotal, + 'MNT_CURRENCY_CODE' => $callbackData['MNT_CURRENCY_CODE'], + 'MNT_SUBSCRIBER_ID' => $callbackData['MNT_SUBSCRIBER_ID'], + 'MNT_TEST_MODE' => $callbackData['MNT_TEST_MODE'], + ); + + $integrityCode = payanyway_getPayAnyWayIntegrityCodeOrFail($gatewayParams); + if (!payanyway_checkSignature($shopData, $callbackData['MNT_SIGNATURE'], $integrityCode)) { + payanyway_sendResponse('FAIL'); } $invoiceItems = payanyway_getInvoiceItems($invoiceDetails); - $invoicePaymentStatus = $invoiceDetails['status']; + $invoicePaymentStatus = (string)$invoiceDetails['status']; + $mntId = payanyway_getPayAnyWayMntIdOrFail($gatewayParams); $resultCode = 200; - $xmlData = [ - 'MNT_ID' => $callbackData['MNT_ID'], + $xmlData = array( + 'MNT_ID' => $mntId, 'MNT_TRANSACTION_ID' => $callbackData['MNT_TRANSACTION_ID'], 'MNT_RESULT_CODE' => $resultCode, - 'MNT_SIGNATURE' => md5( - $resultCode . $callbackData['MNT_ID'] . $callbackData['MNT_TRANSACTION_ID'] . $integrityCode - ), - 'MNT_AMOUNT' => $paidAmount, + 'MNT_SIGNATURE' => md5($resultCode . $mntId . $callbackData['MNT_TRANSACTION_ID'] . $integrityCode), + 'MNT_AMOUNT' => $invoiceTotal, 'MNT_CURRENCY_CODE' => $callbackData['MNT_CURRENCY_CODE'], - 'MNT_DESCRIPTION' => ('Paid' !== $invoicePaymentStatus) - ? 'Invoice success paid' - : 'Invoice already paid', - 'inventory' => payanyway_getInventoryJson($invoiceItems), + 'MNT_DESCRIPTION' => ('Paid' !== $invoicePaymentStatus) ? 'Invoice success paid' : 'Invoice already paid', + 'inventory' => payanyway_getInventoryJson($invoiceItems) ?: null, 'client' => $callbackData['MNT_SUBSCRIBER_ID'], 'sno' => null, - 'delivery' => $paidAmount, - ]; + 'delivery' => null, + ); if ('Paid' !== $invoicePaymentStatus) { $transactionId = $callbackData['MNT_OPERATION_ID']; checkCbTransID($transactionId); // if $transactionId exist, script die() if (!payanyway_existInvoiceTransaction($invoiceId, $transactionId)) { - addInvoicePayment($invoiceId, $transactionId, $paidAmount, PAYANYWAY_PAYMENT_FEE, $gatewayModuleName); + addInvoicePayment($invoiceId, $transactionId, $invoiceTotal, MODULE_PAYMENT_FEE, $gatewayModuleName); logTransaction($gatewayName, $callbackData, 'Success'); } } - payanyway_sendResponse(payanyway_buildXMLResponse($xmlData), 200); + payanyway_sendResponse(payanyway_buildXMLResponse($xmlData)); } /** - * @param array $callbackData - * @param string $integrityCode - * @param non-empty-string|null $command + * @param array $data + * @param string $callbackSignature + * @param string $integrityCode * @return bool */ -function payanyway_checkSignature($callbackData, $integrityCode, $command = null) +function payanyway_checkSignature(array $data, $callbackSignature, $integrityCode) { - $baseFields = [ + $baseFields = array( 'MNT_ID', 'MNT_TRANSACTION_ID', 'MNT_OPERATION_ID', @@ -562,28 +609,30 @@ function payanyway_checkSignature($callbackData, $integrityCode, $command = null 'MNT_CURRENCY_CODE', 'MNT_SUBSCRIBER_ID', 'MNT_TEST_MODE', - ]; + ); - $fields = ('CHECK' === $command) ? array_merge(['MNT_COMMAND'], $baseFields) : $baseFields; + $fields = isset($data['MNT_COMMAND']) && ('CHECK' === $data['MNT_COMMAND']) + ? array_merge(['MNT_COMMAND'], $baseFields) + : $baseFields; - $signatureString = array_reduce($fields, static function ($carry, $field) use ($callbackData) { - return $carry . $callbackData[$field]; + $signatureString = array_reduce($fields, function ($carry, $field) use ($data) { + return $carry . $data[$field]; }, '') . $integrityCode; - return hash_equals(md5($signatureString), $callbackData['MNT_SIGNATURE']); + return hash_equals(md5($signatureString), $callbackSignature); } /** * @see https://developers.whmcs.com/api-reference/getinvoice/ * @param int $invoiceId - * @return array|null + * @return array|null */ function payanyway_getInvoiceDetails($invoiceId) { $command = 'GetInvoice'; - $postData = [ + $postData = array( 'invoiceid' => $invoiceId, - ]; + ); $results = localAPI($command, $postData); if ($results['result'] !== 'success') { @@ -594,10 +643,10 @@ function payanyway_getInvoiceDetails($invoiceId) } /** - * @param array $invoiceDetails - * @return array|null + * @param array $invoiceDetails + * @return array|null */ -function payanyway_getInvoiceItems($invoiceDetails) +function payanyway_getInvoiceItems(array $invoiceDetails) { return (isset($invoiceDetails['items']['item']) && is_array($invoiceDetails['items']['item'])) ? $invoiceDetails['items']['item'] @@ -606,23 +655,23 @@ function payanyway_getInvoiceItems($invoiceDetails) /** * @see https://developers.whmcs.com/api-reference/gettransactions/ - * @param int $invoiceId - * @param non-empty-string $transactionId + * @param int $invoiceId + * @param string $transactionId * @return bool */ function payanyway_existInvoiceTransaction($invoiceId, $transactionId) { - $results = localAPI('GetTransactions', [ + $results = localAPI('GetTransactions', array( 'invoiceid' => $invoiceId, 'transid' => $transactionId, - ]); + )); if ($results['result'] !== 'success') { return false; } - $transactions = isset($results['transactions']['transaction']) ? $results['transactions']['transaction'] : []; - if ([] === $transactions) { + $transactions = isset($results['transactions']['transaction']) ? $results['transactions']['transaction'] : array(); + if (empty($transactions)) { return false; } @@ -636,47 +685,45 @@ function payanyway_existInvoiceTransaction($invoiceId, $transactionId) } /** - * @param array $items + * @param array $items * @return false|string */ -function payanyway_getInventoryJson($items) +function payanyway_getInventoryJson(array $items) { - $inventory = []; + $inventory = array(); foreach ($items as $item) { - $description = html_entity_decode($item['description'], ENT_QUOTES | ENT_HTML5, 'UTF-8'); - $positionName = trim(preg_replace('/[^\p{L}\p{N}\s\-]/u', '', $description)); - if (empty($positionName)) { - $positionName = 'Service'; - } + $name = isset($item['description']) ? $item['description'] : ''; + $price = isset($item['amount']) ? (float)$item['amount'] : 0; + $name = payanyway_validateString($name, INVENTORY_ITEM_NAME_MAX_LENGTH); - $inventory[] = [ - 'name' => mb_substr($positionName, 0, PAYANYWAY_INVENTORY_ITEM_NAME_MAX_LENGTH, 'UTF-8'), - 'price' => (float)payanyway_formatAmount($item['amount']), - 'quantity' => PAYANYWAY_INVENTORY_ITEM_DEFAULT_QUANTITY, - 'vatTag' => PAYANYWAY_VAT_TAG_NONE, - 'pm' => PAYANYWAY_INVENTORY_ITEM_DEFAULT_PAYMENT_METHOD, - 'po' => PAYANYWAY_INVENTORY_ITEM_DEFAULT_PAYMENT_OBJECT, - 'measure' => PAYANYWAY_INVENTORY_ITEM_DEFAULT_MEASURE, - ]; + $inventory[] = array( + 'name' => !empty($name) ? $name : 'Service', + 'price' => (float)payanyway_formatAmount($price), + 'quantity' => INVENTORY_ITEM_DEFAULT_QUANTITY, + 'vatTag' => INVENTORY_ITEM_DEFAULT_VAT_TAG, + 'pm' => INVENTORY_ITEM_DEFAULT_PAYMENT_METHOD, + 'po' => INVENTORY_ITEM_DEFAULT_PAYMENT_OBJECT, + 'measure' => INVENTORY_ITEM_DEFAULT_MEASURE, + ); } return json_encode($inventory, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); } /** - * @param array $data + * @param array $data * @return false|string - * @throws DOMException + * @throws \Exception */ -function payanyway_buildXMLResponse($data) +function payanyway_buildXMLResponse(array $data) { - $dom = new DOMDocument('1.0', 'UTF-8'); + $dom = new \DOMDocument('1.0', 'UTF-8'); $dom->formatOutput = false; $root = $dom->createElement('MNT_RESPONSE'); $dom->appendChild($root); - $requiredFields = [ + $requiredFields = array( 'MNT_ID', 'MNT_TRANSACTION_ID', 'MNT_RESULT_CODE', @@ -684,50 +731,84 @@ function payanyway_buildXMLResponse($data) 'MNT_AMOUNT', 'MNT_CURRENCY_CODE', 'MNT_SIGNATURE', - ]; + ); foreach ($requiredFields as $field) { - if (isset($data[$field])) { - $element = $dom->createElement($field, $data[$field]); - $root->appendChild($element); + if (isset($data[$field]) && ($data[$field] !== '')) { + $root->appendChild($dom->createElement($field, (string)$data[$field])); } } $attributes = $dom->createElement('MNT_ATTRIBUTES'); $root->appendChild($attributes); - $addAttribute = static function ($key, $value) use ($dom, $attributes) { + $attributeMap = array( + 'INVENTORY' => isset($data['inventory']) ? $data['inventory'] : null, + 'CLIENT' => isset($data['client']) ? $data['client'] : null, + 'SNO' => isset($data['sno']) ? $data['sno'] : null, + 'DELIVERY' => isset($data['delivery']) ? $data['delivery'] : null, + ); + + foreach ($attributeMap as $key => $value) { if (!empty($value)) { $attrElement = $dom->createElement('ATTRIBUTE'); - $keyElement = $dom->createElement('KEY', $key); - $valueElement = $dom->createElement('VALUE', $value); - - $attrElement->appendChild($keyElement); - $attrElement->appendChild($valueElement); + $attrElement->appendChild($dom->createElement('KEY', $key)); + $attrElement->appendChild($dom->createElement('VALUE', (string)$value)); $attributes->appendChild($attrElement); } - }; - - $addAttribute('INVENTORY', json_encode($data['inventory'], JSON_UNESCAPED_UNICODE)); - $addAttribute('CLIENT', $data['client']); - - if (!empty($data['sno'])) { - $addAttribute('SNO', $data['sno']); - } - - if (!empty($data['delivery'])) { - $addAttribute('DELIVERY', $data['delivery']); } return $dom->saveXML(); } +/** + * @param string|null $whmcsVersion + * @return string + */ +function payanyway_getCmsModuleVersion($whmcsVersion) +{ + $whmcsVersion = isset($whmcsVersion) ? $whmcsVersion : '.unknown'; + + return sprintf('%s v%s | %s v%s', CMS_NAME, $whmcsVersion, MODULE_NAME, MODULE_VERSION); +} + +/** + * @param string $value + * @param int $maxLength + * @return string + */ +function payanyway_validateString($value, $maxLength) +{ + $maxLength = max(0, $maxLength); + $value = payanyway_sanitizeString($value); + + if (mb_strlen($value, 'UTF-8') <= $maxLength) { + return $value; + } + + $trimLength = max(0, $maxLength - 3); + return mb_substr($value, 0, $trimLength, 'UTF-8') . ($maxLength > 3 ? '...' : ''); +} + +/** + * @param string $value + * @return string + */ +function payanyway_sanitizeString($value) +{ + $decoded = html_entity_decode($value, ENT_QUOTES | ENT_HTML5, 'UTF-8'); + $cleaned = preg_replace('/[^\p{L}\p{N}\s.,()_№+-]/u', '', $decoded); + $cleaned = str_replace(['&', '/', '\\', ';', '%', '#', '"', "'"], '', $cleaned); + + return trim(preg_replace('/\s+/', ' ', $cleaned)); +} + /** * @param string $response * @param int $statusCode * @return void */ -function payanyway_sendResponse($response, $statusCode) +function payanyway_sendResponse($response, $statusCode = 200) { http_response_code($statusCode); @@ -738,13 +819,5 @@ function payanyway_sendResponse($response, $statusCode) } echo $response; - die(); + exit(); } - -class PayAnyWayException extends \Exception -{ - public function __construct($message = '', $code = 0, \Exception $previous = null) - { - parent::__construct($message, $code, $previous); - } -} \ No newline at end of file