import uuid import random import asyncio from datetime import datetime, timedelta import re from playwright.async_api import async_playwright from urllib.parse import urlparse, urlencode, parse_qs, urlunparse import logging import os from aiohttp import web, ClientSession, WSMsgType from markupsafe import escape from threading import Lock import json # import ssl # Удаляем импорт ssl, так как Nginx будет обрабатывать SSL class PaymentSystem: """ A class to manage the payment system with URL cleaning and redirect proxying, running entirely on AIOHTTP. """ # --- Configuration --- # Изменяем порт на внутренний HTTP-порт, так как Nginx будет обрабатывать HTTPS PROXY_PORT = 8080 DONATION_URL = os.getenv("DONATION_URL", "https://www.donationalerts.com/r/galaxymine") WIDGET_TOKEN = os.getenv("WIDGET_TOKEN", "jgpyLAyF57cgUEncoUcW") # ОЧЕНЬ ВАЖНО: Измените это значение в продакшене и используйте переменную окружения! API_SECRET_TOKEN = os.getenv("API_SECRET_TOKEN", "dlaqklqdmorkca") PAYMENT_TIMEOUT = timedelta(hours=6) FINAL_REDIRECT_URL = "https://bill.vortexhost.pro" STATIC_EMAIL = "static@example.com" SITE_TITLE = "VORTEXHOST PAYMENT" MIN_AMOUNTS = { "EU": 100.0, # Minimum 100 RUB for EU "RU": 1.0, # Default minimum for RU "RUCARD": 1.0 # Default minimum for RUCARD } # --- Data --- MESSAGES = [ "Удачи со стримам!", "Отличный стрим!", "Ты просто лучший!", "Я тебя обожаю!", "Крутой контент!", "Продолжай в том же духе!", "Ты огонь!", "Супер стрим!" ] # --- State Management --- payment_sessions = {} websocket_clients = {} used_messages = set() sessions_lock = Lock() websockets_lock = Lock() # --- Logger --- logger = logging.getLogger('PaymentSystem') if not logger.handlers: logger.setLevel(logging.INFO) handler = logging.FileHandler('/var/log/payment_system.log') # Log to file handler.setFormatter(logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')) logger.addHandler(handler) # Also log to console console_handler = logging.StreamHandler() console_handler.setFormatter(logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')) logger.addHandler(console_handler) def __init__(self): self.loop = None self.WIDGET_URL = f"https://www.donationalerts.com/widget/alerts?group_id=1&token={self.WIDGET_TOKEN}" def _get_unique_message(self): available_messages = [msg for msg in self.MESSAGES if msg not in self.used_messages] return random.choice(available_messages) if available_messages else None def generate_payment_link(self, payment_id): params = {"payment_id": payment_id} # Ссылка для клиента всегда должна быть HTTPS, так как Nginx будет обрабатывать SSL return f"https://pay.vortexhost.pro/payment?{urlencode(params)}" def notify_websocket_clients(self, payment_id, message): if self.loop and self.loop.is_running(): asyncio.run_coroutine_threadsafe(self.async_notify_websocket_clients(payment_id, message), self.loop) async def async_notify_websocket_clients(self, payment_id, message): with self.websockets_lock: clients = list(self.websocket_clients.get(payment_id, [])) for ws in clients: try: await ws.send_json(message) self.logger.info(f"Sent WebSocket message for payment {payment_id}: {message}") except Exception as e: self.logger.error(f"Error sending WebSocket message for payment {payment_id}: {e}") async def automate_donation_form(self, amount, message, payment_id, payment_method): for attempt in range(3): async with async_playwright() as p: browser = await p.chromium.launch(headless=True, timeout=120000) context = await browser.new_context() page = await context.new_page() page.on("request", lambda req: self.logger.debug( f"[Playwright Request] Payment {payment_id}: {req.method} {req.url}")) page.on("response", lambda res: asyncio.create_task( self.log_playwright_response(res, payment_id))) try: self.logger.info( f"Attempt {attempt + 1}: Navigating to {self.DONATION_URL} for payment {payment_id}") await page.goto(self.DONATION_URL, wait_until='domcontentloaded', timeout=60000) amount_input = await page.wait_for_selector("input.form-control.base-input", state="visible", timeout=15000) await amount_input.click() for _ in range(3): await page.keyboard.press("Backspace") await asyncio.sleep(random.uniform(0.01, 0.03)) amount_str = str(amount) for char in amount_str: await page.keyboard.type(char) await asyncio.sleep(random.uniform(0.01, 0.04)) if random.random() > 0.5: await page.keyboard.press("Backspace") await asyncio.sleep(0.02) await page.keyboard.type(amount_str[-1]) await asyncio.sleep(0.02) message_input = await page.wait_for_selector("textarea.form-control.base-textarea.editor", state="visible", timeout=15000) await message_input.click() for char in message: await page.keyboard.type(char) await asyncio.sleep(random.uniform(0.01, 0.03)) if random.random() < 0.05: await asyncio.sleep(0.1) await page.click("button.button.button-primary.button-lg", timeout=15000) await asyncio.sleep(0.5) await page.wait_for_selector("div.payment-method", state="visible", timeout=30000) if payment_method == "RU": self.logger.info(f"Selecting RU payment method for {payment_id}") await page.click("div.payment-method:nth-child(1)", timeout=15000) elif payment_method == "EU": self.logger.info(f"Selecting EU payment method for {payment_id}") await page.wait_for_selector(".methods-additional-button", state="visible", timeout=30000) await page.click(".methods-additional-button", timeout=15000) await asyncio.sleep(1) await page.wait_for_selector("div.payment-method:nth-child(4)", state="visible", timeout=30000) await page.click("div.payment-method:nth-child(4)", timeout=15000) elif payment_method == "RUCARD": self.logger.info(f"Selecting RUCARD payment method for {payment_id}") await page.wait_for_selector("div.payment-method:nth-child(2)", state="visible", timeout=30000) await page.click("div.payment-method:nth-child(2)", timeout=15000) else: self.logger.warning( f"Unknown payment method '{payment_method}' for {payment_id}. Defaulting to general card.") await page.click("div.payment-method:nth-child(1)", timeout=15000) await asyncio.sleep(0.5) email_input = await page.wait_for_selector("input.form-control.base-input.padded-left", state="visible", timeout=15000) await email_input.click() for char in self.STATIC_EMAIL: await page.keyboard.type(char) await asyncio.sleep(random.uniform(0.01, 0.03)) submit_button = await page.wait_for_selector("button.button.button-primary.button-lg", state="visible", timeout=15000) await asyncio.sleep(0.5) await submit_button.click() await asyncio.sleep(5) gateway_url = page.url self.logger.info(f"[Playwright] Final URL after clicks: {gateway_url}") if not gateway_url or gateway_url == self.DONATION_URL: raise Exception("Navigation to payment page failed or returned to initial page.") self.logger.info(f"Captured payment gateway URL for proxying: {gateway_url}") with self.sessions_lock: if payment_id in self.payment_sessions: session = self.payment_sessions[payment_id] session['status'] = 'processed' session['cookies'] = await context.cookies() session['gateway_url'] = gateway_url parsed_gateway = urlparse(gateway_url) original_query_params = parse_qs(parsed_gateway.query) # Здесь важно, чтобы клиентская ссылка была HTTPS, так как Nginx будет SSL-терминировать proxied_url_parts = list(urlparse(f"https://pay.vortexhost.pro/pay/{payment_id}")) proxied_url_parts[4] = urlencode(original_query_params, doseq=True) proxied_url_with_params = urlunparse(proxied_url_parts) self.logger.info(f"Sending obfuscated URL to client (with params): {proxied_url_with_params}") self.notify_websocket_clients(payment_id, {"status": "redirect_ready", "redirect_url": proxied_url_with_params}) return except Exception as e: self.logger.error(f"Attempt {attempt + 1} failed for payment {payment_id}: {e}", exc_info=True) if attempt == 2: self.release_payment_resources(payment_id) self.notify_websocket_clients(payment_id, {"status": "error", "message": "Failed to create payment link."}) finally: try: await page.close() await context.close() await browser.close() except Exception as e: self.logger.error(f"Error closing browser: {e}", exc_info=True) async def log_playwright_response(self, response_event, payment_id): if response_event.status >= 300 and response_event.status < 400: self.logger.info( f"--- REDIRECT DETECTED (Playwright) --- Payment {payment_id}: URL: {response_event.url}, Status: {response_event.status}, Location: {response_event.headers.get('Location')}") else: self.logger.debug( f"[Playwright Response] Payment {payment_id}: URL: {response_event.url}, Status: {response_event.status}") async def monitor_donations(self): if not self.WIDGET_TOKEN: self.logger.error("WIDGET_TOKEN is not set. Donation monitor cannot start.") return async with async_playwright() as p: browser = await p.chromium.launch(headless=True, timeout=120000) page = await browser.new_page() try: self.logger.info(f"Navigating to widget URL: {self.WIDGET_URL}") await page.goto(self.WIDGET_URL, wait_until='domcontentloaded', timeout=60000) self.logger.info("Monitoring donations...") while True: text_content = await page.evaluate("() => document.body.innerText || ''") id_matches = re.findall(r'\[ID:([0-9a-f-]{36})\]', text_content) with self.sessions_lock: for payment_id in id_matches: session = self.payment_sessions.get(payment_id) if session and session['status'] != 'completed': self.logger.info(f"Payment {payment_id} detected. Processing completion.") self.process_completed_payment(payment_id) self.cleanup_expired_payments() await asyncio.sleep(2) except Exception as e: self.logger.error(f"Error in monitor_donations: {e}", exc_info=True) finally: try: await page.close() await browser.contexts[0].close() await browser.close() except Exception as e: self.logger.error(f"Error closing browser: {e}", exc_info=True) def release_payment_resources(self, payment_id): with self.sessions_lock: if payment_id in self.payment_sessions: session = self.payment_sessions.pop(payment_id) base_message = session['message'].split(' [ID:')[0] self.used_messages.discard(base_message) self.logger.info(f"Released resources for payment {payment_id}") def process_completed_payment(self, payment_id): self.logger.info(f"Confirmation for payment ID {payment_id} would be sent here.") self.payment_sessions[payment_id]['status'] = 'completed' self.notify_websocket_clients(payment_id, {"status": "completed", "redirect_to": self.FINAL_REDIRECT_URL}) def cleanup_expired_payments(self): now = datetime.now() with self.sessions_lock: expired_pids = [ pid for pid, session in self.payment_sessions.items() if now > session['timestamp'] + self.PAYMENT_TIMEOUT ] for pid in expired_pids: self.logger.info(f"Cleaning up expired payment ID: {pid}") self.release_payment_resources(pid) self.notify_websocket_clients(pid, {"status": "expired"}) async def handle_payment_request(self, request): payment_id = request.query.get('payment_id') with self.sessions_lock: session = self.payment_sessions.get(payment_id) if not session: return web.Response(status=404, text="Payment not found.", content_type="text/plain") payment_amount, message_with_id = session['amount'], session['message'] payment_method = session.get('payment_method', 'RU') session['status'] = 'processing' asyncio.create_task(self.automate_donation_form(payment_amount, message_with_id, payment_id, payment_method)) html_content = f"""
Сумма: {escape(payment_amount)} RUB
Не закрывайте эту страницу.