feat: paygw_moneta 1.0.0, MONETA.Assistant payment gateway for Moodle
This commit is contained in:
@@ -0,0 +1,106 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace paygw_moneta\local\protocol;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use paygw_moneta\local\Money;
|
||||
|
||||
/**
|
||||
* Три формулы подписи MONETA.Assistant (`MONETA.Assistant.ru.pdf`, гл. 3–5).
|
||||
*
|
||||
* Везде MD5 от конкатенации значений без разделителей плюс код проверки
|
||||
* целостности; отсутствующий параметр — пустая строка. Экземпляр держит код,
|
||||
* чтобы он не гулял по сигнатурам методов.
|
||||
*
|
||||
* @package paygw_moneta
|
||||
* @copyright 2026 Moneta Labs {@link https://moneta.ru/}
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
final class Signature
|
||||
{
|
||||
private const PATTERN = '/\A[0-9a-f]{32}\z/i';
|
||||
|
||||
public function __construct(
|
||||
#[\SensitiveParameter]
|
||||
private readonly string $integrityCode,
|
||||
) {
|
||||
if ($integrityCode === '') {
|
||||
throw new InvalidArgumentException('Код проверки целостности не задан.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Формула 1 — платёжная форма (модуль → сервис).
|
||||
*
|
||||
* md5(MNT_ID + MNT_TRANSACTION_ID + MNT_AMOUNT + MNT_CURRENCY_CODE + MNT_SUBSCRIBER_ID + MNT_TEST_MODE + КОД)
|
||||
*/
|
||||
public function forPaymentForm(
|
||||
string $accountId,
|
||||
string $transactionId,
|
||||
Money $amount,
|
||||
string $currency,
|
||||
string $subscriberId,
|
||||
bool $testMode,
|
||||
): string {
|
||||
return $this->hash(
|
||||
$accountId
|
||||
. $transactionId
|
||||
. $amount->toDecimal()
|
||||
. $currency
|
||||
. $subscriberId
|
||||
. AssistantProtocol::testModeFlag($testMode),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Формула 2 — входящий запрос на Check URL / Pay URL (сервис → модуль).
|
||||
*
|
||||
* md5(MNT_COMMAND + MNT_ID + MNT_TRANSACTION_ID + MNT_OPERATION_ID + MNT_AMOUNT
|
||||
* + MNT_CURRENCY_CODE + MNT_SUBSCRIBER_ID + MNT_TEST_MODE + КОД)
|
||||
*
|
||||
* Значения берутся из запроса как пришли: подпись сверяется с тем, что
|
||||
* подписал сервис, а не с нашим представлением о заказе.
|
||||
*/
|
||||
public function forNotification(CallbackNotification $notification): string
|
||||
{
|
||||
return $this->hash(
|
||||
$notification->getCommand()
|
||||
. $notification->getAccountId()
|
||||
. $notification->getTransactionId()
|
||||
. $notification->getOperationId()
|
||||
. $notification->getRawAmount()
|
||||
. $notification->getCurrency()
|
||||
. $notification->getSubscriberId()
|
||||
. $notification->getRawTestMode(),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Формула 3 — ответ модуля на Check URL и Pay URL.
|
||||
*
|
||||
* md5(MNT_RESULT_CODE + MNT_ID + MNT_TRANSACTION_ID + КОД)
|
||||
*/
|
||||
public function forResponse(ResultCode $resultCode, string $accountId, string $transactionId): string
|
||||
{
|
||||
return $this->hash((string) $resultCode->value . $accountId . $transactionId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Сравнение без утечки по времени; регистр hex-строки не важен.
|
||||
*/
|
||||
public static function equals(string $expected, string $received): bool
|
||||
{
|
||||
if (preg_match(self::PATTERN, $received) !== 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return hash_equals(strtolower($expected), strtolower($received));
|
||||
}
|
||||
|
||||
private function hash(string $payload): string
|
||||
{
|
||||
return md5($payload . $this->integrityCode);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user