diff --git a/README.md b/README.md index e852ab5..7148af1 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ > **© Правообладатель:** НКО «МОНЕТА» (ООО) -> **📦 Версия модуля: 1.0.1** +> **📦 Версия модуля: 1.0.2** > **ℹ️ Требования:** > PHP 8.1+ @@ -100,17 +100,19 @@ ### Реквизиты шлюза в платёжном аккаунте -| Поле | Значение | -|---------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| **Номер счёта** | Обязательно. Ваш номер расширенного счёта в системе [MONETA.RU](https://moneta.ru/) | -| **Код проверки целостности данных** | Обязательно. Код проверки целостности данных, указанный в настройках расширенного счёта в системе [MONETA.RU](https://moneta.ru/) | -| **Сервер платежей** | `Боевой` (по умолчанию) / `Демо` (demo.moneta.ru, отдельный номер счёта) | -| **Тестовый режим** | Должен совпадать с флагом «Тестовый режим» в настройках счёта | -| **Email для чеков** | Обязательно. Подставляется в чек, если у покупателя нет ни email, ни телефона — чек придёт на этот адрес, а ФИО покупателя в чеке сохранится | -| **Фискализация чеков** | Фискализация чеков по 54-ФЗ средствами системы [MONETA.RU](https://moneta.ru/) сервисом [kassa.payanyway.ru](https://kassa.payanyway.ru/) | -| **Ставка НДС** | Ставка для позиции чека; по умолчанию «Без НДС» | -| **Отправлять ссылку оплаты на почту** | При создании заказа покупателю приходит сообщение со ссылкой на оплату — можно оплатить позже (по умолчанию включено) | -| **URL для уведомлений** | `https://<сайт>/payment/gateway/moneta/callback.php` — его нужно указать в настройках расширенного счёта `Check URL` / `Pay URL` в системе [MONETA.RU](https://moneta.ru/) | +| Поле | Значение | +|---------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| **Номер счёта** | Обязательно. Ваш номер расширенного счёта в системе [MONETA.RU](https://moneta.ru/) | +| **Код проверки целостности данных** | Обязательно. Код проверки целостности данных, указанный в настройках расширенного счёта в системе [MONETA.RU](https://moneta.ru/) | +| **Сервер платежей** | `Боевой` (по умолчанию) / `Демо` (demo.moneta.ru, отдельный номер счёта) | +| **Тестовый режим** | Должен совпадать с флагом «Тестовый режим» в настройках счёта | +| **Фискализация чеков** | Фискализация чеков по 54-ФЗ средствами системы [MONETA.RU](https://moneta.ru/) сервисом [kassa.payanyway.ru](https://kassa.payanyway.ru/) | +| **Email для чеков** | Обязательно при фискализации (без неё поле скрыто). Подставляется в чек, если у покупателя нет ни email, ни телефона — чек придёт на этот адрес, а ФИО покупателя в чеке сохранится | +| **Ставка НДС** | Ставка для позиции чека при фискализации (без неё поле скрыто); по умолчанию «Без НДС» | +| **Признак способа расчёта** | Для чека при фискализации (без неё поле скрыто); по умолчанию «Полный расчёт» | +| **Признак предмета расчёта** | Для чека при фискализации (без неё поле скрыто); по умолчанию «Услуга». Курсы и сертификаты можно разнести по разным платёжным аккаунтам | +| **Отправлять ссылку оплаты на почту** | При создании заказа покупателю приходит сообщение со ссылкой на оплату — можно оплатить позже (по умолчанию включено) | +| **URL для уведомлений** | `https://<сайт>/payment/gateway/moneta/callback.php` — его нужно указать в настройках расширенного счёта `Check URL` / `Pay URL` в системе [MONETA.RU](https://moneta.ru/) | ## 🔧 Настройка счёта в личном кабинете [MONETA.RU](https://moneta.ru/) diff --git a/classes/gateway.php b/classes/gateway.php index c198189..18bde93 100644 --- a/classes/gateway.php +++ b/classes/gateway.php @@ -8,6 +8,8 @@ use core_payment\form\account_gateway; use paygw_moneta\local\protocol\AssistantProtocol; use paygw_moneta\local\protocol\PaymentServer; use paygw_moneta\local\receipt\Client; +use paygw_moneta\local\receipt\PaymentMethod; +use paygw_moneta\local\receipt\PaymentObject; use paygw_moneta\local\receipt\Vat; use stdClass; @@ -42,7 +44,7 @@ class gateway extends \core_payment\gateway $servers = []; foreach (PaymentServer::cases() as $server) { - $servers[$server->value] = get_string($server->label(), 'paygw_moneta'); + $servers[$server->value] = get_string($server->langKey(), 'paygw_moneta'); } $mform->addElement('select', 'paymentserver', get_string('paymentserver', 'paygw_moneta'), $servers); $mform->setDefault('paymentserver', PaymentServer::Prod->value); @@ -51,20 +53,41 @@ class gateway extends \core_payment\gateway $mform->addElement('advcheckbox', 'testmode', get_string('testmode', 'paygw_moneta')); $mform->addHelpButton('testmode', 'testmode', 'paygw_moneta'); - $mform->addElement('text', 'receiptemail', get_string('receiptemail', 'paygw_moneta')); - $mform->setType('receiptemail', PARAM_RAW_TRIMMED); - $mform->addRule('receiptemail', null, 'required', null, 'client'); - $mform->addHelpButton('receiptemail', 'receiptemail', 'paygw_moneta'); - $mform->addElement('advcheckbox', 'fiscalization', get_string('fiscalization', 'paygw_moneta')); $mform->addHelpButton('fiscalization', 'fiscalization', 'paygw_moneta'); + // Поля чека нужны только при фискализации. Скрываются, а не блокируются: + // заблокированное поле браузер не отправляет, и значение потерялось бы. + $mform->addElement('text', 'receiptemail', get_string('receiptemail', 'paygw_moneta')); + $mform->setType('receiptemail', PARAM_RAW_TRIMMED); + $mform->addHelpButton('receiptemail', 'receiptemail', 'paygw_moneta'); + $mform->hideIf('receiptemail', 'fiscalization', 'notchecked'); + $vatOptions = []; foreach (Vat::cases() as $vat) { $vatOptions[$vat->value] = get_string($vat->langKey(), 'paygw_moneta'); } $mform->addElement('select', 'vat', get_string('vat', 'paygw_moneta'), $vatOptions); $mform->setDefault('vat', Vat::None->value); + $mform->hideIf('vat', 'fiscalization', 'notchecked'); + + $methods = []; + foreach (PaymentMethod::cases() as $method) { + $methods[$method->value] = get_string($method->langKey(), 'paygw_moneta'); + } + $mform->addElement('select', 'paymentmethod', get_string('paymentmethod', 'paygw_moneta'), $methods); + $mform->setDefault('paymentmethod', PaymentMethod::FullPayment->value); + $mform->addHelpButton('paymentmethod', 'paymentmethod', 'paygw_moneta'); + $mform->hideIf('paymentmethod', 'fiscalization', 'notchecked'); + + $objects = []; + foreach (PaymentObject::cases() as $object) { + $objects[$object->value] = get_string($object->langKey(), 'paygw_moneta'); + } + $mform->addElement('select', 'paymentobject', get_string('paymentobject', 'paygw_moneta'), $objects); + $mform->setDefault('paymentobject', PaymentObject::Service->value); + $mform->addHelpButton('paymentobject', 'paymentobject', 'paygw_moneta'); + $mform->hideIf('paymentobject', 'fiscalization', 'notchecked'); $mform->addElement('advcheckbox', 'sendpaymentlink', get_string('sendpaymentlink', 'paygw_moneta')); $mform->addHelpButton('sendpaymentlink', 'sendpaymentlink', 'paygw_moneta'); @@ -87,8 +110,10 @@ class gateway extends \core_payment\gateway $errors['accountnumber'] = get_string('error:accountnumber', 'paygw_moneta'); } + // «Email для чеков» обязателен только при фискализации, но мусор не сохраняем и без неё. $receiptEmail = trim((string) ($data->receiptemail ?? '')); - if (Client::normalizeEmail($receiptEmail) === null) { + $required = !empty($data->fiscalization); + if (($required || $receiptEmail !== '') && Client::normalizeEmail($receiptEmail) === null) { $errors['receiptemail'] = get_string('error:receiptemail', 'paygw_moneta'); } diff --git a/classes/local/GatewayConfig.php b/classes/local/GatewayConfig.php index 8d77e7a..6950df7 100644 --- a/classes/local/GatewayConfig.php +++ b/classes/local/GatewayConfig.php @@ -7,6 +7,8 @@ namespace paygw_moneta\local; use paygw_moneta\local\protocol\PaymentServer; use paygw_moneta\local\protocol\Signature; use paygw_moneta\local\receipt\Client; +use paygw_moneta\local\receipt\PaymentMethod; +use paygw_moneta\local\receipt\PaymentObject; use paygw_moneta\local\receipt\Vat; /** @@ -34,6 +36,8 @@ final class GatewayConfig public readonly bool $enabled, public readonly ?string $receiptEmail = null, public readonly bool $sendPaymentLink = false, + public readonly PaymentMethod $paymentMethod = PaymentMethod::FullPayment, + public readonly PaymentObject $paymentObject = PaymentObject::Service, ) {} /** @@ -51,18 +55,22 @@ final class GatewayConfig $enabled, Client::normalizeEmail((string) ($config['receiptemail'] ?? '')), !empty($config['sendpaymentlink']), + PaymentMethod::tryFrom((string) ($config['paymentmethod'] ?? '')) ?? PaymentMethod::FullPayment, + PaymentObject::tryFrom((string) ($config['paymentobject'] ?? '')) ?? PaymentObject::Service, ); } /** - * Можно ли создавать новые заказы: реквизиты и «Email для чеков», без - * которого чек покупателя без контактов не собрать. Колбэки проверяют - * только {@see isComplete()} — уже оплаченный заказ должен дойти до - * зачисления, даже если настройку потом очистили. + * Можно ли создавать новые заказы: реквизиты, а при фискализации ещё и + * «Email для чеков», без которого чек покупателя без контактов не собрать. + * Колбэки проверяют только {@see isComplete()} — уже оплаченный заказ + * должен дойти до зачисления, даже если настройку потом очистили. */ public function acceptsNewOrders(): bool { - return $this->isComplete() && $this->enabled && $this->receiptEmail !== null; + return $this->isComplete() + && $this->enabled + && (!$this->fiscalization || $this->receiptEmail !== null); } public function isComplete(): bool diff --git a/classes/local/callback/CheckHandler.php b/classes/local/callback/CheckHandler.php index 916f9c7..62254df 100644 --- a/classes/local/callback/CheckHandler.php +++ b/classes/local/callback/CheckHandler.php @@ -56,7 +56,7 @@ final class CheckHandler implements NotificationHandler ); if (!$order->snapshot->fiscalization) { - return $response->toXml(); + return $response->toXml($order->snapshot->receipt); } try { diff --git a/classes/local/callback/PayHandler.php b/classes/local/callback/PayHandler.php index 120ca14..2b43262 100644 --- a/classes/local/callback/PayHandler.php +++ b/classes/local/callback/PayHandler.php @@ -146,6 +146,7 @@ final class PayHandler implements NotificationHandler signature: $config->signature(), cms: CmsInfo::getCmsModuleVersion(), receipt: $receipt, + fiscalization: $order->snapshot->fiscalization, ); return $payResponse->toXml(); diff --git a/classes/local/order/OrderFactory.php b/classes/local/order/OrderFactory.php index 240739a..2968161 100644 --- a/classes/local/order/OrderFactory.php +++ b/classes/local/order/OrderFactory.php @@ -76,15 +76,22 @@ final class OrderFactory email: (string) ($user->email ?? ''), phone: self::phone($user), name: self::fullName($user), - fallbackEmail: $config->receiptEmail, - ) ?? throw new moodle_exception('gatewaynotfound', 'payment'); + fallbackEmail: $config->fiscalization ? $config->receiptEmail : null, + ); $snapshot = new OrderSnapshot( accountNumber: $config->accountNumber, testMode: $config->testMode, fiscalization: $config->fiscalization, vat: $config->vat, - receipt: Receipt::forCourse($itemName, $amount, $client, $config->vat), + receipt: Receipt::forCourse( + $itemName, + $amount, + $client, + $config->vat, + $config->paymentMethod, + $config->paymentObject, + ), description: $itemName, subscriberId: self::subscriberId($user), ); diff --git a/classes/local/protocol/CheckResponse.php b/classes/local/protocol/CheckResponse.php index 7c2479a..c40d645 100644 --- a/classes/local/protocol/CheckResponse.php +++ b/classes/local/protocol/CheckResponse.php @@ -31,9 +31,12 @@ final class CheckResponse } /** - * XML без атрибутов — для мерчанта без кассового сервиса. + * XML — для мерчанта без кассового сервиса + * + * @throws \DOMException + * @throws \JsonException */ - public function toXml(): CallbackResponse + public function toXml(?Receipt $receipt = null): CallbackResponse { return CallbackResponse::xml( MntResponseXml::build( @@ -43,6 +46,7 @@ final class CheckResponse amount: $this->amount, signature: $this->sign(), cms: $this->cms, + attributes: $receipt?->toResponseAttributes(fiscalization: false) ?? [], ), ); } diff --git a/classes/local/protocol/PayResponse.php b/classes/local/protocol/PayResponse.php index 9f69eda..8100535 100644 --- a/classes/local/protocol/PayResponse.php +++ b/classes/local/protocol/PayResponse.php @@ -22,6 +22,7 @@ final class PayResponse private readonly Signature $signature, private readonly string $cms, private readonly ?Receipt $receipt = null, + private readonly bool $fiscalization = false, ) {} /** @@ -30,17 +31,6 @@ final class PayResponse */ public function toXml(): CallbackResponse { - $attributes = []; - if ($this->receipt !== null) { - $attributes['INVENTORY'] = $this->receipt->toInventoryAttribute(); - $attributes['CLIENT'] = $this->receipt->toClientAttribute(); - $duplicates = [ - 'CUSTOMER' => $this->receipt->toCustomerAttribute(), - 'PHONE' => $this->receipt->toPhoneAttribute(), - ]; - $attributes += array_filter($duplicates, static fn(?string $value): bool => $value !== null); - } - return CallbackResponse::xml( MntResponseXml::build( accountId: $this->accountId, @@ -49,7 +39,7 @@ final class PayResponse amount: $this->amount, signature: $this->signature->forResponse($this->resultCode, $this->accountId, $this->transactionId), cms: $this->cms, - attributes: $attributes, + attributes: $this->receipt?->toResponseAttributes($this->fiscalization) ?? [], ), ); } diff --git a/classes/local/protocol/PaymentServer.php b/classes/local/protocol/PaymentServer.php index 8738c9c..8343d4e 100644 --- a/classes/local/protocol/PaymentServer.php +++ b/classes/local/protocol/PaymentServer.php @@ -26,9 +26,9 @@ enum PaymentServer: string } /** - * Ключ строки интерфейса (`server_prod`, `server_demo`). + * Ключ строки в `lang/{ru,en}/paygw_moneta.php` (`server_prod`, `server_demo`). */ - public function label(): string + public function langKey(): string { return 'server_' . strtolower($this->value); } diff --git a/classes/local/receipt/PaymentMethod.php b/classes/local/receipt/PaymentMethod.php index 5d003db..f3c1140 100644 --- a/classes/local/receipt/PaymentMethod.php +++ b/classes/local/receipt/PaymentMethod.php @@ -5,7 +5,7 @@ declare(strict_types=1); namespace paygw_moneta\local\receipt; /** - * Признак способа расчёта (`pm` / `paymentMethod`), перечень из `cmsspecification.pdf`. + * Признак способа расчёта (`pm` / `paymentMethod`), перечень из `kassaspecification.pdf`. * * @package paygw_moneta * @copyright 2026 Moneta Labs {@link https://moneta.ru/} @@ -20,4 +20,9 @@ enum PaymentMethod: string case PartialPayment = 'partial_payment'; case Credit = 'credit'; case CreditPayment = 'credit_payment'; + + public function langKey(): string + { + return 'paymentmethod:' . $this->value; + } } diff --git a/classes/local/receipt/PaymentObject.php b/classes/local/receipt/PaymentObject.php index b660f38..097089d 100644 --- a/classes/local/receipt/PaymentObject.php +++ b/classes/local/receipt/PaymentObject.php @@ -5,7 +5,10 @@ declare(strict_types=1); namespace paygw_moneta\local\receipt; /** - * Признак предмета расчёта (`po` / `paymentObject`), перечень из `cmsspecification.pdf`. + * Признак предмета расчёта (`po` / `paymentObject`): перечень `kassaspecification.pdf` + * плюс `composite` из `cmsspecification.pdf`. + * Внереализационный доход — `non_operating`, как у кассы, а не + * `non-operating_gain` из `cmsspecification.pdf`. * * @package paygw_moneta * @copyright 2026 Moneta Labs {@link https://moneta.ru/} @@ -29,4 +32,12 @@ enum PaymentObject: string case PropertyRight = 'property_right'; case SalesTax = 'sales_tax'; case ResortFee = 'resort_fee'; + case Award = 'award'; + case NonOperating = 'non_operating'; + case InsurancePremium = 'insurance_premium'; + + public function langKey(): string + { + return 'paymentobject:' . $this->value; + } } diff --git a/classes/local/receipt/Receipt.php b/classes/local/receipt/Receipt.php index e132de4..18fd6bb 100644 --- a/classes/local/receipt/Receipt.php +++ b/classes/local/receipt/Receipt.php @@ -10,6 +10,8 @@ use paygw_moneta\local\Money; /** * Чек 54-ФЗ: покупатель и позиции. Сумма позиций обязана совпадать с суммой * операции до копейки — иначе номенклатура не пройдёт валидацию кассы. + * Покупателя нет (`null`), если у него нет ни e-mail, ни телефона, а «Email + * для чеков» не подставлялся: атрибуты покупателя тогда опускаются. * * @package paygw_moneta * @copyright 2026 Moneta Labs {@link https://moneta.ru/} @@ -24,7 +26,7 @@ final class Receipt * @param list $items */ public function __construct( - public readonly Client $client, + public readonly ?Client $client, array $items, ) { if ($items === []) { @@ -40,18 +42,25 @@ final class Receipt } /** - * Единственная позиция «доступ к курсу» на всю сумму заказа. + * Единственная позиция на всю сумму заказа — доступ к курсу или иной + * оплачиваемый объект Moodle; признаки расчёта — из настроек шлюза. */ - public static function forCourse(string $courseName, Money $amount, Client $client, Vat $vat): self - { + public static function forCourse( + string $courseName, + Money $amount, + ?Client $client, + Vat $vat, + PaymentMethod $paymentMethod = PaymentMethod::FullPayment, + PaymentObject $paymentObject = PaymentObject::Service, + ): self { return new self($client, [ new ReceiptItem( name: $courseName, price: $amount, quantity: 1, vat: $vat, - paymentMethod: PaymentMethod::FullPayment, - paymentObject: PaymentObject::Service, + paymentMethod: $paymentMethod, + paymentObject: $paymentObject, ), ]); } @@ -72,38 +81,67 @@ final class Receipt } /** - * Объект `receipt` JSON-ответа Check URL. + * Объект `receipt` JSON-ответа Check URL; без покупателя `client` — пустой + * объект `{}`, как требует схема. * - * @return array{client: array, items: list>} + * @return array{client: array|\stdClass, items: list>} */ public function toCheckReceipt(): array { return [ - 'client' => $this->client->toArray(), + 'client' => $this->client?->toArray() ?? new \stdClass(), 'items' => array_map(static fn(ReceiptItem $item): array => $item->toCheckItem(), $this->items), ]; } + /** + * `MNT_ATTRIBUTES` XML-ответа на Check URL и Pay URL: `INVENTORY`, `CLIENT` + * и дубли покупателя `CUSTOMER`/`PHONE`. Без фискализации позиции идут без + * `vatTag`/`pm`/`po`. + * + * @return array + * @throws \JsonException + */ + public function toResponseAttributes(bool $fiscalization): array + { + $attributes = [ + 'INVENTORY' => $this->toInventoryAttribute($fiscalization), + 'CLIENT' => $this->toClientAttribute(), + 'CUSTOMER' => $this->toCustomerAttribute(), + 'PHONE' => $this->toPhoneAttribute(), + ]; + + return array_filter($attributes, static fn(?string $value): bool => $value !== null); + } + /** * Значение атрибута `INVENTORY` — JSON-массив позиций. * * @throws \JsonException */ - public function toInventoryAttribute(): string + public function toInventoryAttribute(bool $fiscalization): string { return json_encode( - array_map(static fn(ReceiptItem $item): array => $item->toInventoryPosition(), $this->items), + array_map( + static fn(ReceiptItem $item): array => $item->toInventoryPosition($fiscalization), + $this->items, + ), JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES, ); } /** - * Значение атрибута `CLIENT` — JSON-массив из одного объекта. + * Значение атрибута `CLIENT` — JSON-массив из одного объекта. `null` — + * покупателя нет. * * @throws \JsonException */ - public function toClientAttribute(): string + public function toClientAttribute(): ?string { + if ($this->client === null) { + return null; + } + return json_encode( [$this->client->toArray()], JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES, @@ -114,11 +152,11 @@ final class Receipt * Значение атрибута `CUSTOMER` — e-mail покупателя строкой, по * `cmsspecification.pdf`; дублирует `CLIENT.email`, чтобы чек принимался при * любой трактовке документов. `null` — у покупателя - * только телефон. + * только телефон или покупателя нет. */ public function toCustomerAttribute(): ?string { - return $this->client->email; + return $this->client?->email; } /** @@ -127,18 +165,18 @@ final class Receipt */ public function toPhoneAttribute(): ?string { - $phone = $this->client->phone; + $phone = $this->client?->phone; return $phone === null ? null : ltrim($phone, '+'); } /** - * @return array{client: array, items: list>} + * @return array{client: ?array, items: list>} */ public function toArray(): array { return [ - 'client' => $this->client->toArray(), + 'client' => $this->client?->toArray(), 'items' => array_map(static fn(ReceiptItem $item): array => $item->toArray(), $this->items), ]; } @@ -148,7 +186,7 @@ final class Receipt */ public static function fromArray(array $data): self { - $client = Client::fromArray(is_array($data['client'] ?? null) ? $data['client'] : []); + $client = is_array($data['client'] ?? null) ? Client::fromArray($data['client']) : null; $items = array_map(static fn(array $item): ReceiptItem => ReceiptItem::fromArray($item), $data['items'] ?? []); return new self($client, $items); diff --git a/classes/local/receipt/ReceiptItem.php b/classes/local/receipt/ReceiptItem.php index d15742e..0a593be 100644 --- a/classes/local/receipt/ReceiptItem.php +++ b/classes/local/receipt/ReceiptItem.php @@ -77,17 +77,25 @@ final class ReceiptItem /** * Объект `inventPositions` для атрибута `INVENTORY`: цена и количество — - * строками, как в примере спецификации Moneta. + * строками, как в примере спецификации Moneta. `vatTag`/`pm`/`po` — только + * при фискализации: без кассы чек не пробивается, Moneta лишь сохраняет + * позиции в операции. * * @return array */ - public function toInventoryPosition(): array + public function toInventoryPosition(bool $fiscalization): array { - return [ + $position = [ 'name' => $this->name, 'price' => $this->price->toDecimal(), 'quantity' => (string) $this->quantity, 'measure' => self::MEASURE, + ]; + if (!$fiscalization) { + return $position; + } + + return $position + [ 'vatTag' => $this->vat->tag(), 'pm' => $this->paymentMethod->value, 'po' => $this->paymentObject->value, diff --git a/classes/privacy/provider.php b/classes/privacy/provider.php index 78c321d..7dec049 100644 --- a/classes/privacy/provider.php +++ b/classes/privacy/provider.php @@ -168,7 +168,7 @@ class provider implements 'amount' => $order->amount->toDecimal() . ' ' . $order->currency, 'status' => $order->status->value, 'description' => $order->snapshot->description, - 'client' => $order->snapshot->receipt->client->toArray(), + 'client' => $order->snapshot->receipt->client?->toArray() ?? [], 'timecreated' => transform::datetime($order->timeCreated), 'timecompleted' => $order->timeCompleted === null ? null : transform::datetime($order->timeCompleted), ]; diff --git a/lang/en/paygw_moneta.php b/lang/en/paygw_moneta.php index eb79dbb..2a32f3c 100644 --- a/lang/en/paygw_moneta.php +++ b/lang/en/paygw_moneta.php @@ -41,10 +41,10 @@ $string['server_prod'] = 'Production'; $string['testmode'] = 'Test mode'; $string['testmode_help'] = 'Simulated payment without charging money. Must match the account setting in MONETA.RU'; $string['receiptemail'] = 'Receipt email'; -$string['receiptemail_help'] = 'Used in the receipt only when the buyer has neither an email nor a phone in their profile: the fiscal service rejects a receipt without a contact, so it is sent to this address instead (the buyer name stays in the receipt)'; +$string['receiptemail_help'] = 'Required with fiscal receipts. Used in the receipt only when the buyer has neither an email nor a phone in their profile: the fiscal service rejects a receipt without a contact, so it is sent to this address instead (the buyer name stays in the receipt)'; $string['error:receiptemail'] = 'Enter a valid email address of at most 64 characters'; $string['fiscalization'] = 'Fiscal receipts'; -$string['fiscalization_help'] = 'Enable only if the kassa.payanyway.ru service is connected to the account: the check request response will contain receipt data'; +$string['fiscalization_help'] = 'Enable only if the kassa.payanyway.ru service is connected to the account'; $string['vat'] = 'VAT rate'; $string['vat:none'] = 'No VAT'; $string['vat:vat0'] = 'VAT 0%'; @@ -58,6 +58,36 @@ $string['vat:vat107'] = 'VAT 7% (calculated rate 7/107)'; $string['vat:vat110'] = 'VAT 10% (calculated rate 10/110)'; $string['vat:vat120'] = 'VAT 20% (calculated rate 20/120)'; $string['vat:vat122'] = 'VAT 22% (calculated rate 22/122)'; +$string['paymentmethod'] = 'Payment method'; +$string['paymentmethod_help'] = 'Sent in the fiscal receipt. A Moodle payment always covers the full price: usually "Full payment"; "Full prepayment" or "Advance" if the item is delivered after payment'; +$string['paymentmethod:full_prepayment'] = 'Full prepayment'; +$string['paymentmethod:prepayment'] = 'Partial prepayment'; +$string['paymentmethod:advance'] = 'Advance'; +$string['paymentmethod:full_payment'] = 'Full payment'; +$string['paymentmethod:partial_payment'] = 'Partial payment and credit'; +$string['paymentmethod:credit'] = 'Credit'; +$string['paymentmethod:credit_payment'] = 'Credit payment'; +$string['paymentobject'] = 'Payment object'; +$string['paymentobject_help'] = 'Sent in the fiscal receipt. Course access is usually "Service". Different kinds of items (courses, certificates) can be split across Moodle payment accounts, each with its own value'; +$string['paymentobject:commodity'] = 'Commodity'; +$string['paymentobject:excise'] = 'Excise commodity'; +$string['paymentobject:job'] = 'Job'; +$string['paymentobject:service'] = 'Service'; +$string['paymentobject:gambling_bet'] = 'Gambling bet'; +$string['paymentobject:gambling_prize'] = 'Gambling prize'; +$string['paymentobject:lottery'] = 'Lottery ticket'; +$string['paymentobject:lottery_prize'] = 'Lottery prize'; +$string['paymentobject:intellectual_activity'] = 'Intellectual property'; +$string['paymentobject:payment'] = 'Payment (advance, deposit, prepayment, credit)'; +$string['paymentobject:agent_commission'] = 'Agent commission'; +$string['paymentobject:composite'] = 'Composite payment object'; +$string['paymentobject:another'] = 'Other payment object'; +$string['paymentobject:property_right'] = 'Property right'; +$string['paymentobject:sales_tax'] = 'Sales tax'; +$string['paymentobject:resort_fee'] = 'Resort fee'; +$string['paymentobject:award'] = 'Contribution, penalty, fine, reward, bonus'; +$string['paymentobject:non_operating'] = 'Non-operating income'; +$string['paymentobject:insurance_premium'] = 'Insurance premium'; $string['sendpaymentlink'] = 'Send payment link by email'; $string['sendpaymentlink_help'] = 'If enabled, the buyer receives a message with a payment link when an order is created, so they can pay later. The link works while the order is unpaid and the price and credentials are unchanged. The channel (email, site notification) follows Moodle notification preferences'; $string['callbackurl'] = 'Notification URL'; diff --git a/lang/ru/paygw_moneta.php b/lang/ru/paygw_moneta.php index e233ec5..4006e2c 100644 --- a/lang/ru/paygw_moneta.php +++ b/lang/ru/paygw_moneta.php @@ -41,10 +41,10 @@ $string['server_prod'] = 'Боевой'; $string['testmode'] = 'Тестовый режим'; $string['testmode_help'] = 'Имитация оплаты без реального списания средств. Режим должен совпадать с настройкой счёта в MONETA.RU'; $string['receiptemail'] = 'Email для чеков'; -$string['receiptemail_help'] = 'Подставляется в чек, только если у покупателя в профиле нет ни e-mail, ни телефона: без контакта касса чек не примет, поэтому он уйдёт на этот адрес (ФИО покупателя в чеке остаётся)'; +$string['receiptemail_help'] = 'Обязателен при фискализации. Подставляется в чек, только если у покупателя в профиле нет ни e-mail, ни телефона: без контакта касса чек не примет, поэтому он уйдёт на этот адрес (ФИО покупателя в чеке остаётся)'; $string['error:receiptemail'] = 'Укажите корректный e-mail не длиннее 64 символов'; $string['fiscalization'] = 'Фискализация чеков'; -$string['fiscalization_help'] = 'Включайте только если к счёту подключён кассовый сервис kassa.payanyway.ru: ответ на проверочный запрос будет содержать данные чека по 54-ФЗ'; +$string['fiscalization_help'] = 'Включайте только если к счёту подключён кассовый сервис kassa.payanyway.ru'; $string['vat'] = 'Ставка НДС'; $string['vat:none'] = 'Без НДС'; $string['vat:vat0'] = 'НДС 0%'; @@ -58,6 +58,36 @@ $string['vat:vat107'] = 'НДС 7% (расчётная ставка 7/107)'; $string['vat:vat110'] = 'НДС 10% (расчётная ставка 10/110)'; $string['vat:vat120'] = 'НДС 20% (расчётная ставка 20/120)'; $string['vat:vat122'] = 'НДС 22% (расчётная ставка 22/122)'; +$string['paymentmethod'] = 'Признак способа расчёта'; +$string['paymentmethod_help'] = 'Передаётся в чек при фискализации. Оплата в Moodle всегда на полную стоимость: обычно «Полный расчёт»; «Полная предоплата» или «Аванс» — если предмет расчёта передаётся позже оплаты'; +$string['paymentmethod:full_prepayment'] = 'Полная предоплата'; +$string['paymentmethod:prepayment'] = 'Частичная предоплата'; +$string['paymentmethod:advance'] = 'Аванс'; +$string['paymentmethod:full_payment'] = 'Полный расчёт'; +$string['paymentmethod:partial_payment'] = 'Частичный расчёт и кредит'; +$string['paymentmethod:credit'] = 'Передача в кредит'; +$string['paymentmethod:credit_payment'] = 'Оплата кредита'; +$string['paymentobject'] = 'Признак предмета расчёта'; +$string['paymentobject_help'] = 'Передаётся в чек при фискализации. Для доступа к курсу обычно «Услуга». Разные виды товаров (курсы, сертификаты) можно разнести по разным платёжным аккаунтам Moodle со своим признаком в каждом'; +$string['paymentobject:commodity'] = 'Товар'; +$string['paymentobject:excise'] = 'Подакцизный товар'; +$string['paymentobject:job'] = 'Работа'; +$string['paymentobject:service'] = 'Услуга'; +$string['paymentobject:gambling_bet'] = 'Ставка азартной игры'; +$string['paymentobject:gambling_prize'] = 'Выигрыш азартной игры'; +$string['paymentobject:lottery'] = 'Лотерейный билет'; +$string['paymentobject:lottery_prize'] = 'Выигрыш лотереи'; +$string['paymentobject:intellectual_activity'] = 'Результаты интеллектуальной деятельности'; +$string['paymentobject:payment'] = 'Платёж (аванс, задаток, предоплата, кредит)'; +$string['paymentobject:agent_commission'] = 'Агентское вознаграждение'; +$string['paymentobject:composite'] = 'Составной предмет расчёта'; +$string['paymentobject:another'] = 'Иной предмет расчёта'; +$string['paymentobject:property_right'] = 'Имущественное право'; +$string['paymentobject:sales_tax'] = 'Торговый сбор'; +$string['paymentobject:resort_fee'] = 'Курортный сбор'; +$string['paymentobject:award'] = 'Взнос в счёт оплаты, пени, штраф, вознаграждение, бонус'; +$string['paymentobject:non_operating'] = 'Внереализационный доход'; +$string['paymentobject:insurance_premium'] = 'Страховой сбор'; $string['sendpaymentlink'] = 'Отправлять ссылку оплаты на почту'; $string['sendpaymentlink_help'] = 'Если включено, при создании заказа покупателю приходит сообщение со ссылкой на оплату — оплатить можно позже. Ссылка действует, пока заказ не оплачен и не изменились цена или реквизиты. Канал (почта, уведомление на сайте) — по настройкам уведомлений Moodle'; $string['callbackurl'] = 'URL для уведомлений'; diff --git a/version.php b/version.php index 54e39f3..395eb71 100644 --- a/version.php +++ b/version.php @@ -25,7 +25,7 @@ defined('MOODLE_INTERNAL') || die(); $plugin->component = 'paygw_moneta'; -$plugin->version = 2026100100; +$plugin->version = 2026100802; $plugin->requires = 2023100900; $plugin->maturity = MATURITY_STABLE; -$plugin->release = '1.0.1'; +$plugin->release = '1.0.2';