2 Commits
Author SHA1 Message Date
ivan.sadovin 9886c97876 Merge branch 'main' into 'release'
feat: paygw_moneta 1.0.2, receipt attributes by fiscalization, pm/po settings

See merge request products/cashier/cms/Moodle!3
2026-10-12 13:33:28 +03:00
ivan.sadovin e4e04236cc feat: paygw_moneta 1.0.2, receipt attributes by fiscalization, pm/po settings 2026-10-12 13:24:13 +03:00
17 changed files with 234 additions and 75 deletions
+14 -12
View File
@@ -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/)
+32 -7
View File
@@ -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');
}
+13 -5
View File
@@ -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
+1 -1
View File
@@ -56,7 +56,7 @@ final class CheckHandler implements NotificationHandler
);
if (!$order->snapshot->fiscalization) {
return $response->toXml();
return $response->toXml($order->snapshot->receipt);
}
try {
+1
View File
@@ -146,6 +146,7 @@ final class PayHandler implements NotificationHandler
signature: $config->signature(),
cms: CmsInfo::getCmsModuleVersion(),
receipt: $receipt,
fiscalization: $order->snapshot->fiscalization,
);
return $payResponse->toXml();
+10 -3
View File
@@ -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),
);
+6 -2
View File
@@ -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) ?? [],
),
);
}
+2 -12
View File
@@ -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) ?? [],
),
);
}
+2 -2
View File
@@ -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);
}
+6 -1
View File
@@ -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;
}
}
+12 -1
View File
@@ -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;
}
}
+57 -19
View File
@@ -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<ReceiptItem> $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<string, string>, items: list<array<string, mixed>>}
* @return array{client: array<string, string>|\stdClass, items: list<array<string, mixed>>}
*/
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<string, string>
* @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<string, string>, items: list<array<string, string|int>>}
* @return array{client: ?array<string, string>, items: list<array<string, string|int>>}
*/
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);
+11 -3
View File
@@ -77,17 +77,25 @@ final class ReceiptItem
/**
* Объект `inventPositions` для атрибута `INVENTORY`: цена и количество —
* строками, как в примере спецификации Moneta.
* строками, как в примере спецификации Moneta. `vatTag`/`pm`/`po` — только
* при фискализации: без кассы чек не пробивается, Moneta лишь сохраняет
* позиции в операции.
*
* @return array<string, string>
*/
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,
+1 -1
View File
@@ -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),
];
+32 -2
View File
@@ -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';
+32 -2
View File
@@ -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 для уведомлений';
+2 -2
View File
@@ -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';