import sys import subprocess import os # Auto-install dependencies if missing for package in ["flask", "requests", "openpyxl"]: try: __import__(package) except ImportError: print(f"Пакет '{package}' не найден. Устанавливаем...") try: subprocess.check_call([sys.executable, "-m", "pip", "install", package]) print(f"Пакет '{package}' успешно установлен!") except Exception as e: print(f"Ошибка при автоматической установке '{package}': {e}") sys.exit(1) import json import uuid import smtplib import requests from requests.auth import HTTPBasicAuth from email.mime.text import MIMEText from email.mime.multipart import MIMEMultipart from datetime import datetime import ipaddress # YooKassa official IP address ranges for webhook callbacks YOOKASSA_ALLOWED_NETWORKS = [ ipaddress.ip_network("185.71.76.0/27"), ipaddress.ip_network("185.71.77.0/27"), ipaddress.ip_network("77.75.153.0/25"), ipaddress.ip_network("77.75.154.128/25"), ipaddress.ip_network("77.75.156.11/32"), ipaddress.ip_network("77.75.156.35/32"), ipaddress.ip_network("2a02:5180::/32"), ipaddress.ip_network("2a02:5180:0:1509::/64"), ipaddress.ip_network("2a02:5180:0:2655::/64"), ipaddress.ip_network("2a02:5180:0:1533::/64"), ipaddress.ip_network("2a02:5180:0:2669::/64"), ] def is_yookassa_ip(ip_str: str) -> bool: """Check if the provided IP address belongs to YooKassa's official subnets.""" try: ip = ipaddress.ip_address(ip_str) return any(ip in net for net in YOOKASSA_ALLOWED_NETWORKS) except ValueError: return False import openpyxl from openpyxl import Workbook, load_workbook from openpyxl.styles import Font, Alignment, PatternFill from flask import Flask, request, jsonify, redirect, url_for, session, render_template_string # Import local modules import config from database import DatabaseManager # Get root directory ROOT_DIR = os.path.dirname(os.path.abspath(__file__)) # Initialize SQLite database db_path = os.path.join(ROOT_DIR, "bot_database.db") db = DatabaseManager(db_path=db_path) # Initialize Flask # static_folder points to the root directory to serve the whole site! app = Flask(__name__, static_folder=ROOT_DIR, static_url_path='') app.secret_key = os.getenv("FLASK_SECRET_KEY", "ikp_stable_admin_session_key_secret_2026") # Path to the Excel spreadsheet in the form directory FILE_NAME = os.path.join(ROOT_DIR, "form", "registration_for_training.xlsx") def init_excel(): """Create the Excel file with styled headers if it doesn't exist.""" recreate = False if os.path.exists(FILE_NAME): try: wb = load_workbook(FILE_NAME) ws = wb.active # Recreate if it does not have the 'id_user' column as the first header if ws.cell(row=1, column=1).value != "id_user": recreate = True except Exception: recreate = True if recreate: try: os.remove(FILE_NAME) print("Обнаружена старая структура Excel файла. Пересоздаём с новыми колонками...") except Exception as e: print(f"Не удалось удалить старый Excel файл: {e}") if not os.path.exists(FILE_NAME): # Ensure form directory exists os.makedirs(os.path.dirname(FILE_NAME), exist_ok=True) wb = Workbook() ws = wb.active ws.title = "Заявки на обучение" headers = [ "id_user", "Имя", "Номер телефона", "E-mail", "Ссылка на профиль ВКонтакте", "Согласие с офертой", "Согласие с политикой", "Согласие на рассылку", "Услуга", "Сумма", "Статус оплаты", "ID платежа YooKassa", "Дата регистрации", "Дата оплаты" ] ws.append(headers) # Style header cells header_font = Font(name="Calibri", size=11, bold=True, color="FFFFFF") header_fill = PatternFill(start_color="1F4E78", end_color="1F4E78", fill_type="solid") header_align = Alignment(horizontal="center", vertical="center") for col_num, header in enumerate(headers, 1): cell = ws.cell(row=1, column=col_num) cell.font = header_font cell.fill = header_fill cell.alignment = header_align column_widths = [15, 20, 25, 25, 30, 15, 15, 15, 25, 15, 15, 30, 22, 22] for i, width in enumerate(column_widths, 1): ws.column_dimensions[openpyxl.utils.get_column_letter(i)].width = width wb.save(FILE_NAME) print(f"Создан новый файл Excel с поддержкой платежей: {FILE_NAME}") def get_next_user_id(ws): """Generate the next unique user ID in the format id_001, id_002...""" max_row = ws.max_row if max_row <= 1: return "id_001" last_id_val = ws.cell(row=max_row, column=1).value if not last_id_val or not str(last_id_val).startswith("id_"): return f"id_{(max_row - 1):03d}" try: numeric_part = int(str(last_id_val).split("_")[1]) next_num = numeric_part + 1 return f"id_{next_num:03d}" except (IndexError, ValueError): return f"id_{max_row:03d}" def add_registration_to_excel(reg_id, data): """Append a new form submission row into the Excel sheet.""" init_excel() try: wb = load_workbook(FILE_NAME) ws = wb.active except Exception as e: print(f"Ошибка загрузки Excel файла, пересоздаём: {e}") init_excel() wb = load_workbook(FILE_NAME) ws = wb.active current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S") row = [ reg_id, data.get("name", "").strip(), data.get("phone", "").strip(), data.get("email", "").strip(), data.get("vk", "").strip(), "Да" if data.get("agreeOferta") else "Нет", "Да" if data.get("agreePolicy") else "Нет", "Да" if data.get("agreeNewsletter") else "Нет", data.get("serviceName", "Разовая тренировка").strip(), float(data.get("servicePrice", 870)), "Не оплачено", data.get("yookassaPaymentId", ""), current_time, "" ] ws.append(row) new_row_index = ws.max_row data_align_left = Alignment(horizontal="left", vertical="center") data_align_center = Alignment(horizontal="center", vertical="center") for col_idx in range(1, len(row) + 1): cell = ws.cell(row=new_row_index, column=col_idx) if col_idx in [2, 4, 5, 9]: cell.alignment = data_align_left else: cell.alignment = data_align_center wb.save(FILE_NAME) def update_excel_payment_status(reg_id, status="Оплачено", paid_at=""): """Update payment status in the Excel file for a specific registration ID.""" init_excel() if not os.path.exists(FILE_NAME): return try: wb = load_workbook(FILE_NAME) ws = wb.active found = False for r in range(2, ws.max_row + 1): if str(ws.cell(row=r, column=1).value) == str(reg_id): ws.cell(row=r, column=11).value = status # Col 11: Статус оплаты ws.cell(row=r, column=14).value = paid_at # Col 14: Дата оплаты found = True break if found: wb.save(FILE_NAME) print(f"Статус оплаты заказа {reg_id} успешно обновлен в Excel на '{status}'") except Exception as e: print(f"Ошибка при обновлении статуса оплаты в Excel: {e}") # ==================================================== # YooKassa REST API Functions # ==================================================== def create_yookassa_payment(reg_id, amount, description, return_url, customer_email=None, customer_phone=None, customer_name=None): shop_id = config.YOOKASSA_SHOP_ID secret_key = config.YOOKASSA_SECRET_KEY if not shop_id or not secret_key: print("[YOOKASSA] Создание платежа пропущено: логин/пароль ЮKassa не настроены в .env") return None url = "https://api.yookassa.ru/v3/payments" headers = { "Idempotence-Key": str(uuid.uuid4()), "Content-Type": "application/json" } payload = { "amount": { "value": f"{amount:.2f}", "currency": "RUB" }, "capture": True, "confirmation": { "type": "redirect", "return_url": return_url }, "description": description[:128], "metadata": { "registration_id": reg_id } } # Add 54-FZ compliant receipt if customer contact is available if customer_email or customer_phone: clean_phone = None if customer_phone: clean_phone = "".join(filter(str.isdigit, customer_phone)) if clean_phone.startswith("8") and len(clean_phone) == 11: clean_phone = "7" + clean_phone[1:] customer = {} if customer_name: customer["full_name"] = customer_name[:256] if customer_email: customer["email"] = customer_email if clean_phone: customer["phone"] = clean_phone payload["receipt"] = { "customer": customer, "tax_system_code": config.TAX_SYSTEM_CODE, "items": [ { "description": description[:128], "quantity": "1.00", "amount": { "value": f"{amount:.2f}", "currency": "RUB" }, "vat_code": 1, # "Без НДС" (no VAT) "payment_mode": "full_payment", "payment_subject": "service", "agent_type": "bank_paying_agent", "supplier_info": { "phone": config.SUPPLIER_PHONE, "name": config.SUPPLIER_NAME, "inn": config.SUPPLIER_INN } } ] } try: response = requests.post(url, json=payload, headers=headers, auth=HTTPBasicAuth(shop_id, secret_key), timeout=15) if response.status_code == 200: res_data = response.json() payment_id = res_data.get("id") confirmation_url = res_data.get("confirmation", {}).get("confirmation_url") return {"payment_id": payment_id, "confirmation_url": confirmation_url} else: print(f"[YOOKASSA] Ошибка API ({response.status_code}): {response.text}") return None except Exception as e: print(f"[YOOKASSA] Исключение при вызове API ЮKassa: {e}") return None def check_yookassa_payment_status(payment_id): """Retrieve payment status directly from YooKassa API.""" shop_id = config.YOOKASSA_SHOP_ID secret_key = config.YOOKASSA_SECRET_KEY if not shop_id or not secret_key or not payment_id: return None url = f"https://api.yookassa.ru/v3/payments/{payment_id}" try: response = requests.get(url, auth=HTTPBasicAuth(shop_id, secret_key), timeout=15) if response.status_code == 200: res_data = response.json() return res_data.get("status") # succeeded, pending, canceled else: print(f"[YOOKASSA] Ошибка проверки платежа {payment_id}: {response.text}") return None except Exception as e: print(f"[YOOKASSA] Исключение при проверке платежа: {e}") return None # ==================================================== # Notifications Logic # ==================================================== try: import vk_api from vk_api.utils import get_random_id except ImportError: vk_api = None def send_vk_operator_notification(reg_id, data, paid=False): """Send VK chat alert to operators about registrations.""" if not vk_api: return token = getattr(config, "VK_TOKEN", "") operator_id = getattr(config, "OPERATOR_PEER_ID", 0) if not token or token == "your_vk_community_token_here" or not operator_id: return try: vk_session = vk_api.VkApi(token=token) vk = vk_session.get_api() status_emoji = "✅ [ОПЛАЧЕНО]" if paid else "⏳ [ОЖИДАЕТ ОПЛАТЫ]" message = ( f"🌐 **Новая активность на сайте ИКП!**\n\n" f"Статус: {status_emoji}\n" f"🆔 ID заявки: {reg_id}\n" f"👤 Имя: {data.get('name', '').strip()}\n" f"📞 Телефон: {data.get('phone', '').strip()}\n" f"✉️ E-mail: {data.get('email', '').strip()}\n" f"🌐 Профиль VK: {data.get('vk', '').strip()}\n" f"🏆 Услуга: {data.get('serviceName', '').strip()}\n" f"💰 Сумма: {data.get('servicePrice', '')} ₽\n\n" f"⚖️ Согласия пользователя:\n" f"• Оферта: {'Да' if data.get('agreeOferta') else 'Нет'}\n" f"• Политика ПД: {'Да' if data.get('agreePolicy') else 'Нет'}\n" f"• Рассылка: {'Да' if data.get('agreeNewsletter') else 'Нет'}\n" ) vk.messages.send( peer_id=operator_id, message=message, random_id=get_random_id() ) print(f"[VK NOTIFICATION] Отправлено уведомление оператору (ID: {operator_id}) для {reg_id}.") except Exception as e: print(f"[VK NOTIFICATION] Ошибка при отправке уведомления в VK: {e}") def send_email_notification(reg_id, data, paid=False): """Send SMTP email notification.""" smtp_server = config.SMTP_SERVER smtp_port = config.SMTP_PORT smtp_user = config.SMTP_USER smtp_password = config.SMTP_PASSWORD smtp_from = config.SMTP_FROM or smtp_user smtp_to = config.SMTP_TO or smtp_user if not smtp_server or not smtp_user or not smtp_password: print("[SMTP] Пропущено: параметры SMTP не заполнены в .env") return False status_str = "УСПЕШНО ОПЛАЧЕН" if paid else "СОЗДАН (ОЖИДАЕТ ОПЛАТЫ)" subject = f"Новый заказ на сайте ИКП #{reg_id} - {status_str}" html_content = f"""

ИКП — Детали заказа

Здравствуйте!

На сайте зарегистрирована заявка с новым статусом:

ID заявки: {reg_id}
Статус заказа: {status_str}
Выбранная услуга: {data.get('serviceName')}
Сумма платежа: {data.get('servicePrice')} ₽
Имя клиента: {data.get('name')}
Номер телефона: {data.get('phone')}
E-mail: {data.get('email')}
Профиль VK: {data.get('vk')}
Дата регистрации: {datetime.now().strftime("%d.%m.%Y %H:%M:%S")}
Это автоматическое системное уведомление от ИКП.
""" try: msg = MIMEMultipart('alternative') msg['Subject'] = subject msg['From'] = smtp_from msg['To'] = smtp_to msg.attach(MIMEText(html_content, 'html', 'utf-8')) if smtp_port == 465: server = smtplib.SMTP_SSL(smtp_server, smtp_port) else: server = smtplib.SMTP(smtp_server, smtp_port) server.starttls() server.login(smtp_user, smtp_password) server.sendmail(smtp_from, [smtp_to], msg.as_string()) server.quit() print(f"[SMTP] Письмо о заказе {reg_id} отправлено на {smtp_to}") return True except Exception as e: print(f"[SMTP] Ошибка отправки письма: {e}") return False # ==================================================== # Flask Web App Routes # ==================================================== # Template filters @app.template_filter('numberformat') def numberformat_filter(value): try: return f"{int(value):,}".replace(",", " ") except (ValueError, TypeError): return value # Serve static files logic @app.route('/') def root_index(): return app.send_static_file('index.html') @app.route('/form/') @app.route('/form/index.html') def form_index(): return app.send_static_file('form/index.html') @app.after_request def add_cors_headers(response): response.headers['Access-Control-Allow-Origin'] = '*' response.headers['Access-Control-Allow-Methods'] = 'POST, GET, OPTIONS' response.headers['Access-Control-Allow-Headers'] = 'Content-Type' return response @app.route('/api/services', methods=['GET']) def get_api_services(): try: services = db.get_services() return jsonify({"status": "success", "services": services}) except Exception as e: return jsonify({"status": "error", "message": str(e)}), 500 @app.route('/submit', methods=['POST', 'OPTIONS']) def submit(): if request.method == 'OPTIONS': return '', 204 try: data = request.json if not data: return jsonify({"status": "error", "message": "Отсутствуют данные запроса"}), 400 # Validate mandatory data name = data.get("name", "").strip() phone = data.get("phone", "").strip() email = data.get("email", "").strip() if not name or not phone or not email: return jsonify({"status": "error", "message": "Заполните обязательные поля: Имя, Телефон, E-mail"}), 400 init_excel() # Load excel to generate sequential user ID try: wb = load_workbook(FILE_NAME) ws = wb.active except Exception: init_excel() wb = load_workbook(FILE_NAME) ws = wb.active reg_id = get_next_user_id(ws) # Secure price lookup based on serviceId from SQLite database service_id = str(data.get("serviceId", "web_1")).strip() db_services = db.get_services() service_info = db_services.get(service_id) if not service_info: return jsonify({"status": "error", "message": f"Выбранная услуга '{service_id}' не найдена в базе данных"}), 400 service_name = service_info["name"] amount = service_info["price_numeric"] # 1. YooKassa checkout generation return_url = f"{request.host_url}form/index.html?success=true®_id={reg_id}&service_id={service_id}" payment_info = create_yookassa_payment( reg_id=reg_id, amount=amount, description=f"Оплата: {service_name} (ИКП)", return_url=return_url, customer_email=email, customer_phone=phone, customer_name=name ) payment_id = None payment_url = None if payment_info: payment_id = payment_info.get("payment_id") payment_url = payment_info.get("confirmation_url") # Update payload dict with secure details data_to_save = data.copy() data_to_save["yookassaPaymentId"] = payment_id or "" data_to_save["serviceName"] = service_name data_to_save["servicePrice"] = amount # 2. Save registration in SQLite database as pending db.add_web_registration(reg_id, { "name": name, "phone": phone, "email": email, "vk": data.get("vk", "").strip(), "agreeOferta": data.get("agreeOferta"), "agreePolicy": data.get("agreePolicy"), "agreeNewsletter": data.get("agreeNewsletter"), "serviceName": service_name, "servicePrice": amount, "paymentStatus": "pending", "yookassaPaymentId": payment_id }) # 3. Save initial registration in Excel add_registration_to_excel(reg_id, data_to_save) # 4. Trigger operator alerts (unpaid yet) send_vk_operator_notification(reg_id, data_to_save, paid=False) send_email_notification(reg_id, data_to_save, paid=False) res = {"status": "success", "message": "Заявка зарегистрирована!"} if payment_url: res["payment_url"] = payment_url return jsonify(res) except PermissionError: return jsonify({ "status": "error", "message": "Файл таблицы Excel заблокирован. Пожалуйста, закройте Excel на сервере и попробуйте отправить форму заново." }), 500 except Exception as e: import traceback traceback.print_exc() return jsonify({"status": "error", "message": str(e)}), 500 # ==================================================== # YooKassa Webhook Listener # ==================================================== @app.route('/webhook/yookassa', methods=['POST']) def yookassa_webhook(): try: # Enforce YooKassa IP filtering if enabled if config.VERIFY_WEBHOOK_IP: # Handle proxy and Docker headers x_forwarded_for = request.headers.get("X-Forwarded-For") if x_forwarded_for: client_ip = x_forwarded_for.split(',')[0].strip() else: client_ip = request.remote_addr if not is_yookassa_ip(client_ip): print(f"[SECURITY WARNING] Заблокирован запрос к вебхуку от недоверенного IP: {client_ip}") return jsonify({"status": "error", "message": "Access Denied: Untrusted IP"}), 403 event_data = request.json if not event_data: return 'Empty body', 400 event_type = event_data.get("event") if event_type == "payment.succeeded": payment_obj = event_data.get("object", {}) payment_id = payment_obj.get("id") metadata = payment_obj.get("metadata", {}) reg_id = metadata.get("registration_id") if reg_id: # Retrieve existing record reg = db.get_web_registration(reg_id) if reg: # Only process if status is not already paid if reg.get("payment_status") != "paid": paid_at = datetime.now().strftime("%Y-%m-%d %H:%M:%S") # 1. Update SQLite Status db.update_web_payment_status(reg_id, "paid", payment_id, paid_at) # 2. Update Excel file Status update_excel_payment_status(reg_id, "Оплачено", paid_at) # Rebuild data dict for notification helpers notify_data = { "name": reg.get("name"), "phone": reg.get("phone"), "email": reg.get("email"), "vk": reg.get("vk_link"), "agreeOferta": reg.get("agree_oferta"), "agreePolicy": reg.get("agree_policy"), "agreeNewsletter": reg.get("agree_newsletter"), "serviceName": reg.get("service_name"), "servicePrice": reg.get("amount") } # 3. Send paid success notifications send_vk_operator_notification(reg_id, notify_data, paid=True) send_email_notification(reg_id, notify_data, paid=True) print(f"[WEBHOOK] Заявка {reg_id} успешно переведена в статус ОПЛАЧЕНО.") else: print(f"[WEBHOOK] Заявка {reg_id} не найдена в базе данных.") else: print("[WEBHOOK] Отсутствует registration_id в метаданных платежа.") return 'OK', 200 except Exception as e: print(f"[WEBHOOK] Исключение при обработке вебхука: {e}") return 'Internal Server Error', 500 # ==================================================== # Admin Cabinet Views (ЛК Администратора) # ==================================================== LOGIN_TEMPLATE = """ ИКП Панель | Авторизация

ИКП Админ

Панель управления оплатами

{% if error %}
⚠️ {{ error }}
{% endif %}
""" DASHBOARD_TEMPLATE = """ ИКП Админ | Реестр оплат
ИКП Панель управления оплат
Выйти

Всего заявок

{{ stats.total }}

Оплачено заказов

{{ stats.paid_count }}

Ожидает оплаты

{{ stats.pending_count }}

Сумма сборов

{{ stats.total_revenue | int | numberformat }} ₽

🔍
{% for reg in registrations %} {% endfor %}
ID Клиент Контакты Услуга Сумма Статус Регистрация Действие
{{ reg.id }} {{ reg.name }}
📞 {{ reg.phone }}
✉️ {{ reg.email }}
{% if reg.vk_link %} {% endif %}
{{ reg.service_name }} {{ reg.amount | int }} ₽ {% if reg.payment_status == 'paid' %} Оплачено {% else %} Ожидает {% endif %}
Рег: {{ reg.created_at }}
{% if reg.paid_at %}
Опл: {{ reg.paid_at }}
{% endif %}
{% if reg.payment_status != 'paid' and reg.yookassa_payment_id %} {% else %} - {% endif %}
""" @app.route('/admin', methods=['GET']) def admin_dashboard(): if not session.get('logged_in'): return render_template_string(LOGIN_TEMPLATE) # Fetch registrations and services registrations = db.get_all_web_registrations() services = db.get_all_services_list() # Calculate stats stats = { "total": len(registrations), "paid_count": sum(1 for r in registrations if r["payment_status"] == "paid"), "pending_count": sum(1 for r in registrations if r["payment_status"] != "paid"), "total_revenue": sum(r["amount"] for r in registrations if r["payment_status"] == "paid") } return render_template_string(DASHBOARD_TEMPLATE, registrations=registrations, stats=stats, services=services) @app.route('/admin/services/add', methods=['POST']) def admin_add_service(): if not session.get('logged_in'): return jsonify({"status": "error", "message": "Не авторизован"}), 403 try: data = request.json if not data: return jsonify({"status": "error", "message": "Отсутствуют данные запроса"}), 400 service_id = data.get("id", "").strip() name = data.get("name", "").strip() price = data.get("price", "").strip() price_numeric = data.get("price_numeric") description = data.get("description", "").strip() if not service_id or not name or not price or price_numeric is None: return jsonify({"status": "error", "message": "Заполните все обязательные поля"}), 400 try: price_numeric = float(price_numeric) except ValueError: return jsonify({"status": "error", "message": "Числовая цена должна быть числом"}), 400 success = db.add_service(service_id, name, price, price_numeric, description) if success: return jsonify({"status": "success", "message": "Услуга успешно добавлена"}) else: return jsonify({"status": "error", "message": "Услуга с таким ID уже существует"}), 400 except Exception as e: return jsonify({"status": "error", "message": str(e)}), 500 @app.route('/admin/services/edit/', methods=['POST']) def admin_edit_service(service_id): if not session.get('logged_in'): return jsonify({"status": "error", "message": "Не авторизован"}), 403 try: data = request.json if not data: return jsonify({"status": "error", "message": "Отсутствуют данные запроса"}), 400 name = data.get("name", "").strip() price = data.get("price", "").strip() price_numeric = data.get("price_numeric") description = data.get("description", "").strip() is_active = data.get("is_active", 1) if not name or not price or price_numeric is None: return jsonify({"status": "error", "message": "Заполните все обязательные поля"}), 400 try: price_numeric = float(price_numeric) except ValueError: return jsonify({"status": "error", "message": "Числовая цена должна быть числом"}), 400 success = db.update_service(service_id, name, price, price_numeric, description, int(is_active)) if success: return jsonify({"status": "success", "message": "Услуга успешно сохранена"}) else: return jsonify({"status": "error", "message": "Не удалось обновить услугу"}), 400 except Exception as e: return jsonify({"status": "error", "message": str(e)}), 500 @app.route('/admin/login', methods=['POST']) def admin_login(): password = request.form.get("password") if password == config.ADMIN_PASSWORD: session['logged_in'] = True return redirect(url_for('admin_dashboard')) else: return render_template_string(LOGIN_TEMPLATE, error="Неверный пароль администратора") @app.route('/admin/logout', methods=['GET']) def admin_logout(): session.pop('logged_in', None) return redirect(url_for('admin_dashboard')) @app.route('/admin/check-payment/', methods=['POST']) def admin_check_payment(reg_id): if not session.get('logged_in'): return jsonify({"status": "error", "message": "Не авторизован"}), 403 try: reg = db.get_web_registration(reg_id) if not reg: return jsonify({"status": "error", "message": "Заявка не найдена"}), 404 payment_id = reg.get("yookassa_payment_id") if not payment_id: return jsonify({"status": "error", "message": "У этой заявки нет ID платежа ЮKassa"}), 400 yoo_status = check_yookassa_payment_status(payment_id) if not yoo_status: return jsonify({"status": "error", "message": "Не удалось связаться с ЮKassa API"}), 500 # Update if paid if yoo_status == "succeeded" and reg.get("payment_status") != "paid": paid_at = datetime.now().strftime("%Y-%m-%d %H:%M:%S") db.update_web_payment_status(reg_id, "paid", payment_id, paid_at) update_excel_payment_status(reg_id, "Оплачено", paid_at) # Send paid notifications notify_data = { "name": reg.get("name"), "phone": reg.get("phone"), "email": reg.get("email"), "vk": reg.get("vk_link"), "agreeOferta": reg.get("agree_oferta"), "agreePolicy": reg.get("agree_policy"), "agreeNewsletter": reg.get("agree_newsletter"), "serviceName": reg.get("service_name"), "servicePrice": reg.get("amount") } send_vk_operator_notification(reg_id, notify_data, paid=True) send_email_notification(reg_id, notify_data, paid=True) return jsonify({"status": "success", "payment_status": "paid"}) return jsonify({"status": "success", "payment_status": reg.get("payment_status")}) except Exception as e: return jsonify({"status": "error", "message": str(e)}), 500 def run_server(port=8000): init_excel() print(f"\n=======================================================") print(f"Flask-сервер ИКП запущен на http://localhost:{port}/") print(f"Главная страница: http://localhost:{port}/") print(f"Форма регистрации: http://localhost:{port}/form/index.html") print(f"Панель администратора: http://localhost:{port}/admin") print(f"Данные пишутся в SQLite и Excel: {FILE_NAME}") print(f"Нажмите Ctrl+C для остановки сервера.") print(f"=======================================================\n") app.run(host='0.0.0.0', port=port, debug=True) if __name__ == '__main__': run_server()