acceptsNewOrders()) { throw new moodle_exception('gatewaynotfound', 'payment'); } // Оплата у сервиса прошла, доставка сорвалась: новый заказ создавать нельзя, // иначе покупатель заплатит дважды, а повтор уведомления по старому доставит его. if ($this->repository->findFailedForItem((int) $user->id, $component, $paymentArea, $itemId) !== null) { throw new moodle_exception('error:orderinprogress', 'paygw_moneta'); } $payable = helper::get_payable($component, $paymentArea, $itemId); $currency = $payable->get_currency(); if ($currency !== AssistantProtocol::CURRENCY) { throw new moodle_exception('error:unsupportedcurrency', 'paygw_moneta'); } $surcharge = helper::get_gateway_surcharge(GatewayConfig::GATEWAY); $amount = Money::fromFloat(helper::get_rounded_cost($payable->get_amount(), $currency, $surcharge)); if (!$amount->isPositive()) { throw new moodle_exception('error:invalidamount', 'paygw_moneta'); } $itemName = self::itemName($component, $paymentArea, $itemId); $client = Client::fromContacts( email: (string) ($user->email ?? ''), phone: self::phone($user), name: self::fullName($user), fallbackEmail: $config->receiptEmail, ) ?? throw new moodle_exception('gatewaynotfound', 'payment'); $snapshot = new OrderSnapshot( accountNumber: $config->accountNumber, testMode: $config->testMode, fiscalization: $config->fiscalization, vat: $config->vat, receipt: Receipt::forCourse($itemName, $amount, $client, $config->vat), description: $itemName, subscriberId: self::subscriberId($user), ); return $this->repository->obtain( accountId: $payable->get_account_id(), userId: (int) $user->id, component: $component, paymentArea: $paymentArea, itemId: $itemId, amount: $amount, currency: $currency, snapshot: $snapshot, created: $created, ); } private static function itemName(string $component, string $paymentArea, int $itemId): string { global $DB; if ($component === 'enrol_fee' && $paymentArea === 'fee') { $courseId = $DB->get_field('enrol', 'courseid', ['id' => $itemId, 'enrol' => 'fee']); $name = $courseId ? $DB->get_field('course', 'fullname', ['id' => $courseId]) : false; if (is_string($name) && ReceiptText::sanitize($name) !== '') { return $name; } } return get_string('genericitem', 'paygw_moneta'); } public static function subscriberId(stdClass $user): string { $email = trim((string) ($user->email ?? '')); if ($email !== '' && filter_var($email, FILTER_VALIDATE_EMAIL) !== false) { return $email; } return Client::normalizePhone(self::phone($user)) ?? (string) $user->id; } /** * Первый заполненный телефон профиля: рабочий, затем мобильный. */ private static function phone(stdClass $user): string { $phone = trim((string) ($user->phone1 ?? '')); return $phone !== '' ? $phone : trim((string) ($user->phone2 ?? '')); } /** * ФИО для чека в порядке «Фамилия Имя Отчество» — как `name` объекта * `client` в `kassaspecification.pdf`, независимо от настройки отображения имён. */ private static function fullName(stdClass $user): string { $parts = array_filter( array_map( static fn(string $field): string => trim((string) ($user->$field ?? '')), ['lastname', 'firstname', 'middlename'], ), static fn(string $part): bool => $part !== '', ); return implode(' ', $parts); } }