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"
)
vk.messages.send(
peer_id=operator_id,
message=message,
random_id=get_random_id()
)
except Exception as e:
print(f"[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_raw = config.SMTP_TO or smtp_user
smtp_to = [email.strip() for email in smtp_to_raw.split(',')]
print(f"[SMTP] Настройки: Server={smtp_server}, Port={smtp_port}, User={smtp_user}, To={smtp_to}")
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")} |
Это автоматическое системное уведомление от ИКП.
"""
server = None
try:
msg = MIMEMultipart('alternative')
msg['Subject'] = subject
msg['From'] = smtp_from
msg['To'] = ', '.join(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()) # smtp_to — список
print(f"[SMTP] Письмо о заказе {reg_id} отправлено на {smtp_to}")
return True
except Exception as e:
print(f"[SMTP] Ошибка отправки письма: {e}")
return False
finally:
if server:
try:
server.quit()
except Exception:
pass
# ====================================================
# 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}?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 }} ₽
| ID |
Клиент |
Контакты |
Услуга |
Сумма |
Статус |
Регистрация |
Действие |
{% for reg in registrations %}
| {{ 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 %}
|
{% endfor %}
Каталог услуг
Управление тарифами и ценами
| ID услуги |
Название |
Отображаемая цена |
Числовая цена (для YooKassa) |
Описание / Игры |
Статус |
Действия |
{% for svc in services %}
| {{ svc.id }} |
{{ svc.name }} |
{{ svc.price }} |
{{ svc.price_numeric | int }} ₽ |
{{ svc.description }} |
{% if svc.is_active == 1 %}
Активна
{% else %}
Отключена
{% endif %}
|
|
{% endfor %}
"""
@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()