Merge master into release for version 1.1.0
This commit is contained in:
parent
6e289965d0
commit
874b61677f
111
README.md
111
README.md
@ -1,24 +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
|
||||
|
||||
|
||||
Удачных платежей!
|
||||

|
||||

|
||||

|
||||
|
||||
**[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/)
|
||||
|
||||
---
|
||||
|
||||
**✅ Модуль настроен, приятных платежей!** 💰🎉
|
||||
@ -1,51 +1,41 @@
|
||||
<?php
|
||||
|
||||
# Required File Includes
|
||||
include("../../../includes/functions.php");
|
||||
include("../../../includes/gatewayfunctions.php");
|
||||
include("../../../includes/invoicefunctions.php");
|
||||
/**
|
||||
* PayAnyWay - WHMCS Callback Handler
|
||||
*
|
||||
* Receives payment status and check notifications from PayAnyWay
|
||||
* and automatically marks WHMCS invoices as paid upon successful payments.
|
||||
*
|
||||
* Notification URL to configure in your app:
|
||||
* https://your_whmcs_site/modules/gateways/callback/payanyway.php
|
||||
*/
|
||||
|
||||
$gatewaymodule = "payanyway"; # Enter your gateway module name here replacing template
|
||||
require_once __DIR__ . '/../../../init.php';
|
||||
require_once __DIR__ . '/../../../includes/gatewayfunctions.php';
|
||||
require_once __DIR__ . '/../../../includes/invoicefunctions.php';
|
||||
require_once __DIR__ . '/../payanyway.php';
|
||||
|
||||
$GATEWAY = getGatewayVariables($gatewaymodule);
|
||||
if (!$GATEWAY["type"]) die("Module Not Activated"); # Checks gateway module is active before accepting callback
|
||||
$requestData = ($_SERVER['REQUEST_METHOD'] === 'POST') ? $_POST : $_GET;
|
||||
|
||||
# Get Returned Variables - Adjust for Post Variable Names from your Gateway's Documentation
|
||||
$invoiceid = $_GET["MNT_TRANSACTION_ID"];
|
||||
$transid = $_GET["MNT_OPERATION_ID"];
|
||||
$amount = $_GET["MNT_AMOUNT"];
|
||||
try {
|
||||
$gatewayModuleName = 'payanyway';
|
||||
$gatewayParams = getGatewayVariables($gatewayModuleName);
|
||||
if (!$gatewayParams['type']) {
|
||||
payanyway_sendResponse('FAIL', 500); // Module Not Activated
|
||||
}
|
||||
|
||||
$invoiceid = checkCbInvoiceID($invoiceid, $GATEWAY["name"]); # Checks invoice ID is a valid invoice number or ends processing
|
||||
$gatewayName = payanyway_getWhmcsVariableOrFail($gatewayParams, 'name');
|
||||
$integrityCode = payanyway_getPayAnyWayIntegrityCodeOrFail($gatewayParams);
|
||||
|
||||
checkCbTransID($transid); # Checks transaction number isn't already in the database and ends processing if it does
|
||||
$callbackData = payanyway_getCallbackData($requestData);
|
||||
|
||||
if (_checkSignature($GATEWAY)) {
|
||||
# Successful
|
||||
addInvoicePayment($invoiceid, $transid, $amount, null, $gatewaymodule); # Apply Payment to Invoice: invoiceid, transactionid, amount paid, fees, modulename
|
||||
logTransaction($GATEWAY["name"], $_GET, "Successful"); # Save to Gateway Log: name, data array, status
|
||||
die("SUCCESS");
|
||||
} else {
|
||||
# Unsuccessful
|
||||
logTransaction($GATEWAY["name"], $_GET, "Unsuccessful"); # Save to Gateway Log: name, data array, status
|
||||
}
|
||||
die("FAIL");
|
||||
|
||||
|
||||
|
||||
function _checkSignature($GATEWAY) {
|
||||
$params = '';
|
||||
if (isset($_GET['MNT_COMMAND'])) $params .= $_GET['MNT_COMMAND'];
|
||||
$params .= $_GET['MNT_ID'] . $_GET['MNT_TRANSACTION_ID'];
|
||||
if (isset($_GET['MNT_OPERATION_ID'])) $params .= $_GET['MNT_OPERATION_ID'];
|
||||
if (isset($_GET['MNT_AMOUNT'])) $params .= $_GET['MNT_AMOUNT'];
|
||||
$params .= $_GET['MNT_CURRENCY_CODE'];
|
||||
if (isset($_GET['MNT_SUBSCRIBER_ID'])) $params .= $_GET['MNT_SUBSCRIBER_ID'];
|
||||
$params .= $_GET['MNT_TEST_MODE'];
|
||||
|
||||
$signature = md5($params . $GATEWAY['mnt_dataintegrity_code']);
|
||||
|
||||
if (strcasecmp($signature, $_GET['MNT_SIGNATURE']) == 0) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
('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);
|
||||
} catch (\Exception $e) {
|
||||
logTransaction($gatewayModuleName, $requestData, 'Error: ' . $e->getMessage());
|
||||
payanyway_sendResponse('FAIL', 500);
|
||||
}
|
||||
@ -1,60 +1,750 @@
|
||||
<?php
|
||||
/**
|
||||
* PayAnyWay - WHMCS Payment Gateway Module
|
||||
*
|
||||
* WHMCS Gateway Module Developer Documentation:
|
||||
*
|
||||
* @see https://developers.whmcs.com/payment-gateways/
|
||||
*/
|
||||
|
||||
function payanyway_config() {
|
||||
$configarray = array(
|
||||
"FriendlyName" => 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 (<a>) 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 '<p style="color:red;"><strong>Error:</strong> '
|
||||
. htmlspecialchars($e->getMessage(), ENT_QUOTES | ENT_HTML5, 'UTF-8') . '</p>';
|
||||
}
|
||||
|
||||
// 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 = '<form method="post" action="' . $url . '">';
|
||||
foreach ($postFields as $key => $value) {
|
||||
$value = htmlspecialchars($value, ENT_QUOTES | ENT_HTML5, 'UTF-8');
|
||||
$htmlOutput .= '<input type="hidden" name="' . $key . '" value="' . $value . '" />';
|
||||
}
|
||||
|
||||
$htmlOutput .= '<input type="submit" value="' . $langPayNow . '" />';
|
||||
$htmlOutput .= '</form>';
|
||||
|
||||
return $htmlOutput;
|
||||
}
|
||||
|
||||
/**
|
||||
* WHMCS PayAnyWay Payment Gateway Helper Functions
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param array<non-empty-string, mixed> $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<non-empty-string, mixed> $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<non-empty-string, mixed> $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<non-empty-string, mixed> $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<non-empty-string, mixed> $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<non-empty-string, mixed> $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<non-empty-string, mixed> $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<non-empty-string, mixed> $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<non-empty-string, mixed> $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<non-empty-string, mixed> $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<non-empty-string, mixed> $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<non-empty-string, mixed> $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<non-empty-string, mixed> $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<non-empty-string, mixed> $requestData
|
||||
* @return array<non-empty-string, mixed>
|
||||
* @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<non-empty-string, mixed> $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<non-empty-string, mixed> $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 = '<form method="post" action="https://www.payanyway.ru/assistant.htm">
|
||||
<input type="hidden" name="MNT_ID" value="'.$gatewayusername.'" />
|
||||
<input type="hidden" name="MNT_TRANSACTION_ID" value="'.$invoiceid.'" />
|
||||
<input type="hidden" name="MNT_AMOUNT" value="'.$amount.'" />
|
||||
<input type="hidden" name="MNT_CURRENCY_CODE" value="'.$currency.'" />
|
||||
<input type="hidden" name="MNT_SIGNATURE" value="'.$signature.'" />
|
||||
<input type="hidden" name="MNT_TEST_MODE" value="'.$gatewaytestmode.'" />
|
||||
<input type="hidden" name="MNT_DESCRIPTION" value="'.$description.'" />
|
||||
<input type="submit" value="Pay Now" />
|
||||
</form>';
|
||||
|
||||
return $code;
|
||||
payanyway_sendResponse(payanyway_buildXMLResponse($xmlData), 200);
|
||||
}
|
||||
|
||||
?>
|
||||
/**
|
||||
* @param array<non-empty-string, mixed> $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<non-empty-string, mixed>|null
|
||||
*/
|
||||
function payanyway_getInvoiceDetails($invoiceId)
|
||||
{
|
||||
$command = 'GetInvoice';
|
||||
$postData = [
|
||||
'invoiceid' => $invoiceId,
|
||||
];
|
||||
|
||||
$results = localAPI($command, $postData);
|
||||
if ($results['result'] !== 'success') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<non-empty-string, mixed> $invoiceDetails
|
||||
* @return array<non-empty-string, mixed>|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<non-empty-string, mixed> $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<non-empty-string, mixed> $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, '<?xml') === 0 || strpos($response, '<MNT_RESPONSE') !== false) {
|
||||
header('Content-Type: application/xml');
|
||||
} else {
|
||||
header('Content-Type: text/plain; charset=UTF-8');
|
||||
}
|
||||
|
||||
echo $response;
|
||||
die();
|
||||
}
|
||||
|
||||
class PayAnyWayException extends \Exception
|
||||
{
|
||||
public function __construct($message = '', $code = 0, \Exception $previous = null)
|
||||
{
|
||||
parent::__construct($message, $code, $previous);
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user