import os import shutil from PyQt6.QtWidgets import QButtonGroup, QMessageBox, QPushButton from PyQt6.QtGui import QIcon from PyQt6.QtCore import QSize from PyQt6 import QtCore from core.Constants import Constants from gui.v2.ui.pages.Page import Page from gui.v2.ui.popups.message_box import style_message_box, mark_confirm_button from gui.v2.workers.ticketing_worker_thread import TicketingWorkerThread HOW_MANY_PROFILES_DEFAULT = 6 class TicketCryptoPickerPage(Page): def __init__(self, page_stack, main_window=None, parent=None): super().__init__("TicketCrypto", page_stack, main_window, parent) self.update_status = main_window self.selected_plan = None self.bypass_existing = False self.worker = None self.title.setText("Payment Method") self.title.setGeometry(QtCore.QRect(510, 30, 250, 40)) self.title.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter) self.button_reverse.setVisible(True) self.button_reverse.clicked.connect(self.reverse) button_info = [ ("monero", "monero", (545, 75)), ("bitcoin", "bitcoin", (545, 290)), ("lightning", "lightnering", (545, 180)), ("litecoin", "litecoin", (545, 395)), ] self.buttonGroup = QButtonGroup(self) self.buttons = [] for j, (currency, icon_name, position) in enumerate(button_info): button = QPushButton(self) button.setGeometry(position[0], position[1], 185, 75) button.setIconSize(QSize(190, 120)) button.setCheckable(True) button.setIcon(QIcon(os.path.join(self.btn_path, f"{icon_name}.png"))) button.setProperty('currency', currency) self.buttons.append(button) self.buttonGroup.addButton(button, j) button.clicked.connect(self.on_currency_selected) def set_selected_plan(self, plan_key): self.selected_plan = plan_key self.bypass_existing = False self.update_status.update_status(f"Selected plan: {plan_key}") for btn in self.buttons: btn.setChecked(False) def on_currency_selected(self): selected_button = self.buttonGroup.checkedButton() if not selected_button or not self.selected_plan: return currency = selected_button.property('currency') self.start_initiate_payment(currency) def start_initiate_payment(self, currency): self.update_status.update_status("Initiating payment...") self.worker = TicketingWorkerThread('INITIATE_PAYMENT', params={ 'how_many_profiles': HOW_MANY_PROFILES_DEFAULT, 'which_key': self.selected_plan, 'which_cryptocurrency': currency, 'bypass_existing': self.bypass_existing, }) self.worker.invoice_ready.connect(self.on_invoice_ready) self.worker.error.connect(self.on_error) self.worker.start() def on_invoice_ready(self, invoice): if invoice is None or invoice is False: self.update_status.update_status("Could not initiate payment.") return if not getattr(invoice, 'is_valid', False): return self._handle_api_errors(invoice) self.custom_window.navigator.navigate("payment_details") payment_page = self.custom_window.navigator.get_cached("payment_details") if payment_page is not None: payment_page.set_ticket_invoice(invoice, self.selected_plan) def _handle_api_errors(self, invoice): error_code = getattr(invoice, 'error_code', None) if error_code == "already_exists" and not self.bypass_existing: self._prompt_wipe_existing() return elif error_code == "billing_code_exists" and not self.bypass_existing: temp_billing_code = getattr(invoice, 'temp_billing_code', None) self._prompt_wipe_existing(temp_billing_code) return else: error_msg = getattr(invoice, 'final_error_msg', None) or error_code or "Could not initiate payment." self.update_status.update_status(error_msg) return def _prompt_wipe_existing(self, temp_billing_code=None): msg = QMessageBox(self) if temp_billing_code: msg.setWindowTitle("Existing billing code found") msg.setText("You already have a ticket billing code. Use it, or wipe it and start over?") use_button = msg.addButton("Use existing", QMessageBox.ButtonRole.AcceptRole) wipe_button = msg.addButton("Wipe and start over", QMessageBox.ButtonRole.DestructiveRole) cancel_button = msg.addButton("Cancel", QMessageBox.ButtonRole.RejectRole) mark_confirm_button(use_button) else: msg.setWindowTitle("Existing tickets found") msg.setText("You already have ticket data. Wipe it and start over?") use_button = None wipe_button = msg.addButton("Wipe and start over", QMessageBox.ButtonRole.DestructiveRole) cancel_button = msg.addButton("Cancel", QMessageBox.ButtonRole.RejectRole) mark_confirm_button(wipe_button) style_message_box(msg) msg.exec() clicked = msg.clickedButton() if temp_billing_code and clicked == use_button: self.update_status.update_status("Reusing existing billing code") self.custom_window.navigator.navigate("payment_details") payment_page = self.custom_window.navigator.get_cached("payment_details") if payment_page is not None: payment_page.resume_ticket_billing(temp_billing_code) return if clicked == wipe_button: self._restart_payment_after_wipe() else: self.update_status.update_status("Cancelled.") def _restart_payment_after_wipe(self): if not self._delete_ticket_data(): return self.bypass_existing = False currency_btn = self.buttonGroup.checkedButton() if currency_btn: self.start_initiate_payment(currency_btn.property('currency')) def _delete_ticket_data(self): paths = [ Constants.TICKET_TRACKER_PATH, os.path.join(Constants.HV_TICKETING_CONFIG_HOME, "billing_choices.json"), ] try: for path in paths: if os.path.exists(path): os.remove(path) if os.path.isdir(Constants.HV_TICKETING_DATA_HOME): shutil.rmtree(Constants.HV_TICKETING_DATA_HOME) return True except OSError as error: self.update_status.update_status(f"Could not wipe ticket data: {error}") return False def on_error(self, msg): self.update_status.update_status(f"Payment error: {msg}") def reverse(self): self.custom_window.navigator.navigate("plan_picker")