*/ public readonly array $items; /** * @param list $items */ public function __construct( public readonly Client $client, array $items, ) { if ($items === []) { throw new InvalidArgumentException('Чек без позиций.'); } foreach ($items as $item) { if (!$item instanceof ReceiptItem) { throw new InvalidArgumentException('Позиция чека должна быть ReceiptItem.'); } } $this->items = array_values($items); } /** * Единственная позиция «доступ к курсу» на всю сумму заказа. */ public static function forCourse(string $courseName, Money $amount, Client $client, Vat $vat): self { return new self($client, [ new ReceiptItem( name: $courseName, price: $amount, quantity: 1, vat: $vat, paymentMethod: PaymentMethod::FullPayment, paymentObject: PaymentObject::Service, ), ]); } public function total(): Money { $total = Money::fromMinorUnits(0); foreach ($this->items as $item) { $total = $total->add($item->total()); } return $total; } public function matchesAmount(Money $amount): bool { return $this->total()->equals($amount); } /** * Объект `receipt` JSON-ответа Check URL. * * @return array{client: array, items: list>} */ public function toCheckReceipt(): array { return [ 'client' => $this->client->toArray(), 'items' => array_map(static fn(ReceiptItem $item): array => $item->toCheckItem(), $this->items), ]; } /** * Значение атрибута `INVENTORY` — JSON-массив позиций. * * @throws \JsonException */ public function toInventoryAttribute(): string { return json_encode( array_map(static fn(ReceiptItem $item): array => $item->toInventoryPosition(), $this->items), JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES, ); } /** * Значение атрибута `CLIENT` — JSON-массив из одного объекта. * * @throws \JsonException */ public function toClientAttribute(): string { return json_encode( [$this->client->toArray()], JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES, ); } /** * Значение атрибута `CUSTOMER` — e-mail покупателя строкой, по * `cmsspecification.pdf`; дублирует `CLIENT.email`, чтобы чек принимался при * любой трактовке документов. `null` — у покупателя * только телефон. */ public function toCustomerAttribute(): ?string { return $this->client->email; } /** * Значение атрибута `PHONE` — телефон покупателя без `+`, как в примере * `cmsspecification.pdf`; дублирует `CLIENT.phone`. `null` — телефона нет. */ public function toPhoneAttribute(): ?string { $phone = $this->client->phone; return $phone === null ? null : ltrim($phone, '+'); } /** * @return array{client: array, items: list>} */ public function toArray(): array { return [ 'client' => $this->client->toArray(), 'items' => array_map(static fn(ReceiptItem $item): array => $item->toArray(), $this->items), ]; } /** * @param array{client?: array, items?: list>} $data */ public static function fromArray(array $data): self { $client = Client::fromArray(is_array($data['client'] ?? null) ? $data['client'] : []); $items = array_map(static fn(array $item): ReceiptItem => ReceiptItem::fromArray($item), $data['items'] ?? []); return new self($client, $items); } }