184 lines
5.2 KiB
PHP
184 lines
5.2 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace MonetaPhpShop\protocol;
|
|
|
|
use InvalidArgumentException;
|
|
use MonetaPhpShop\Money;
|
|
|
|
/**
|
|
* Âõîäÿùèé çàïðîñ ñåðâèñà íà Check URL (`MNT_COMMAND=CHECK`) èëè Pay URL.
|
|
*/
|
|
final class CallbackNotification
|
|
{
|
|
private const ACCOUNT_ID_MAX_LENGTH = 20;
|
|
private const TRANSACTION_ID_MAX_LENGTH = 255;
|
|
private const OPERATION_ID_MAX_LENGTH = 255;
|
|
|
|
/**
|
|
* Íîìåð îïåðàöèè MONETA.RU â Pay URL — òîëüêî öèôðû
|
|
*/
|
|
private const PAY_OPERATION_ID = '/\A[0-9]{1,20}\z/';
|
|
private const SUBSCRIBER_ID_MAX_LENGTH = 255;
|
|
private const CURRENCY_LENGTH = 3;
|
|
private const SIGNATURE_LENGTH = 32;
|
|
|
|
/** @var string */
|
|
private $command;
|
|
|
|
/** @var string */
|
|
private $accountId;
|
|
|
|
/** @var string */
|
|
private $transactionId;
|
|
|
|
/** @var string */
|
|
private $operationId;
|
|
|
|
/** @var string */
|
|
private $rawAmount;
|
|
|
|
/** @var Money|null */
|
|
private $amount;
|
|
|
|
/** @var string */
|
|
private $currency;
|
|
|
|
/** @var string */
|
|
private $subscriberId;
|
|
|
|
/** @var string */
|
|
private $rawTestMode;
|
|
|
|
/** @var string */
|
|
private $signature;
|
|
|
|
/**
|
|
* @param array<string, mixed> $fields ïàðàìåòðû çàïðîñà ($_POST èëè $_GET — ìåòîä çàäà¸òñÿ â íàñòðîéêàõ ñ÷¸òà)
|
|
*/
|
|
public function __construct(array $fields)
|
|
{
|
|
$this->command = self::optional($fields, 'MNT_COMMAND', strlen(AssistantProtocol::COMMAND_CHECK));
|
|
if (($this->command !== '') && ($this->command !== AssistantProtocol::COMMAND_CHECK)) {
|
|
throw new InvalidArgumentException('Íåèçâåñòíàÿ êîìàíäà óâåäîìëåíèÿ.');
|
|
}
|
|
|
|
$this->accountId = self::required($fields, 'MNT_ID', self::ACCOUNT_ID_MAX_LENGTH);
|
|
$this->transactionId = self::required($fields, 'MNT_TRANSACTION_ID', self::TRANSACTION_ID_MAX_LENGTH);
|
|
$this->operationId = self::optional($fields, 'MNT_OPERATION_ID', self::OPERATION_ID_MAX_LENGTH);
|
|
$this->rawAmount = self::optional($fields, 'MNT_AMOUNT', strlen(Money::MAX));
|
|
$this->amount = $this->rawAmount === '' ? null : Money::fromDecimal($this->rawAmount);
|
|
$this->currency = self::required($fields, 'MNT_CURRENCY_CODE', self::CURRENCY_LENGTH);
|
|
$this->subscriberId = self::optional($fields, 'MNT_SUBSCRIBER_ID', self::SUBSCRIBER_ID_MAX_LENGTH);
|
|
$this->rawTestMode = self::required($fields, 'MNT_TEST_MODE', 1);
|
|
if (($this->rawTestMode !== '0') && ($this->rawTestMode !== '1')) {
|
|
throw new InvalidArgumentException('Íåäîïóñòèìîå çíà÷åíèå MNT_TEST_MODE.');
|
|
}
|
|
$this->signature = self::required($fields, 'MNT_SIGNATURE', self::SIGNATURE_LENGTH);
|
|
|
|
if (
|
|
(($this->amount === null) || (preg_match(self::PAY_OPERATION_ID, $this->operationId) !== 1))
|
|
&& !$this->isCheck()
|
|
) {
|
|
throw new InvalidArgumentException('Óâåäîìëåíèþ îá îïëàòå íóæíû ñóììà è íîìåð îïåðàöèè èç öèôð.');
|
|
}
|
|
}
|
|
|
|
public function isCheck(): bool
|
|
{
|
|
return ($this->command === AssistantProtocol::COMMAND_CHECK);
|
|
}
|
|
|
|
public function getCommand(): string
|
|
{
|
|
return $this->command;
|
|
}
|
|
|
|
public function getAccountId(): string
|
|
{
|
|
return $this->accountId;
|
|
}
|
|
|
|
public function getTransactionId(): string
|
|
{
|
|
return $this->transactionId;
|
|
}
|
|
|
|
public function getOperationId(): string
|
|
{
|
|
return $this->operationId;
|
|
}
|
|
|
|
/**
|
|
* Ñóììà êàê ïðèøëà — òîëüêî äëÿ ïîäïèñè; äëÿ ñâåðêè — {@see self::getAmount()}.
|
|
*/
|
|
public function getRawAmount(): string
|
|
{
|
|
return $this->rawAmount;
|
|
}
|
|
|
|
public function getAmount(): ?Money
|
|
{
|
|
return $this->amount;
|
|
}
|
|
|
|
public function getCurrency(): string
|
|
{
|
|
return $this->currency;
|
|
}
|
|
|
|
public function getSubscriberId(): string
|
|
{
|
|
return $this->subscriberId;
|
|
}
|
|
|
|
public function getRawTestMode(): string
|
|
{
|
|
return $this->rawTestMode;
|
|
}
|
|
|
|
public function isTestMode(): bool
|
|
{
|
|
return $this->rawTestMode === '1';
|
|
}
|
|
|
|
public function isSignedBy(Signature $signature): bool
|
|
{
|
|
return Signature::equals($signature->forNotification($this), $this->signature);
|
|
}
|
|
|
|
/**
|
|
* @param array<string, mixed> $fields
|
|
*/
|
|
private static function required(array $fields, string $name, int $maxLength): string
|
|
{
|
|
$value = self::optional($fields, $name, $maxLength);
|
|
if ($value === '') {
|
|
throw new InvalidArgumentException("Íå ïåðåäàíî ïîëå {$name}.");
|
|
}
|
|
|
|
return $value;
|
|
}
|
|
|
|
/**
|
|
* @param array<string, mixed> $fields
|
|
*/
|
|
private static function optional(array $fields, string $name, int $maxLength): string
|
|
{
|
|
if (!array_key_exists($name, $fields)) {
|
|
return '';
|
|
}
|
|
|
|
$value = $fields[$name];
|
|
if (!is_string($value)) {
|
|
throw new InvalidArgumentException("Ïîëå {$name} äîëæíî áûòü ñòðîêîé.");
|
|
}
|
|
if (strlen($value) > $maxLength) {
|
|
throw new InvalidArgumentException("Ïîëå {$name} äëèííåå äîïóñòèìîãî.");
|
|
}
|
|
|
|
return $value;
|
|
}
|
|
}
|