From 3f22677573b25cd7765b568abd6c755cd0e62368 Mon Sep 17 00:00:00 2001 From: Andrey Smakotin Date: Sat, 29 Nov 2025 16:54:24 +0300 Subject: [PATCH] =?UTF-8?q?=D0=97=D0=B0=D1=89=D0=B8=D1=82=D0=B0=20=D0=BE?= =?UTF-8?q?=D1=82=20=D0=BF=D0=B5=D1=80=D0=B5=D0=BF=D0=BB=D0=B0=D1=82=D1=8B?= =?UTF-8?q?=20=D0=BA=D0=BE=D1=88=D0=B5=D0=BB=D1=8C=D0=BA=D0=BE=D0=BC=20?= =?UTF-8?q?=D0=B8=20=D1=83=D0=BB=D1=83=D1=87=D1=88=D0=B5=D0=BD=D0=B8=D0=B5?= =?UTF-8?q?=20=D0=BE=D1=82=D0=BE=D0=B1=D1=80=D0=B0=D0=B6=D0=B5=D0=BD=D0=B8?= =?UTF-8?q?=D1=8F=20=D1=82=D1=80=D0=B0=D0=BD=D0=B7=D0=B0=D0=BA=D1=86=D0=B8?= =?UTF-8?q?=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Изменения в UI (order_form.html): - Добавлен data-code к опциям способов оплаты для идентификации метода кошелька - ID для селекта способа оплаты (payment-method-select) и поля суммы (payment-amount-input) - Динамическое ограничение max на поле суммы платежа при выборе кошелька - Подсказка 'Макс: X руб.' отображается только для оплаты кошельком - Для внешних методов (карта, наличные) ограничение отсутствует — переплата допустима Логика JS: - При выборе метода с code == 'account_balance' устанавливается max = order.amount_due - Для остальных методов max удаляется — оператор может внести сумму больше остатка - Переплата по внешним методам корректно зачисляется в кошелёк через WalletService.add_overpayment Серверная защита (transaction_service.py): - В TransactionService.create_payment добавлена проверка: если payment_method.code == 'account_balance' и amount > order.amount_due — ValidationError - Сообщение: 'Сумма оплаты из кошелька (X руб.) не может превышать остаток к оплате (Y руб.)' - Защита от обхода UI через API или прямой вызов Улучшение отображения (order_form.html, order_detail.html): - Для возврата в кошелёк (transaction_type == 'refund' и code == 'account_balance') показываем 'на баланс счёта' вместо названия метода - История становится понятнее: '−750,00 руб. Возврат 29.11.2025 на баланс счёта' Сценарий: - Кошелёк клиента 500 руб., заказ 65 руб. - Оператор выбирает оплату из кошелька — поле суммы ограничено 65 руб. - Попытка ввести 500 заблокирована UI и серверной валидацией - Для внешней оплаты (карта онлайн) можно внести 500 руб. — остаток 435 автоматически зачислится в кошелёк как переплата Цель: - Исключить путаницу в истории транзакций при оплате кошельком - Разграничить поведение: кошелёк строго ограничен, внешние методы допускают переплату - Обеспечить прозрачность движения средств для операторов --- myproject/orders/models/payment.py | 0 .../orders/services/transaction_service.py | 7 +++ .../orders/templates/orders/order_detail.html | 6 ++- .../orders/templates/orders/order_form.html | 45 +++++++++++++++++-- 4 files changed, 53 insertions(+), 5 deletions(-) create mode 100644 myproject/orders/models/payment.py diff --git a/myproject/orders/models/payment.py b/myproject/orders/models/payment.py new file mode 100644 index 0000000..e69de29 diff --git a/myproject/orders/services/transaction_service.py b/myproject/orders/services/transaction_service.py index 0d4145f..1b2fa32 100644 --- a/myproject/orders/services/transaction_service.py +++ b/myproject/orders/services/transaction_service.py @@ -54,6 +54,13 @@ class TransactionService: except PaymentMethod.DoesNotExist: raise ValueError(f'Способ оплаты "{payment_method}" не найден') + # Ограничение для кошелька: нельзя оплатить больше чем к оплате + if payment_method.code == 'account_balance' and amount > order.amount_due: + raise ValidationError( + f'Сумма оплаты из кошелька ({amount} руб.) не может превышать ' + f'остаток к оплате ({order.amount_due} руб.)' + ) + # Создаём транзакцию txn = Transaction.objects.create( order=order, diff --git a/myproject/orders/templates/orders/order_detail.html b/myproject/orders/templates/orders/order_detail.html index 21fbd5d..c5c6012 100644 --- a/myproject/orders/templates/orders/order_detail.html +++ b/myproject/orders/templates/orders/order_detail.html @@ -358,7 +358,11 @@ {{ transaction.transaction_date|date:"d.m.Y H:i" }}
- {{ transaction.payment_method.name }} + {% if transaction.transaction_type == 'refund' and transaction.payment_method.code == 'account_balance' %} + Возврат на баланс счёта + {% else %} + {{ transaction.payment_method.name }} + {% endif %} {% if transaction.notes or transaction.reason %}
{{ transaction.notes|default:transaction.reason }} {% endif %} diff --git a/myproject/orders/templates/orders/order_form.html b/myproject/orders/templates/orders/order_form.html index bdb5619..b98e282 100644 --- a/myproject/orders/templates/orders/order_form.html +++ b/myproject/orders/templates/orders/order_form.html @@ -671,7 +671,13 @@ {{ transaction.transaction_date|date:"d.m.Y H:i" }}
- {{ transaction.payment_method.name }} + + {% if transaction.transaction_type == 'refund' and transaction.payment_method.code == 'account_balance' %} + Возврат на баланс счёта + {% else %} + {{ transaction.payment_method.name }} + {% endif %} + {% if transaction.transaction_type == 'refund' %}−{% else %}+{% endif %}{{ transaction.amount|floatformat:2 }} @@ -727,12 +733,12 @@
- {% load orders_tags %} {% get_payment_methods as payment_methods %} {% for pm in payment_methods %} - + {% endfor %}
@@ -740,10 +746,11 @@
0 %}value="{{ order.amount_due|unlocalize }}"{% endif %} required> руб.
+
@@ -887,6 +894,36 @@ document.addEventListener('DOMContentLoaded', function() { }); + +