diff --git a/myproject/orders/static/orders/js/unified_transaction_form.js b/myproject/orders/static/orders/js/unified_transaction_form.js
new file mode 100644
index 0000000..4a710c2
--- /dev/null
+++ b/myproject/orders/static/orders/js/unified_transaction_form.js
@@ -0,0 +1,218 @@
+/**
+ * Unified Transaction Form Module
+ * Переиспользуемый модуль для формы платежей/возвратов
+ */
+
+(function(window) {
+ 'use strict';
+
+ /**
+ * Инициализирует унифицированную форму транзакций
+ * @param {Element|string} formElement - DOM элемент формы или селектор
+ * @param {Object} options - Настройки
+ * @param {string} options.paymentUrl - URL для платежей
+ * @param {string} options.refundUrl - URL для возвратов
+ * @param {number} options.amountDue - Сумма к оплате
+ * @param {number} options.amountPaid - Сумма оплаченного
+ * @param {number} options.walletBalance - Баланс кошелька клиента
+ */
+ function initUnifiedTransactionForm(formElement, options) {
+ var form = typeof formElement === 'string'
+ ? document.querySelector(formElement)
+ : formElement;
+
+ if (!form) {
+ console.error('[UnifiedTransactionForm] Form not found');
+ return;
+ }
+
+ options = options || {};
+
+ // URLs из опций или data-атрибутов
+ var paymentUrl = options.paymentUrl || form.dataset.paymentUrl;
+ var refundUrl = options.refundUrl || form.dataset.refundUrl;
+
+ // Данные из опций или data-атрибутов
+ var amountDue = options.amountDue !== undefined
+ ? parseFloat(options.amountDue)
+ : parseFloat(form.dataset.amountDue || 0);
+
+ var amountPaid = options.amountPaid !== undefined
+ ? parseFloat(options.amountPaid)
+ : parseFloat(form.dataset.amountPaid || 0);
+
+ var walletBalance = options.walletBalance !== undefined
+ ? parseFloat(options.walletBalance)
+ : parseFloat(form.dataset.walletBalance || 0);
+
+ // Элементы формы
+ var modeButtons = form.querySelectorAll('#mode-toggle [data-mode]');
+ var methodButtons = form.querySelectorAll('.method-btn');
+ var methodHidden = form.querySelector('#unified-method-id');
+
+ var amountInput = form.querySelector('#unified-amount');
+ var amountHint = form.querySelector('#amount-hint');
+ var amountMaxSpan = form.querySelector('#amount-max');
+ var walletEmptyHint = form.querySelector('#wallet-empty-hint');
+
+ var refundReasonWrap = form.querySelector('#refund-reason-wrap');
+ var refundReasonInput = form.querySelector('#refund-reason');
+ var notesInput = form.querySelector('#unified-notes');
+ var submitBtn = form.querySelector('#unified-submit');
+
+ // Состояние
+ var currentMode = 'payment'; // режим по умолчанию — Платёж
+ var currentMethodCode = null;
+
+ // Применить режим
+ function applyMode() {
+ if (currentMode === 'payment') {
+ form.action = paymentUrl;
+ methodHidden.name = 'payment_method';
+ amountInput.name = 'amount';
+ notesInput.name = 'notes';
+ refundReasonWrap.style.display = 'none';
+ refundReasonInput.removeAttribute('name');
+ refundReasonInput.removeAttribute('required');
+
+ submitBtn.classList.remove('btn-warning', 'text-dark');
+ submitBtn.classList.add('btn-success');
+ submitBtn.innerHTML = ' Оплатить';
+
+ updateLimitsForPayment();
+ } else {
+ form.action = refundUrl;
+ methodHidden.name = 'refund_payment_method';
+ amountInput.name = 'refund_amount';
+ notesInput.name = 'refund_notes';
+ refundReasonWrap.style.display = 'block';
+ refundReasonInput.name = 'refund_reason';
+ refundReasonInput.setAttribute('required', 'required');
+
+ submitBtn.classList.remove('btn-success');
+ submitBtn.classList.add('btn-warning', 'text-dark');
+ submitBtn.innerHTML = ' Вернуть средства';
+
+ updateLimitsForRefund();
+ }
+ }
+
+ // Лимиты для платежа (кошелёк ограничивается балансом и суммой к оплате)
+ function updateLimitsForPayment() {
+ if (currentMethodCode === 'account_balance') {
+ var max = Math.min(amountDue, walletBalance);
+ amountInput.max = max;
+ amountHint.style.display = 'inline';
+ amountMaxSpan.textContent = (max || 0).toFixed(2);
+ } else {
+ amountInput.removeAttribute('max');
+ amountHint.style.display = 'none';
+ }
+ }
+
+ // Лимиты для возврата (всегда ограничиваем суммой оплаченного)
+ function updateLimitsForRefund() {
+ amountInput.max = amountPaid || 0;
+ amountHint.style.display = 'inline';
+ amountMaxSpan.textContent = (amountPaid || 0).toFixed(2);
+ }
+
+ // Выбор режима
+ modeButtons.forEach(function(btn) {
+ btn.addEventListener('click', function() {
+ modeButtons.forEach(function(b) { b.classList.remove('active'); });
+ btn.classList.add('active');
+ currentMode = btn.dataset.mode;
+
+ // Сбрасываем выбор способа и сумму при смене режима
+ methodButtons.forEach(function(b) { b.classList.remove('active'); });
+ methodHidden.value = '';
+ currentMethodCode = null;
+ amountInput.value = currentMode === 'payment' ? (amountDue || '') : '';
+
+ applyMode();
+ });
+ });
+
+ // Выбор способа
+ methodButtons.forEach(function(btn) {
+ btn.addEventListener('click', function() {
+ var methodCode = btn.dataset.code;
+
+ // Скрываем подсказку
+ walletEmptyHint.style.display = 'none';
+
+ // Проверяем баланс кошелька
+ if (currentMode === 'payment' && methodCode === 'account_balance' && walletBalance === 0) {
+ walletEmptyHint.style.display = 'block';
+ return;
+ }
+
+ // Активируем выбранный способ
+ methodButtons.forEach(function(b) { b.classList.remove('active'); });
+ btn.classList.add('active');
+ methodHidden.value = btn.dataset.id;
+ currentMethodCode = methodCode;
+
+ // Для кошелька подставляем максимальную сумму
+ if (currentMode === 'payment' && methodCode === 'account_balance') {
+ amountInput.value = Math.min(amountDue, walletBalance).toFixed(2);
+ }
+
+ // Обновляем лимиты
+ currentMode === 'payment' ? updateLimitsForPayment() : updateLimitsForRefund();
+ });
+ });
+
+ // Инициализация начального режима
+ if (modeButtons.length > 0) {
+ modeButtons[0].classList.add('active');
+ }
+ amountInput.value = amountDue || '';
+ applyMode();
+
+ // Валидация перед отправкой формы
+ form.addEventListener('submit', function(e) {
+ // Проверяем выбран ли способ оплаты
+ if (!methodHidden.value) {
+ e.preventDefault();
+
+ // Подсвечиваем блок со способами оплаты
+ var methodsContainer = form.querySelector('#unified-methods');
+ methodsContainer.classList.add('border', 'border-danger', 'rounded', 'p-2');
+
+ // Показываем подсказку
+ var existingHint = form.querySelector('#method-required-hint');
+ if (existingHint) {
+ existingHint.remove();
+ }
+
+ var hint = document.createElement('small');
+ hint.id = 'method-required-hint';
+ hint.className = 'text-danger d-block mt-1';
+ var messageText = currentMode === 'payment' ? 'способ оплаты' : 'способ возврата';
+ hint.innerHTML = ' Пожалуйста, выберите ' + messageText;
+ methodsContainer.parentElement.appendChild(hint);
+
+ // Убираем подсветку при выборе
+ var removeValidation = function() {
+ methodsContainer.classList.remove('border', 'border-danger', 'rounded', 'p-2');
+ var hintEl = form.querySelector('#method-required-hint');
+ if (hintEl) hintEl.remove();
+ };
+
+ methodButtons.forEach(function(btn) {
+ btn.addEventListener('click', removeValidation, { once: true });
+ });
+
+ return false;
+ }
+ });
+
+ console.log('[UnifiedTransactionForm] Initialized for form:', form.id || form);
+ }
+
+ // Экспорт
+ window.initUnifiedTransactionForm = initUnifiedTransactionForm;
+
+})(window);
diff --git a/myproject/orders/templates/orders/order_form.html b/myproject/orders/templates/orders/order_form.html
index d39c305..7e8a861 100644
--- a/myproject/orders/templates/orders/order_form.html
+++ b/myproject/orders/templates/orders/order_form.html
@@ -883,186 +883,21 @@ document.addEventListener('DOMContentLoaded', function() {
});
+
+
+{% if order.pk %}
+{% endif %}