Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9886c97876 | ||
|
|
e4e04236cc | ||
|
|
b4ff4c1ea9 | ||
|
|
2be833119b |
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
> **© Правообладатель:** НКО «МОНЕТА» (ООО)
|
> **© Правообладатель:** НКО «МОНЕТА» (ООО)
|
||||||
|
|
||||||
> **📦 Версия модуля: 1.0.0**
|
> **📦 Версия модуля: 1.0.2**
|
||||||
|
|
||||||
> **ℹ️ Требования:**
|
> **ℹ️ Требования:**
|
||||||
> PHP 8.1+
|
> PHP 8.1+
|
||||||
@@ -100,17 +100,19 @@
|
|||||||
|
|
||||||
### Реквизиты шлюза в платёжном аккаунте
|
### Реквизиты шлюза в платёжном аккаунте
|
||||||
|
|
||||||
| Поле | Значение |
|
| Поле | Значение |
|
||||||
|---------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
|---------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||||
| **Номер счёта** | Обязательно. Ваш номер расширенного счёта в системе [MONETA.RU](https://moneta.ru/) |
|
| **Номер счёта** | Обязательно. Ваш номер расширенного счёта в системе [MONETA.RU](https://moneta.ru/) |
|
||||||
| **Код проверки целостности данных** | Обязательно. Код проверки целостности данных, указанный в настройках расширенного счёта в системе [MONETA.RU](https://moneta.ru/) |
|
| **Код проверки целостности данных** | Обязательно. Код проверки целостности данных, указанный в настройках расширенного счёта в системе [MONETA.RU](https://moneta.ru/) |
|
||||||
| **Сервер платежей** | `Боевой` (по умолчанию) / `Демо` (demo.moneta.ru, отдельный номер счёта) |
|
| **Сервер платежей** | `Боевой` (по умолчанию) / `Демо` (demo.moneta.ru, отдельный номер счёта) |
|
||||||
| **Тестовый режим** | Должен совпадать с флагом «Тестовый режим» в настройках счёта |
|
| **Тестовый режим** | Должен совпадать с флагом «Тестовый режим» в настройках счёта |
|
||||||
| **Email для чеков** | Обязательно. Подставляется в чек, если у покупателя нет ни email, ни телефона — чек придёт на этот адрес, а ФИО покупателя в чеке сохранится |
|
| **Фискализация чеков** | Фискализация чеков по 54-ФЗ средствами системы [MONETA.RU](https://moneta.ru/) сервисом [kassa.payanyway.ru](https://kassa.payanyway.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/) |
|
| **Признак предмета расчёта** | Для чека при фискализации (без неё поле скрыто); по умолчанию «Услуга». Курсы и сертификаты можно разнести по разным платёжным аккаунтам |
|
||||||
|
| **Отправлять ссылку оплаты на почту** | При создании заказа покупателю приходит сообщение со ссылкой на оплату — можно оплатить позже (по умолчанию включено) |
|
||||||
|
| **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/)
|
||||||
|
|
||||||
|
|||||||
+32
-8
@@ -8,6 +8,8 @@ use core_payment\form\account_gateway;
|
|||||||
use paygw_moneta\local\protocol\AssistantProtocol;
|
use paygw_moneta\local\protocol\AssistantProtocol;
|
||||||
use paygw_moneta\local\protocol\PaymentServer;
|
use paygw_moneta\local\protocol\PaymentServer;
|
||||||
use paygw_moneta\local\receipt\Client;
|
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 paygw_moneta\local\receipt\Vat;
|
||||||
use stdClass;
|
use stdClass;
|
||||||
|
|
||||||
@@ -42,7 +44,7 @@ class gateway extends \core_payment\gateway
|
|||||||
|
|
||||||
$servers = [];
|
$servers = [];
|
||||||
foreach (PaymentServer::cases() as $server) {
|
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->addElement('select', 'paymentserver', get_string('paymentserver', 'paygw_moneta'), $servers);
|
||||||
$mform->setDefault('paymentserver', PaymentServer::Prod->value);
|
$mform->setDefault('paymentserver', PaymentServer::Prod->value);
|
||||||
@@ -51,21 +53,41 @@ class gateway extends \core_payment\gateway
|
|||||||
$mform->addElement('advcheckbox', 'testmode', get_string('testmode', 'paygw_moneta'));
|
$mform->addElement('advcheckbox', 'testmode', get_string('testmode', 'paygw_moneta'));
|
||||||
$mform->addHelpButton('testmode', '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->addElement('advcheckbox', 'fiscalization', get_string('fiscalization', 'paygw_moneta'));
|
||||||
$mform->addHelpButton('fiscalization', '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 = [];
|
$vatOptions = [];
|
||||||
foreach (Vat::cases() as $vat) {
|
foreach (Vat::cases() as $vat) {
|
||||||
$vatOptions[$vat->value] = get_string($vat->langKey(), 'paygw_moneta');
|
$vatOptions[$vat->value] = get_string($vat->langKey(), 'paygw_moneta');
|
||||||
}
|
}
|
||||||
$mform->addElement('select', 'vat', get_string('vat', 'paygw_moneta'), $vatOptions);
|
$mform->addElement('select', 'vat', get_string('vat', 'paygw_moneta'), $vatOptions);
|
||||||
$mform->addHelpButton('vat', 'vat', 'paygw_moneta');
|
|
||||||
$mform->setDefault('vat', Vat::None->value);
|
$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->addElement('advcheckbox', 'sendpaymentlink', get_string('sendpaymentlink', 'paygw_moneta'));
|
||||||
$mform->addHelpButton('sendpaymentlink', 'sendpaymentlink', 'paygw_moneta');
|
$mform->addHelpButton('sendpaymentlink', 'sendpaymentlink', 'paygw_moneta');
|
||||||
@@ -88,8 +110,10 @@ class gateway extends \core_payment\gateway
|
|||||||
$errors['accountnumber'] = get_string('error:accountnumber', 'paygw_moneta');
|
$errors['accountnumber'] = get_string('error:accountnumber', 'paygw_moneta');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// «Email для чеков» обязателен только при фискализации, но мусор не сохраняем и без неё.
|
||||||
$receiptEmail = trim((string) ($data->receiptemail ?? ''));
|
$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');
|
$errors['receiptemail'] = get_string('error:receiptemail', 'paygw_moneta');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ namespace paygw_moneta\local;
|
|||||||
use paygw_moneta\local\protocol\PaymentServer;
|
use paygw_moneta\local\protocol\PaymentServer;
|
||||||
use paygw_moneta\local\protocol\Signature;
|
use paygw_moneta\local\protocol\Signature;
|
||||||
use paygw_moneta\local\receipt\Client;
|
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 paygw_moneta\local\receipt\Vat;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -34,6 +36,8 @@ final class GatewayConfig
|
|||||||
public readonly bool $enabled,
|
public readonly bool $enabled,
|
||||||
public readonly ?string $receiptEmail = null,
|
public readonly ?string $receiptEmail = null,
|
||||||
public readonly bool $sendPaymentLink = false,
|
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,
|
$enabled,
|
||||||
Client::normalizeEmail((string) ($config['receiptemail'] ?? '')),
|
Client::normalizeEmail((string) ($config['receiptemail'] ?? '')),
|
||||||
!empty($config['sendpaymentlink']),
|
!empty($config['sendpaymentlink']),
|
||||||
|
PaymentMethod::tryFrom((string) ($config['paymentmethod'] ?? '')) ?? PaymentMethod::FullPayment,
|
||||||
|
PaymentObject::tryFrom((string) ($config['paymentobject'] ?? '')) ?? PaymentObject::Service,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Можно ли создавать новые заказы: реквизиты и «Email для чеков», без
|
* Можно ли создавать новые заказы: реквизиты, а при фискализации ещё и
|
||||||
* которого чек покупателя без контактов не собрать. Колбэки проверяют
|
* «Email для чеков», без которого чек покупателя без контактов не собрать.
|
||||||
* только {@see isComplete()} — уже оплаченный заказ должен дойти до
|
* Колбэки проверяют только {@see isComplete()} — уже оплаченный заказ
|
||||||
* зачисления, даже если настройку потом очистили.
|
* должен дойти до зачисления, даже если настройку потом очистили.
|
||||||
*/
|
*/
|
||||||
public function acceptsNewOrders(): bool
|
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
|
public function isComplete(): bool
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ final class PaymentFormPage
|
|||||||
private function __construct() {}
|
private function __construct() {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return array{action: string, cost: string, description: string, iscourse: bool, logourl: string, fields: list<array{name: string, value: string}>}
|
* @return array{action: string, cost: string, description: string, logourl: string, fields: list<array{name: string, value: string}>}
|
||||||
*/
|
*/
|
||||||
public static function context(Order $order, GatewayConfig $config): array
|
public static function context(Order $order, GatewayConfig $config): array
|
||||||
{
|
{
|
||||||
@@ -62,7 +62,6 @@ final class PaymentFormPage
|
|||||||
// Сумма для покупателя — как в остальном Moodle: «120,25 ₽».
|
// Сумма для покупателя — как в остальном Moodle: «120,25 ₽».
|
||||||
'cost' => helper::get_cost_as_string((float) $order->amount->toDecimal(), $order->currency),
|
'cost' => helper::get_cost_as_string((float) $order->amount->toDecimal(), $order->currency),
|
||||||
'description' => $order->snapshot->description,
|
'description' => $order->snapshot->description,
|
||||||
'iscourse' => $order->component === 'enrol_fee',
|
|
||||||
'logourl' => $OUTPUT->image_url('img', 'paygw_moneta')->out(false),
|
'logourl' => $OUTPUT->image_url('img', 'paygw_moneta')->out(false),
|
||||||
'fields' => $fields,
|
'fields' => $fields,
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ final class CheckHandler implements NotificationHandler
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (!$order->snapshot->fiscalization) {
|
if (!$order->snapshot->fiscalization) {
|
||||||
return $response->toXml();
|
return $response->toXml($order->snapshot->receipt);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -146,6 +146,7 @@ final class PayHandler implements NotificationHandler
|
|||||||
signature: $config->signature(),
|
signature: $config->signature(),
|
||||||
cms: CmsInfo::getCmsModuleVersion(),
|
cms: CmsInfo::getCmsModuleVersion(),
|
||||||
receipt: $receipt,
|
receipt: $receipt,
|
||||||
|
fiscalization: $order->snapshot->fiscalization,
|
||||||
);
|
);
|
||||||
|
|
||||||
return $payResponse->toXml();
|
return $payResponse->toXml();
|
||||||
|
|||||||
@@ -76,15 +76,22 @@ final class OrderFactory
|
|||||||
email: (string) ($user->email ?? ''),
|
email: (string) ($user->email ?? ''),
|
||||||
phone: self::phone($user),
|
phone: self::phone($user),
|
||||||
name: self::fullName($user),
|
name: self::fullName($user),
|
||||||
fallbackEmail: $config->receiptEmail,
|
fallbackEmail: $config->fiscalization ? $config->receiptEmail : null,
|
||||||
) ?? throw new moodle_exception('gatewaynotfound', 'payment');
|
);
|
||||||
|
|
||||||
$snapshot = new OrderSnapshot(
|
$snapshot = new OrderSnapshot(
|
||||||
accountNumber: $config->accountNumber,
|
accountNumber: $config->accountNumber,
|
||||||
testMode: $config->testMode,
|
testMode: $config->testMode,
|
||||||
fiscalization: $config->fiscalization,
|
fiscalization: $config->fiscalization,
|
||||||
vat: $config->vat,
|
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,
|
description: $itemName,
|
||||||
subscriberId: self::subscriberId($user),
|
subscriberId: self::subscriberId($user),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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(
|
return CallbackResponse::xml(
|
||||||
MntResponseXml::build(
|
MntResponseXml::build(
|
||||||
@@ -43,6 +46,7 @@ final class CheckResponse
|
|||||||
amount: $this->amount,
|
amount: $this->amount,
|
||||||
signature: $this->sign(),
|
signature: $this->sign(),
|
||||||
cms: $this->cms,
|
cms: $this->cms,
|
||||||
|
attributes: $receipt?->toResponseAttributes(fiscalization: false) ?? [],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ final class PayResponse
|
|||||||
private readonly Signature $signature,
|
private readonly Signature $signature,
|
||||||
private readonly string $cms,
|
private readonly string $cms,
|
||||||
private readonly ?Receipt $receipt = null,
|
private readonly ?Receipt $receipt = null,
|
||||||
|
private readonly bool $fiscalization = false,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -30,17 +31,6 @@ final class PayResponse
|
|||||||
*/
|
*/
|
||||||
public function toXml(): CallbackResponse
|
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(
|
return CallbackResponse::xml(
|
||||||
MntResponseXml::build(
|
MntResponseXml::build(
|
||||||
accountId: $this->accountId,
|
accountId: $this->accountId,
|
||||||
@@ -49,7 +39,7 @@ final class PayResponse
|
|||||||
amount: $this->amount,
|
amount: $this->amount,
|
||||||
signature: $this->signature->forResponse($this->resultCode, $this->accountId, $this->transactionId),
|
signature: $this->signature->forResponse($this->resultCode, $this->accountId, $this->transactionId),
|
||||||
cms: $this->cms,
|
cms: $this->cms,
|
||||||
attributes: $attributes,
|
attributes: $this->receipt?->toResponseAttributes($this->fiscalization) ?? [],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
return 'server_' . strtolower($this->value);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ declare(strict_types=1);
|
|||||||
namespace paygw_moneta\local\receipt;
|
namespace paygw_moneta\local\receipt;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Признак способа расчёта (`pm` / `paymentMethod`), перечень из `cmsspecification.pdf`.
|
* Признак способа расчёта (`pm` / `paymentMethod`), перечень из `kassaspecification.pdf`.
|
||||||
*
|
*
|
||||||
* @package paygw_moneta
|
* @package paygw_moneta
|
||||||
* @copyright 2026 Moneta Labs {@link https://moneta.ru/}
|
* @copyright 2026 Moneta Labs {@link https://moneta.ru/}
|
||||||
@@ -20,4 +20,9 @@ enum PaymentMethod: string
|
|||||||
case PartialPayment = 'partial_payment';
|
case PartialPayment = 'partial_payment';
|
||||||
case Credit = 'credit';
|
case Credit = 'credit';
|
||||||
case CreditPayment = 'credit_payment';
|
case CreditPayment = 'credit_payment';
|
||||||
|
|
||||||
|
public function langKey(): string
|
||||||
|
{
|
||||||
|
return 'paymentmethod:' . $this->value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,10 @@ declare(strict_types=1);
|
|||||||
namespace paygw_moneta\local\receipt;
|
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
|
* @package paygw_moneta
|
||||||
* @copyright 2026 Moneta Labs {@link https://moneta.ru/}
|
* @copyright 2026 Moneta Labs {@link https://moneta.ru/}
|
||||||
@@ -29,4 +32,12 @@ enum PaymentObject: string
|
|||||||
case PropertyRight = 'property_right';
|
case PropertyRight = 'property_right';
|
||||||
case SalesTax = 'sales_tax';
|
case SalesTax = 'sales_tax';
|
||||||
case ResortFee = 'resort_fee';
|
case ResortFee = 'resort_fee';
|
||||||
|
case Award = 'award';
|
||||||
|
case NonOperating = 'non_operating';
|
||||||
|
case InsurancePremium = 'insurance_premium';
|
||||||
|
|
||||||
|
public function langKey(): string
|
||||||
|
{
|
||||||
|
return 'paymentobject:' . $this->value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ use paygw_moneta\local\Money;
|
|||||||
/**
|
/**
|
||||||
* Чек 54-ФЗ: покупатель и позиции. Сумма позиций обязана совпадать с суммой
|
* Чек 54-ФЗ: покупатель и позиции. Сумма позиций обязана совпадать с суммой
|
||||||
* операции до копейки — иначе номенклатура не пройдёт валидацию кассы.
|
* операции до копейки — иначе номенклатура не пройдёт валидацию кассы.
|
||||||
|
* Покупателя нет (`null`), если у него нет ни e-mail, ни телефона, а «Email
|
||||||
|
* для чеков» не подставлялся: атрибуты покупателя тогда опускаются.
|
||||||
*
|
*
|
||||||
* @package paygw_moneta
|
* @package paygw_moneta
|
||||||
* @copyright 2026 Moneta Labs {@link https://moneta.ru/}
|
* @copyright 2026 Moneta Labs {@link https://moneta.ru/}
|
||||||
@@ -24,7 +26,7 @@ final class Receipt
|
|||||||
* @param list<ReceiptItem> $items
|
* @param list<ReceiptItem> $items
|
||||||
*/
|
*/
|
||||||
public function __construct(
|
public function __construct(
|
||||||
public readonly Client $client,
|
public readonly ?Client $client,
|
||||||
array $items,
|
array $items,
|
||||||
) {
|
) {
|
||||||
if ($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, [
|
return new self($client, [
|
||||||
new ReceiptItem(
|
new ReceiptItem(
|
||||||
name: $courseName,
|
name: $courseName,
|
||||||
price: $amount,
|
price: $amount,
|
||||||
quantity: 1,
|
quantity: 1,
|
||||||
vat: $vat,
|
vat: $vat,
|
||||||
paymentMethod: PaymentMethod::FullPayment,
|
paymentMethod: $paymentMethod,
|
||||||
paymentObject: PaymentObject::Service,
|
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
|
public function toCheckReceipt(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'client' => $this->client->toArray(),
|
'client' => $this->client?->toArray() ?? new \stdClass(),
|
||||||
'items' => array_map(static fn(ReceiptItem $item): array => $item->toCheckItem(), $this->items),
|
'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-массив позиций.
|
* Значение атрибута `INVENTORY` — JSON-массив позиций.
|
||||||
*
|
*
|
||||||
* @throws \JsonException
|
* @throws \JsonException
|
||||||
*/
|
*/
|
||||||
public function toInventoryAttribute(): string
|
public function toInventoryAttribute(bool $fiscalization): string
|
||||||
{
|
{
|
||||||
return json_encode(
|
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,
|
JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Значение атрибута `CLIENT` — JSON-массив из одного объекта.
|
* Значение атрибута `CLIENT` — JSON-массив из одного объекта. `null` —
|
||||||
|
* покупателя нет.
|
||||||
*
|
*
|
||||||
* @throws \JsonException
|
* @throws \JsonException
|
||||||
*/
|
*/
|
||||||
public function toClientAttribute(): string
|
public function toClientAttribute(): ?string
|
||||||
{
|
{
|
||||||
|
if ($this->client === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return json_encode(
|
return json_encode(
|
||||||
[$this->client->toArray()],
|
[$this->client->toArray()],
|
||||||
JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES,
|
JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES,
|
||||||
@@ -114,11 +152,11 @@ final class Receipt
|
|||||||
* Значение атрибута `CUSTOMER` — e-mail покупателя строкой, по
|
* Значение атрибута `CUSTOMER` — e-mail покупателя строкой, по
|
||||||
* `cmsspecification.pdf`; дублирует `CLIENT.email`, чтобы чек принимался при
|
* `cmsspecification.pdf`; дублирует `CLIENT.email`, чтобы чек принимался при
|
||||||
* любой трактовке документов. `null` — у покупателя
|
* любой трактовке документов. `null` — у покупателя
|
||||||
* только телефон.
|
* только телефон или покупателя нет.
|
||||||
*/
|
*/
|
||||||
public function toCustomerAttribute(): ?string
|
public function toCustomerAttribute(): ?string
|
||||||
{
|
{
|
||||||
return $this->client->email;
|
return $this->client?->email;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -127,18 +165,18 @@ final class Receipt
|
|||||||
*/
|
*/
|
||||||
public function toPhoneAttribute(): ?string
|
public function toPhoneAttribute(): ?string
|
||||||
{
|
{
|
||||||
$phone = $this->client->phone;
|
$phone = $this->client?->phone;
|
||||||
|
|
||||||
return $phone === null ? null : ltrim($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
|
public function toArray(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'client' => $this->client->toArray(),
|
'client' => $this->client?->toArray(),
|
||||||
'items' => array_map(static fn(ReceiptItem $item): array => $item->toArray(), $this->items),
|
'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
|
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'] ?? []);
|
$items = array_map(static fn(array $item): ReceiptItem => ReceiptItem::fromArray($item), $data['items'] ?? []);
|
||||||
|
|
||||||
return new self($client, $items);
|
return new self($client, $items);
|
||||||
|
|||||||
@@ -77,17 +77,25 @@ final class ReceiptItem
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Объект `inventPositions` для атрибута `INVENTORY`: цена и количество —
|
* Объект `inventPositions` для атрибута `INVENTORY`: цена и количество —
|
||||||
* строками, как в примере спецификации Moneta.
|
* строками, как в примере спецификации Moneta. `vatTag`/`pm`/`po` — только
|
||||||
|
* при фискализации: без кассы чек не пробивается, Moneta лишь сохраняет
|
||||||
|
* позиции в операции.
|
||||||
*
|
*
|
||||||
* @return array<string, string>
|
* @return array<string, string>
|
||||||
*/
|
*/
|
||||||
public function toInventoryPosition(): array
|
public function toInventoryPosition(bool $fiscalization): array
|
||||||
{
|
{
|
||||||
return [
|
$position = [
|
||||||
'name' => $this->name,
|
'name' => $this->name,
|
||||||
'price' => $this->price->toDecimal(),
|
'price' => $this->price->toDecimal(),
|
||||||
'quantity' => (string) $this->quantity,
|
'quantity' => (string) $this->quantity,
|
||||||
'measure' => self::MEASURE,
|
'measure' => self::MEASURE,
|
||||||
|
];
|
||||||
|
if (!$fiscalization) {
|
||||||
|
return $position;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $position + [
|
||||||
'vatTag' => $this->vat->tag(),
|
'vatTag' => $this->vat->tag(),
|
||||||
'pm' => $this->paymentMethod->value,
|
'pm' => $this->paymentMethod->value,
|
||||||
'po' => $this->paymentObject->value,
|
'po' => $this->paymentObject->value,
|
||||||
|
|||||||
@@ -168,7 +168,7 @@ class provider implements
|
|||||||
'amount' => $order->amount->toDecimal() . ' ' . $order->currency,
|
'amount' => $order->amount->toDecimal() . ' ' . $order->currency,
|
||||||
'status' => $order->status->value,
|
'status' => $order->status->value,
|
||||||
'description' => $order->snapshot->description,
|
'description' => $order->snapshot->description,
|
||||||
'client' => $order->snapshot->receipt->client->toArray(),
|
'client' => $order->snapshot->receipt->client?->toArray() ?? [],
|
||||||
'timecreated' => transform::datetime($order->timeCreated),
|
'timecreated' => transform::datetime($order->timeCreated),
|
||||||
'timecompleted' => $order->timeCompleted === null ? null : transform::datetime($order->timeCompleted),
|
'timecompleted' => $order->timeCompleted === null ? null : transform::datetime($order->timeCompleted),
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -41,12 +41,11 @@ $string['server_prod'] = 'Production';
|
|||||||
$string['testmode'] = 'Test mode';
|
$string['testmode'] = 'Test mode';
|
||||||
$string['testmode_help'] = 'Simulated payment without charging money. Must match the account setting in MONETA.RU';
|
$string['testmode_help'] = 'Simulated payment without charging money. Must match the account setting in MONETA.RU';
|
||||||
$string['receiptemail'] = 'Receipt email';
|
$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['error:receiptemail'] = 'Enter a valid email address of at most 64 characters';
|
||||||
$string['fiscalization'] = 'Fiscal receipts';
|
$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'] = 'VAT rate';
|
||||||
$string['vat_help'] = 'VAT rate for the "course access" receipt line';
|
|
||||||
$string['vat:none'] = 'No VAT';
|
$string['vat:none'] = 'No VAT';
|
||||||
$string['vat:vat0'] = 'VAT 0%';
|
$string['vat:vat0'] = 'VAT 0%';
|
||||||
$string['vat:vat5'] = 'VAT 5%';
|
$string['vat:vat5'] = 'VAT 5%';
|
||||||
@@ -59,16 +58,43 @@ $string['vat:vat107'] = 'VAT 7% (calculated rate 7/107)';
|
|||||||
$string['vat:vat110'] = 'VAT 10% (calculated rate 10/110)';
|
$string['vat:vat110'] = 'VAT 10% (calculated rate 10/110)';
|
||||||
$string['vat:vat120'] = 'VAT 20% (calculated rate 20/120)';
|
$string['vat:vat120'] = 'VAT 20% (calculated rate 20/120)';
|
||||||
$string['vat:vat122'] = 'VAT 22% (calculated rate 22/122)';
|
$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'] = '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['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';
|
$string['callbackurl'] = 'Notification URL';
|
||||||
$string['callbackurl:copied'] = 'Notification URL copied';
|
$string['callbackurl:copied'] = 'Notification URL copied';
|
||||||
$string['callbackurl_help'] = 'Set this address as both Pay URL and Check URL in the MONETA.RU account settings. It must be reachable from the internet over HTTPS';
|
$string['callbackurl_help'] = 'Set this address as both Pay URL and Check URL in the MONETA.RU account settings. It must be reachable from the internet over HTTPS';
|
||||||
|
|
||||||
$string['checkout:access'] = 'Access to the course opens automatically after payment.';
|
$string['checkout:secure'] = "You will be taken to the secure {$string['pluginname']} payment page";
|
||||||
$string['checkout:course'] = 'Course payment';
|
|
||||||
$string['checkout:item'] = 'Payment';
|
|
||||||
$string['checkout:secure'] = "You will be taken to the secure {$string['pluginname']} payment page.";
|
|
||||||
$string['checkout:total'] = 'Amount due';
|
$string['checkout:total'] = 'Amount due';
|
||||||
$string['confirmpayment'] = "Proceeding to {$string['pluginname']} payment";
|
$string['confirmpayment'] = "Proceeding to {$string['pluginname']} payment";
|
||||||
$string['pay'] = 'Pay {$a}';
|
$string['pay'] = 'Pay {$a}';
|
||||||
|
|||||||
@@ -41,12 +41,11 @@ $string['server_prod'] = 'Боевой';
|
|||||||
$string['testmode'] = 'Тестовый режим';
|
$string['testmode'] = 'Тестовый режим';
|
||||||
$string['testmode_help'] = 'Имитация оплаты без реального списания средств. Режим должен совпадать с настройкой счёта в MONETA.RU';
|
$string['testmode_help'] = 'Имитация оплаты без реального списания средств. Режим должен совпадать с настройкой счёта в MONETA.RU';
|
||||||
$string['receiptemail'] = 'Email для чеков';
|
$string['receiptemail'] = 'Email для чеков';
|
||||||
$string['receiptemail_help'] = 'Подставляется в чек, только если у покупателя в профиле нет ни e-mail, ни телефона: без контакта касса чек не примет, поэтому он уйдёт на этот адрес (ФИО покупателя в чеке остаётся)';
|
$string['receiptemail_help'] = 'Обязателен при фискализации. Подставляется в чек, только если у покупателя в профиле нет ни e-mail, ни телефона: без контакта касса чек не примет, поэтому он уйдёт на этот адрес (ФИО покупателя в чеке остаётся)';
|
||||||
$string['error:receiptemail'] = 'Укажите корректный e-mail не длиннее 64 символов';
|
$string['error:receiptemail'] = 'Укажите корректный e-mail не длиннее 64 символов';
|
||||||
$string['fiscalization'] = 'Фискализация чеков';
|
$string['fiscalization'] = 'Фискализация чеков';
|
||||||
$string['fiscalization_help'] = 'Включайте только если к счёту подключён кассовый сервис kassa.payanyway.ru: ответ на проверочный запрос будет содержать данные чека по 54-ФЗ';
|
$string['fiscalization_help'] = 'Включайте только если к счёту подключён кассовый сервис kassa.payanyway.ru';
|
||||||
$string['vat'] = 'Ставка НДС';
|
$string['vat'] = 'Ставка НДС';
|
||||||
$string['vat_help'] = 'Ставка НДС для позиции «доступ к курсу» в кассовом чеке';
|
|
||||||
$string['vat:none'] = 'Без НДС';
|
$string['vat:none'] = 'Без НДС';
|
||||||
$string['vat:vat0'] = 'НДС 0%';
|
$string['vat:vat0'] = 'НДС 0%';
|
||||||
$string['vat:vat5'] = 'НДС 5%';
|
$string['vat:vat5'] = 'НДС 5%';
|
||||||
@@ -59,16 +58,43 @@ $string['vat:vat107'] = 'НДС 7% (расчётная ставка 7/107)';
|
|||||||
$string['vat:vat110'] = 'НДС 10% (расчётная ставка 10/110)';
|
$string['vat:vat110'] = 'НДС 10% (расчётная ставка 10/110)';
|
||||||
$string['vat:vat120'] = 'НДС 20% (расчётная ставка 20/120)';
|
$string['vat:vat120'] = 'НДС 20% (расчётная ставка 20/120)';
|
||||||
$string['vat:vat122'] = 'НДС 22% (расчётная ставка 22/122)';
|
$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'] = 'Отправлять ссылку оплаты на почту';
|
||||||
$string['sendpaymentlink_help'] = 'Если включено, при создании заказа покупателю приходит сообщение со ссылкой на оплату — оплатить можно позже. Ссылка действует, пока заказ не оплачен и не изменились цена или реквизиты. Канал (почта, уведомление на сайте) — по настройкам уведомлений Moodle';
|
$string['sendpaymentlink_help'] = 'Если включено, при создании заказа покупателю приходит сообщение со ссылкой на оплату — оплатить можно позже. Ссылка действует, пока заказ не оплачен и не изменились цена или реквизиты. Канал (почта, уведомление на сайте) — по настройкам уведомлений Moodle';
|
||||||
$string['callbackurl'] = 'URL для уведомлений';
|
$string['callbackurl'] = 'URL для уведомлений';
|
||||||
$string['callbackurl:copied'] = 'URL для уведомлений скопирован';
|
$string['callbackurl:copied'] = 'URL для уведомлений скопирован';
|
||||||
$string['callbackurl_help'] = 'Укажите этот адрес в настройках счёта MONETA.RU как Pay URL и Check URL. Он должен быть доступен из интернета по HTTPS';
|
$string['callbackurl_help'] = 'Укажите этот адрес в настройках счёта MONETA.RU как Pay URL и Check URL. Он должен быть доступен из интернета по HTTPS';
|
||||||
|
|
||||||
$string['checkout:access'] = 'Доступ к курсу откроется автоматически после оплаты.';
|
$string['checkout:secure'] = "Вы перейдёте на защищённую страницу оплаты {$string['pluginname']}";
|
||||||
$string['checkout:course'] = 'Оплата курса';
|
|
||||||
$string['checkout:item'] = 'Оплата';
|
|
||||||
$string['checkout:secure'] = "Вы перейдёте на защищённую страницу оплаты {$string['pluginname']}.";
|
|
||||||
$string['checkout:total'] = 'К оплате';
|
$string['checkout:total'] = 'К оплате';
|
||||||
$string['confirmpayment'] = "Переход к оплате через {$string['pluginname']}";
|
$string['confirmpayment'] = "Переход к оплате через {$string['pluginname']}";
|
||||||
$string['pay'] = 'Оплатить {$a}';
|
$string['pay'] = 'Оплатить {$a}';
|
||||||
|
|||||||
@@ -23,15 +23,6 @@
|
|||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.paygw-moneta-checkout-label {
|
|
||||||
margin-bottom: 0.5rem;
|
|
||||||
color: var(--bs-secondary-color, #6c757d);
|
|
||||||
font-size: 0.8125rem;
|
|
||||||
font-weight: 600;
|
|
||||||
letter-spacing: 0.08em;
|
|
||||||
text-transform: uppercase;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Название курса бывает длинным: переносим по словам, а слишком длинное слово — где угодно. */
|
/* Название курса бывает длинным: переносим по словам, а слишком длинное слово — где угодно. */
|
||||||
.paygw-moneta-checkout-title {
|
.paygw-moneta-checkout-title {
|
||||||
margin: 0 0 1.5rem;
|
margin: 0 0 1.5rem;
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
@template paygw_moneta/payment_form
|
@template paygw_moneta/payment_form
|
||||||
|
|
||||||
Карточка перехода на платёжную страницу MONETA.Assistant: логотип со
|
Карточка перехода на платёжную страницу MONETA.Assistant: логотип со
|
||||||
ссылкой на moneta.ru, что оплачивается, сумма, кнопка и пояснение. Сама не
|
ссылкой на moneta.ru, название, сумма, кнопка и пояснение. Сама не
|
||||||
уходит — покупатель нажимает «Оплатить»; после нажатия кнопка блокируется,
|
уходит — покупатель нажимает «Оплатить»; после нажатия кнопка блокируется,
|
||||||
чтобы повторный клик не отправил форму дважды. Название может быть длинным:
|
чтобы повторный клик не отправил форму дважды. Название может быть длинным:
|
||||||
переносится, не растягивая карточку.
|
переносится, не растягивая карточку.
|
||||||
@@ -27,7 +27,6 @@
|
|||||||
* action - адрес платёжной формы
|
* action - адрес платёжной формы
|
||||||
* cost - сумма с валютой для показа
|
* cost - сумма с валютой для показа
|
||||||
* description - описание заказа
|
* description - описание заказа
|
||||||
* iscourse - оплачивается запись на курс (enrol_fee)
|
|
||||||
* logourl - адрес логотипа
|
* logourl - адрес логотипа
|
||||||
* fields - подписанные поля формы: name, value
|
* fields - подписанные поля формы: name, value
|
||||||
|
|
||||||
@@ -36,7 +35,6 @@
|
|||||||
"action": "https://demo.moneta.ru/assistant.htm",
|
"action": "https://demo.moneta.ru/assistant.htm",
|
||||||
"cost": "120,25 ₽",
|
"cost": "120,25 ₽",
|
||||||
"description": "Монета",
|
"description": "Монета",
|
||||||
"iscourse": true,
|
|
||||||
"logourl": "https://example.test/theme/image.php/boost/paygw_moneta/1/img",
|
"logourl": "https://example.test/theme/image.php/boost/paygw_moneta/1/img",
|
||||||
"fields": [{"name": "MNT_ID", "value": "54600817"}]
|
"fields": [{"name": "MNT_ID", "value": "54600817"}]
|
||||||
}
|
}
|
||||||
@@ -47,10 +45,6 @@
|
|||||||
<img class="paygw-moneta-checkout-logo" src="{{logourl}}" alt="{{#str}}pluginname, paygw_moneta{{/str}}">
|
<img class="paygw-moneta-checkout-logo" src="{{logourl}}" alt="{{#str}}pluginname, paygw_moneta{{/str}}">
|
||||||
<span class="sr-only visually-hidden">{{#str}}opensinnewwindow{{/str}}</span>
|
<span class="sr-only visually-hidden">{{#str}}opensinnewwindow{{/str}}</span>
|
||||||
</a>
|
</a>
|
||||||
<div class="paygw-moneta-checkout-label">
|
|
||||||
{{#iscourse}}{{#str}}checkout:course, paygw_moneta{{/str}}{{/iscourse}}
|
|
||||||
{{^iscourse}}{{#str}}checkout:item, paygw_moneta{{/str}}{{/iscourse}}
|
|
||||||
</div>
|
|
||||||
<h3 class="paygw-moneta-checkout-title">{{description}}</h3>
|
<h3 class="paygw-moneta-checkout-title">{{description}}</h3>
|
||||||
<div class="paygw-moneta-checkout-total">
|
<div class="paygw-moneta-checkout-total">
|
||||||
<span class="text-muted">{{#str}}checkout:total, paygw_moneta{{/str}}</span>
|
<span class="text-muted">{{#str}}checkout:total, paygw_moneta{{/str}}</span>
|
||||||
@@ -64,10 +58,7 @@
|
|||||||
</form>
|
</form>
|
||||||
<div class="paygw-moneta-checkout-note text-muted small">
|
<div class="paygw-moneta-checkout-note text-muted small">
|
||||||
{{#pix}}i/lock, core{{/pix}}
|
{{#pix}}i/lock, core{{/pix}}
|
||||||
<div>
|
<div>{{#str}}checkout:secure, paygw_moneta{{/str}}</div>
|
||||||
<div>{{#str}}checkout:secure, paygw_moneta{{/str}}</div>
|
|
||||||
{{#iscourse}}<div class="mt-1">{{#str}}checkout:access, paygw_moneta{{/str}}</div>{{/iscourse}}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+2
-2
@@ -25,7 +25,7 @@
|
|||||||
defined('MOODLE_INTERNAL') || die();
|
defined('MOODLE_INTERNAL') || die();
|
||||||
|
|
||||||
$plugin->component = 'paygw_moneta';
|
$plugin->component = 'paygw_moneta';
|
||||||
$plugin->version = 2026092500;
|
$plugin->version = 2026100802;
|
||||||
$plugin->requires = 2023100900;
|
$plugin->requires = 2023100900;
|
||||||
$plugin->maturity = MATURITY_STABLE;
|
$plugin->maturity = MATURITY_STABLE;
|
||||||
$plugin->release = '1.0.0';
|
$plugin->release = '1.0.2';
|
||||||
|
|||||||
Reference in New Issue
Block a user