100 lines
3.7 KiB
PHP
100 lines
3.7 KiB
PHP
<?php
|
||
|
||
declare(strict_types=1);
|
||
|
||
namespace paygw_moneta\local\order;
|
||
|
||
use InvalidArgumentException;
|
||
use paygw_moneta\local\receipt\Receipt;
|
||
use paygw_moneta\local\receipt\Vat;
|
||
|
||
/**
|
||
* Снимок настроек счёта и данных чека на момент создания заказа.
|
||
*
|
||
* Колбэки сверяют уведомление с тем, что реально было запрошено (сумма, режим,
|
||
* данные чека, `MNT_SUBSCRIBER_ID`), а не с текущей ценой курса и профилем. Номер счёта в снимке —
|
||
* дополнительная защита: он обязан совпадать и с текущей настройкой, потому что
|
||
* ключ подписи всегда берётся из неё; заказы, созданные до смены счёта,
|
||
* оплатить нельзя. Секрет (код проверки целостности) в снимок не попадает.
|
||
*
|
||
* @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 OrderSnapshot
|
||
{
|
||
public function __construct(
|
||
public readonly string $accountNumber,
|
||
public readonly bool $testMode,
|
||
public readonly bool $fiscalization,
|
||
public readonly Vat $vat,
|
||
public readonly Receipt $receipt,
|
||
public readonly string $description,
|
||
public readonly string $subscriberId,
|
||
) {
|
||
if ($accountNumber === '') {
|
||
throw new InvalidArgumentException('В снимке заказа нет номера счёта.');
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @return array<string, mixed>
|
||
*/
|
||
public function toArray(): array
|
||
{
|
||
return [
|
||
'accountnumber' => $this->accountNumber,
|
||
'testmode' => $this->testMode,
|
||
'fiscalization' => $this->fiscalization,
|
||
'vat' => $this->vat->value,
|
||
'receipt' => $this->receipt->toArray(),
|
||
'description' => $this->description,
|
||
'subscriberid' => $this->subscriberId,
|
||
];
|
||
}
|
||
|
||
public function toJson(): string
|
||
{
|
||
return json_encode($this->toArray(), JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE);
|
||
}
|
||
|
||
/**
|
||
* @param array<string, mixed> $data
|
||
*/
|
||
public static function fromArray(array $data): self
|
||
{
|
||
return new self(
|
||
accountNumber: (string) ($data['accountnumber'] ?? ''),
|
||
testMode: (bool) ($data['testmode'] ?? false),
|
||
fiscalization: (bool) ($data['fiscalization'] ?? false),
|
||
vat: Vat::from((string) ($data['vat'] ?? '')),
|
||
receipt: Receipt::fromArray(is_array($data['receipt'] ?? null) ? $data['receipt'] : []),
|
||
description: (string) ($data['description'] ?? ''),
|
||
subscriberId: (string) ($data['subscriberid'] ?? ''),
|
||
);
|
||
}
|
||
|
||
public static function fromJson(string $json): self
|
||
{
|
||
$data = json_decode($json, true, 512, JSON_THROW_ON_ERROR);
|
||
if (!is_array($data)) {
|
||
throw new InvalidArgumentException('Снимок заказа повреждён.');
|
||
}
|
||
|
||
return self::fromArray($data);
|
||
}
|
||
|
||
/**
|
||
* Совпадение реквизитов и чека; описание не сравнивается — оно не влияет
|
||
* на оплату и не должно плодить заказы.
|
||
*/
|
||
public function equals(self $other): bool
|
||
{
|
||
$mine = $this->toArray();
|
||
$theirs = $other->toArray();
|
||
unset($mine['description'], $theirs['description']);
|
||
|
||
return $mine === $theirs;
|
||
}
|
||
}
|