824 lines
25 KiB
PHP
824 lines
25 KiB
PHP
<?php
|
|
|
|
/**
|
|
* PayAnyWay - WHMCS Payment Gateway Module
|
|
*
|
|
* WHMCS Gateway Module Developer Documentation:
|
|
*
|
|
* @see https://developers.whmcs.com/payment-gateways/
|
|
*/
|
|
|
|
const CMS_NAME = 'WHMCS';
|
|
const MODULE_NAME = 'payanyway';
|
|
const MODULE_VERSION = '1.0.0';
|
|
|
|
const TRANSACTION_ID_STRING_DELIMITER = '|';
|
|
|
|
const PAYMENT_URL_PROD = 'https://www.payanyway.ru/assistant.htm';
|
|
const PAYMENT_URL_DEMO = 'https://demo.moneta.ru/assistant.htm';
|
|
|
|
const DESCRIPTION_MAX_LENGTH = 500;
|
|
|
|
const INVENTORY_ITEM_NAME_MAX_LENGTH = 128;
|
|
const INVENTORY_ITEM_DEFAULT_PAYMENT_METHOD = 'full_payment';
|
|
const INVENTORY_ITEM_DEFAULT_PAYMENT_OBJECT = 'service';
|
|
const INVENTORY_ITEM_DEFAULT_QUANTITY = 1;
|
|
const INVENTORY_ITEM_DEFAULT_MEASURE = 'unit';
|
|
const INVENTORY_ITEM_DEFAULT_VAT_TAG = '1105'; // none
|
|
|
|
const MODULE_PAYMENT_FEE = 0.0;
|
|
|
|
if (!defined('WHMCS')) {
|
|
die('This file cannot be accessed directly');
|
|
}
|
|
|
|
/**
|
|
* Module metadata for WHMCS gateway registration.
|
|
*
|
|
* @return array<string, mixed>
|
|
*/
|
|
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<string, mixed>
|
|
*/
|
|
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<string, mixed> $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(array $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);
|
|
|
|
// 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 = '<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;
|
|
} catch (\Exception $e) {
|
|
return '<p style="color:red;"><strong>Error:</strong> '
|
|
. htmlspecialchars($e->getMessage(), ENT_QUOTES | ENT_HTML5, 'UTF-8') . '</p>';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* WHMCS PayAnyWay Payment Gateway Helper Functions
|
|
*/
|
|
|
|
/**
|
|
* @param array<string, mixed> $data
|
|
* @return int
|
|
* @throws \InvalidArgumentException
|
|
*/
|
|
function payanyway_getPayAnyWayMntIdOrFail(array $data)
|
|
{
|
|
$mntId = payanyway_getOptionalInt($data, 'mnt_id');
|
|
if (null === $mntId) {
|
|
throw new \InvalidArgumentException(
|
|
'PayAnyWay module is not configured properly. '
|
|
. 'Please set Account Number in WHMCS Admin → Payment Gateways.'
|
|
);
|
|
}
|
|
|
|
return $mntId;
|
|
}
|
|
|
|
/**
|
|
* @param array<string, mixed> $data
|
|
* @return string
|
|
* @throws \InvalidArgumentException
|
|
*/
|
|
function payanyway_getPayAnyWayIntegrityCodeOrFail(array $data)
|
|
{
|
|
$integrityCode = payanyway_getOptionalString($data, 'mnt_dataintegrity_code');
|
|
if (null === $integrityCode) {
|
|
throw new \InvalidArgumentException(
|
|
'PayAnyWay module is not configured properly. '
|
|
. 'Please set Code of Data Integrity Verification in WHMCS Admin → Payment Gateways.'
|
|
);
|
|
}
|
|
|
|
return $integrityCode;
|
|
}
|
|
|
|
/**
|
|
* @param array<string, mixed> $data
|
|
* @return int
|
|
* @throws \InvalidArgumentException
|
|
*/
|
|
function payanyway_getWhmcsInvoiceIdOrFail(array $data)
|
|
{
|
|
$invoiceId = payanyway_getOptionalInt($data, 'invoiceid');
|
|
if (null === $invoiceId) {
|
|
throw new \InvalidArgumentException('Missing or invalid invoice ID (integer required)');
|
|
}
|
|
|
|
return $invoiceId;
|
|
}
|
|
|
|
/**
|
|
* @param array<string, mixed> $data
|
|
* @return float
|
|
* @throws \InvalidArgumentException
|
|
*/
|
|
function payanyway_getWhmcsInvoiceAmountOrFail(array $data)
|
|
{
|
|
$amount = payanyway_getOptionalFloat($data, 'amount');
|
|
if (null === $amount) {
|
|
throw new \InvalidArgumentException('Missing or invalid invoice amount (float required).');
|
|
}
|
|
|
|
return $amount;
|
|
}
|
|
|
|
/**
|
|
* @param array<string, mixed> $data
|
|
* @return string
|
|
* @throws \InvalidArgumentException
|
|
*/
|
|
function payanyway_getWhmcsInvoiceCurrencyOrFail(array $data)
|
|
{
|
|
$supportedCurrencies = array('RUB');
|
|
|
|
$currency = payanyway_getOptionalString($data, 'currency');
|
|
if (null === $currency) {
|
|
$supported = implode(', ', $supportedCurrencies);
|
|
throw new \InvalidArgumentException(
|
|
'Missing currency code. Module supports only: ' . $supported
|
|
);
|
|
}
|
|
|
|
$currency = strtoupper($currency);
|
|
if (!in_array($currency, $supportedCurrencies, true)) {
|
|
$supported = implode(', ', $supportedCurrencies);
|
|
throw new \InvalidArgumentException(
|
|
"Unsupported currency code: '{$currency}'. Module supports only: {$supported}"
|
|
);
|
|
}
|
|
|
|
return $currency;
|
|
}
|
|
|
|
/**
|
|
* @param array<string, mixed> $data
|
|
* @param string $name
|
|
* @return string
|
|
* @throws \InvalidArgumentException
|
|
*/
|
|
function payanyway_getWhmcsVariableOrFail(array $data, $name)
|
|
{
|
|
$var = payanyway_getOptionalString($data, $name);
|
|
if (null === $var) {
|
|
throw new \InvalidArgumentException(sprintf('WHMCS variable $params[\'%s\'] is not set.', $name));
|
|
}
|
|
|
|
return $var;
|
|
}
|
|
|
|
/**
|
|
* @param array<string, mixed> $data
|
|
* @param string $key
|
|
* @return int|null
|
|
*/
|
|
function payanyway_getOptionalInt(array $data, $key)
|
|
{
|
|
if (!isset($data[$key]) || !is_numeric($data[$key])) {
|
|
return null;
|
|
}
|
|
|
|
return (int)$data[$key];
|
|
}
|
|
|
|
/**
|
|
* @param array<string, mixed> $data
|
|
* @param string $key
|
|
* @return float|null
|
|
*/
|
|
function payanyway_getOptionalFloat(array $data, $key)
|
|
{
|
|
if (!isset($data[$key]) || !is_numeric($data[$key])) {
|
|
return null;
|
|
}
|
|
|
|
return (float)$data[$key];
|
|
}
|
|
|
|
/**
|
|
* @param array<string, mixed> $data
|
|
* @param string $key
|
|
* @return float
|
|
* @throws \InvalidArgumentException
|
|
*/
|
|
function payanyway_getRequiredFloat(array $data, $key)
|
|
{
|
|
if (!isset($data[$key])) {
|
|
throw new \InvalidArgumentException('Missing required field ' . $key);
|
|
}
|
|
|
|
$value = $data[$key];
|
|
if (!is_numeric($value)) {
|
|
throw new \InvalidArgumentException(
|
|
sprintf('Field "%s" must be a number, %s given', $key, gettype($value))
|
|
);
|
|
}
|
|
|
|
return (float)$value;
|
|
}
|
|
|
|
/**
|
|
* @param array<string, mixed> $data
|
|
* @param string $key
|
|
* @return string
|
|
* @throws \InvalidArgumentException
|
|
*/
|
|
function payanyway_getRequiredString(array $data, $key)
|
|
{
|
|
if (!isset($data[$key])) {
|
|
throw new \InvalidArgumentException('Missing required field ' . $key);
|
|
}
|
|
|
|
$value = $data[$key];
|
|
if (!is_string($value)) {
|
|
throw new \InvalidArgumentException(
|
|
sprintf('Field "%s" must be a string, %s given', $key, gettype($value))
|
|
);
|
|
}
|
|
|
|
if ('' === $value) {
|
|
throw new \InvalidArgumentException(sprintf('Field "%s" must be a non-empty string', $key));
|
|
}
|
|
|
|
return $value;
|
|
}
|
|
|
|
/**
|
|
* @param array<string, mixed> $data
|
|
* @param string $key
|
|
* @return string|null
|
|
*/
|
|
function payanyway_getOptionalString(array $data, $key)
|
|
{
|
|
if (!isset($data[$key])) {
|
|
return null;
|
|
}
|
|
|
|
$value = $data[$key];
|
|
|
|
return (is_string($value) && ('' !== trim($value))) ? $value : null;
|
|
}
|
|
|
|
/**
|
|
* @param array<string, mixed> $data
|
|
* @param string $key
|
|
* @param string $default
|
|
* @return string
|
|
*/
|
|
function payanyway_getOptionalStringWithDefault(array $data, $key, $default = '')
|
|
{
|
|
$value = payanyway_getOptionalString($data, $key);
|
|
|
|
return (null !== $value) ? $value : $default;
|
|
}
|
|
|
|
/**
|
|
* @param float $amount
|
|
* @return string
|
|
*/
|
|
function payanyway_formatAmount($amount)
|
|
{
|
|
return number_format($amount, 2, '.', '');
|
|
}
|
|
|
|
/**
|
|
* @param array<string, mixed> $data
|
|
* @return string
|
|
*/
|
|
function payanyway_getSubscriberId(array $data)
|
|
{
|
|
$email = isset($data['clientdetails']['email'])
|
|
? filter_var($data['clientdetails']['email'], FILTER_SANITIZE_EMAIL)
|
|
: '';
|
|
$phoneNumber = isset($data['clientdetails']['phonenumber'])
|
|
? preg_replace('/[^0-9+]/', '', $data['clientdetails']['phonenumber'])
|
|
: '';
|
|
|
|
return $email ?: $phoneNumber;
|
|
}
|
|
|
|
/**
|
|
* @param int $invoiceId
|
|
* @return string
|
|
*/
|
|
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<string, mixed> $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}";
|
|
|
|
if ('' !== $clientName) {
|
|
$description .= " from {$clientName}";
|
|
}
|
|
|
|
$originalDescription = isset($params['description']) ? $params['description'] : '';
|
|
if ('' !== $originalDescription) {
|
|
$description .= ': ' . htmlspecialchars($originalDescription, ENT_QUOTES, 'UTF-8');
|
|
}
|
|
|
|
return payanyway_validateString($description, DESCRIPTION_MAX_LENGTH);
|
|
}
|
|
|
|
/**
|
|
* @param array<string, mixed> $requestData
|
|
* @return array<string, mixed>
|
|
*/
|
|
function payanyway_getCallbackData(array $requestData)
|
|
{
|
|
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'),
|
|
'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<string, mixed> $callbackData
|
|
* @param array<string, mixed> $gatewayParams
|
|
* @return void
|
|
* @throws \Exception
|
|
*/
|
|
function payanyway_handleCheckCallback(array $callbackData, array $gatewayParams)
|
|
{
|
|
$integrityCode = payanyway_getPayAnyWayIntegrityCodeOrFail($gatewayParams);
|
|
if (!payanyway_checkSignature($callbackData, $callbackData['MNT_SIGNATURE'], $integrityCode)) {
|
|
payanyway_sendResponse('FAIL');
|
|
}
|
|
|
|
$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');
|
|
}
|
|
|
|
$invoiceItems = payanyway_getInvoiceItems($invoiceDetails);
|
|
$invoicePaymentStatus = (string)$invoiceDetails['status'];
|
|
|
|
$xmlData = array(
|
|
'MNT_ID' => payanyway_getPayAnyWayMntIdOrFail($gatewayParams),
|
|
'MNT_TRANSACTION_ID' => $callbackData['MNT_TRANSACTION_ID'],
|
|
'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' => null,
|
|
);
|
|
|
|
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<string, mixed> $callbackData
|
|
* @param string $invoicePaymentStatus
|
|
* @return array
|
|
* @throws \Exception
|
|
*/
|
|
function payanyway_determineCheckResult(array $callbackData, $invoicePaymentStatus)
|
|
{
|
|
if (empty($callbackData['MNT_AMOUNT'])) {
|
|
return array(100, "Invoice status is '{$invoicePaymentStatus}'");
|
|
}
|
|
|
|
if ('Paid' === $invoicePaymentStatus) {
|
|
return array(200, 'Invoice Paid');
|
|
}
|
|
|
|
return array(402, "Invoice status is '{$invoicePaymentStatus}'");
|
|
}
|
|
|
|
/**
|
|
* @param array<string, mixed> $callbackData
|
|
* @param array<string, mixed> $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');
|
|
}
|
|
$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 = (string)$invoiceDetails['status'];
|
|
|
|
$mntId = payanyway_getPayAnyWayMntIdOrFail($gatewayParams);
|
|
$resultCode = 200;
|
|
$xmlData = array(
|
|
'MNT_ID' => $mntId,
|
|
'MNT_TRANSACTION_ID' => $callbackData['MNT_TRANSACTION_ID'],
|
|
'MNT_RESULT_CODE' => $resultCode,
|
|
'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) ?: null,
|
|
'client' => $callbackData['MNT_SUBSCRIBER_ID'],
|
|
'sno' => null,
|
|
'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, $invoiceTotal, MODULE_PAYMENT_FEE, $gatewayModuleName);
|
|
logTransaction($gatewayName, $callbackData, 'Success');
|
|
}
|
|
}
|
|
|
|
payanyway_sendResponse(payanyway_buildXMLResponse($xmlData));
|
|
}
|
|
|
|
/**
|
|
* @param array<string, mixed> $data
|
|
* @param string $callbackSignature
|
|
* @param string $integrityCode
|
|
* @return bool
|
|
*/
|
|
function payanyway_checkSignature(array $data, $callbackSignature, $integrityCode)
|
|
{
|
|
$baseFields = array(
|
|
'MNT_ID',
|
|
'MNT_TRANSACTION_ID',
|
|
'MNT_OPERATION_ID',
|
|
'MNT_AMOUNT',
|
|
'MNT_CURRENCY_CODE',
|
|
'MNT_SUBSCRIBER_ID',
|
|
'MNT_TEST_MODE',
|
|
);
|
|
|
|
$fields = isset($data['MNT_COMMAND']) && ('CHECK' === $data['MNT_COMMAND'])
|
|
? array_merge(['MNT_COMMAND'], $baseFields)
|
|
: $baseFields;
|
|
|
|
$signatureString = array_reduce($fields, function ($carry, $field) use ($data) {
|
|
return $carry . $data[$field];
|
|
}, '') . $integrityCode;
|
|
|
|
return hash_equals(md5($signatureString), $callbackSignature);
|
|
}
|
|
|
|
/**
|
|
* @see https://developers.whmcs.com/api-reference/getinvoice/
|
|
* @param int $invoiceId
|
|
* @return array<string, mixed>|null
|
|
*/
|
|
function payanyway_getInvoiceDetails($invoiceId)
|
|
{
|
|
$command = 'GetInvoice';
|
|
$postData = array(
|
|
'invoiceid' => $invoiceId,
|
|
);
|
|
|
|
$results = localAPI($command, $postData);
|
|
if ($results['result'] !== 'success') {
|
|
return null;
|
|
}
|
|
|
|
return $results;
|
|
}
|
|
|
|
/**
|
|
* @param array<string, mixed> $invoiceDetails
|
|
* @return array<string, mixed>|null
|
|
*/
|
|
function payanyway_getInvoiceItems(array $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 string $transactionId
|
|
* @return bool
|
|
*/
|
|
function payanyway_existInvoiceTransaction($invoiceId, $transactionId)
|
|
{
|
|
$results = localAPI('GetTransactions', array(
|
|
'invoiceid' => $invoiceId,
|
|
'transid' => $transactionId,
|
|
));
|
|
|
|
if ($results['result'] !== 'success') {
|
|
return false;
|
|
}
|
|
|
|
$transactions = isset($results['transactions']['transaction']) ? $results['transactions']['transaction'] : array();
|
|
if (empty($transactions)) {
|
|
return false;
|
|
}
|
|
|
|
foreach ($transactions as $transaction) {
|
|
if (isset($transaction['transid']) && ($transaction['transid'] === $transactionId)) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* @param array<string, mixed> $items
|
|
* @return false|string
|
|
*/
|
|
function payanyway_getInventoryJson(array $items)
|
|
{
|
|
$inventory = array();
|
|
foreach ($items as $item) {
|
|
$name = isset($item['description']) ? $item['description'] : '';
|
|
$price = isset($item['amount']) ? (float)$item['amount'] : 0;
|
|
$name = payanyway_validateString($name, INVENTORY_ITEM_NAME_MAX_LENGTH);
|
|
|
|
$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<string, mixed> $data
|
|
* @return false|string
|
|
* @throws \Exception
|
|
*/
|
|
function payanyway_buildXMLResponse(array $data)
|
|
{
|
|
$dom = new \DOMDocument('1.0', 'UTF-8');
|
|
$dom->formatOutput = false;
|
|
|
|
$root = $dom->createElement('MNT_RESPONSE');
|
|
$dom->appendChild($root);
|
|
|
|
$requiredFields = array(
|
|
'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]) && ($data[$field] !== '')) {
|
|
$root->appendChild($dom->createElement($field, (string)$data[$field]));
|
|
}
|
|
}
|
|
|
|
$attributes = $dom->createElement('MNT_ATTRIBUTES');
|
|
$root->appendChild($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');
|
|
$attrElement->appendChild($dom->createElement('KEY', $key));
|
|
$attrElement->appendChild($dom->createElement('VALUE', (string)$value));
|
|
$attributes->appendChild($attrElement);
|
|
}
|
|
}
|
|
|
|
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 = 200)
|
|
{
|
|
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;
|
|
exit();
|
|
}
|