import os from PyQt6.QtWidgets import QApplication, QLabel, QLineEdit, QPushButton from PyQt6.QtGui import QIcon, QPixmap from PyQt6.QtCore import QSize, QTimer from PyQt6 import QtCore from gui.v2.ui.pages.Page import Page from gui.v2.ui.popups.qrcode_dialog import QRCodeDialog from gui.v2.workers.ticketing_worker_thread import TicketingWorkerThread from gui.v2.workers.worker_thread import WorkerThread class PaymentDetailsPage(Page): def __init__(self, page_stack, main_window=None, parent=None): super().__init__("Payment Details", page_stack, main_window, parent) self.update_status = main_window self.text_fields = [] self.invoice_data = {} self.selected_duration = None self.selected_currency = None self.ticketing = False self.temp_billing_code = None self._ticket_check_running = False self._ticket_worker = None self.ticket_poll_timer = QTimer(self) self.ticket_poll_timer.timeout.connect(self._tick_check_paid) self.create_interface_elements() self.button_reverse.setVisible(True) def create_interface_elements(self): self.title = QLabel("Payment Details", self) self.title.setGeometry(QtCore.QRect(530, 30, 210, 40)) self.title.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter) self.button_reverse.clicked.connect(self.reverse) self.qr_code_button = QPushButton(self) self.qr_code_button.setGeometry(360, 435, 50, 50) qr_icon = QIcon(os.path.join(self.btn_path, "qr-code.png")) self.qr_code_button.setIcon(qr_icon) self.qr_code_button.setIconSize(self.qr_code_button.size()) self.qr_code_button.clicked.connect(self.show_qr_code) self.qr_code_button.setToolTip("Show QR Code") self.qr_code_button.setDisabled(True) labels_info = [ ("Your new billing ID", None, (20, 40), (240, 50)), ("", "cuadro400x50", (20, 90), (400, 50)), ("Duration", None, (20, 155), (150, 50)), ("", "cuadro150x50", (200, 155), (150, 50)), ("Amount", None, (10, 220), (150, 50)), ("", "cuadro150x50", (200, 220), (150, 50)), ("Hit this white icon to copy paste the address:", None, (15, 305), (500, 30)), ("", "cuadro400x50", (20, 350), (400, 50)), ("Hit this icon to Scan the QR code:", None, (15, 450), (360, 30)), ] for text, image_name, position, size in labels_info: label = QLabel(self) label.setGeometry(position[0], position[1], size[0], size[1]) if "Hit" in text: label.setStyleSheet("font-size: 16px;") label.setAlignment( QtCore.Qt.AlignmentFlag.AlignLeft | QtCore.Qt.AlignmentFlag.AlignVCenter) else: label.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter) if image_name: pixmap = QPixmap(os.path.join( self.btn_path, f"{image_name}.png")) label.setPixmap(pixmap) label.setScaledContents(True) else: label.setText(text) line_edit_info = [ (20, 90, 400, 50), (200, 155, 150, 50), (200, 220, 150, 50), (20, 350, 400, 50), ] for x, y, width, height in line_edit_info: line_edit = QLineEdit(self) line_edit.setGeometry(x, y, width, height) line_edit.setReadOnly(True) self.text_fields.append(line_edit) line_edit.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter) copy_button_info = [ (430, 90, 0), (360, 220, 2), (480, 290, 3), ] for x, y, field_index in copy_button_info: button = QPushButton(self) button.setGeometry(x, y, 50, 50) icon = QIcon(os.path.join(self.btn_path, "paste_button.png")) button.setIcon(icon) button.setIconSize(button.size()) button.clicked.connect( lambda checked, index=field_index: self.copy_text(index)) currency_button_info = [ ("monero", (545, 75)), ("bitcoin", (545, 290)), ("lightnering", (545, 180)), ("litecoin", (545, 395)) ] self.currency_display_buttons = [] for icon_name, position in currency_button_info: button = QPushButton(self) button.setGeometry(position[0], position[1], 185, 75) button.setIconSize(QSize(190, 120)) button.setCheckable(True) button.setEnabled(False) self.currency_display_buttons.append(button) button.setIcon( QIcon(os.path.join(self.btn_path, f"{icon_name}.png"))) def fetch_invoice_duration(self): duration_month_num = int(self.selected_duration.split()[0]) if duration_month_num == 12: total_hours = 365 * 24 else: total_hours = duration_month_num * (30 * 24) return total_hours def display_selected_options(self): if self.selected_duration: self.text_fields[1].setText(self.selected_duration) self.text_fields[1].setStyleSheet('font-size: 13px;') if self.selected_currency: currency_index_map = { "monero": 0, "bitcoin": 1, "lightning": 2, "litecoin": 3 } currency_index = currency_index_map.get(self.selected_currency, -1) if currency_index >= 0: for i, button in enumerate(self.currency_display_buttons): button.setChecked(i == currency_index) button.setEnabled(i == currency_index) def on_request_invoice(self): total_hours = self.fetch_invoice_duration() if self.selected_currency is None: self.update_status.update_status('No currency selected') return profile_data = { 'id': int(self.update_status.current_profile_id), 'duration': total_hours, 'currency': 'xmr' if self.selected_currency == 'monero' else 'btc' if self.selected_currency == 'bitcoin' else 'btc-ln' if self.selected_currency == 'lightning' else 'ltc' if self.selected_currency == 'litecoin' else None } self.update_status.update_status('Generating Invoice...') self.worker_thread = WorkerThread( 'GET_SUBSCRIPTION', profile_data=profile_data) self.worker_thread.text_output.connect(self.invoice_update_text_output) self.worker_thread.invoice_output.connect( self.on_invoice_generation_finished) self.worker_thread.invoice_finished.connect(self.on_invoice_finished) self.worker_thread.start() def invoice_update_text_output(self, text): self.update_status.update_status(text) if 'No compatible' in text: for line in self.text_fields: line.setText('') def store_invoice_data(self, billing_details: dict, duration: int): self.invoice_data = { 'billing_details': billing_details, 'duration': duration } def check_invoice(self): self.display_selected_options() total_hours = self.fetch_invoice_duration() if self.invoice_data and self.invoice_data['duration'] == total_hours: self.update_status.update_status('Checking invoice status...') self.check_invoice_status( self.invoice_data['billing_details'].billing_code) else: self.on_request_invoice() def check_invoice_status(self, billing_code: str): self.worker_thread = WorkerThread('CHECK_INVOICE_STATUS', profile_data={ 'billing_code': billing_code}) self.worker_thread.invoice_finished.connect( self.on_invoice_status_finished) self.worker_thread.start() def on_invoice_status_finished(self, result): if result: self.parse_invoice_data(self.invoice_data['billing_details']) else: self.update_status.update_status( 'Invoice has expired. Generating new invoice...') self.on_request_invoice() def on_invoice_generation_finished(self, billing_details: object, text: str): total_hours = self.fetch_invoice_duration() self.store_invoice_data(billing_details, duration=total_hours) self.parse_invoice_data(billing_details) def parse_invoice_data(self, invoice_data: object): billing_details = { 'billing_code': invoice_data.billing_code } if self.selected_currency.lower() == 'lightning': currency = 'Bitcoin Lightning' else: currency = self.selected_currency preferred_method = next( (pm for pm in invoice_data.payment_methods if pm.name.lower() == currency.lower()), None) if preferred_method: billing_details['due_amount'] = preferred_method.due billing_details['address'] = preferred_method.address else: self.update_status.update_status( 'No payment method found for the selected currency.') return billing_values = list(billing_details.values()) text_field_indices = [0, 2, 3] for i, dict_value in enumerate(billing_values): if i < 3: field_index = text_field_indices[i] if i == 2: text = str(dict_value) self.full_address = text metrics = self.text_fields[field_index].fontMetrics() width = self.text_fields[field_index].width() elided_text = metrics.elidedText( text, QtCore.Qt.TextElideMode.ElideMiddle, width) self.text_fields[field_index].setText(elided_text) elif i == 1: text = str(dict_value) self.text_fields[field_index].setProperty("fullText", text) self.text_fields[field_index].setText(text) font_size = 14 if len(text) > 9: font_size = 15 if len(text) > 13: font_size = 10 if len(text) > 16: font_size = 10 self.text_fields[field_index].setStyleSheet( f"font-size: {font_size}px; font-weight: bold;") else: self.text_fields[field_index].setProperty( "fullText", str(dict_value)) self.text_fields[field_index].setText(str(dict_value)) self.qr_code_button.setDisabled(False) self.update_status.update_status( 'Invoice generated. Awaiting payment...') def on_invoice_finished(self, result): if result: self.invoice_data.clear() self.show_payment_confirmed(self.text_fields[0].text()) return self.update_status.update_status( 'An error occurred when generating invoice') def show_payment_confirmed(self, billing_code): self.custom_window.navigator.navigate("payment_confirmed") payment_page = self.custom_window.navigator.get_cached("payment_confirmed") if payment_page is not None: for line in self.text_fields: line.setText('') payment_page.set_billing_code(billing_code) else: print("PaymentConfirmed page not found") def find_payment_confirmed_page(self): return self.custom_window.navigator.get_cached("payment_confirmed") def copy_text(self, field: int): try: original_status = self.update_status.status_label.text() original_status = original_status.replace("Status: ", "") if int(field) == 3: text = self.full_address else: text = self.text_fields[int(field)].text() QApplication.clipboard().setText(text) if field == 0: self.update_status.update_status( 'Billing code copied to clipboard!') elif field == 3: self.update_status.update_status( 'Address copied to clipboard!') else: self.update_status.update_status( 'Pay amount copied to clipboard!') QTimer.singleShot( 2000, lambda: self.update_status.update_status(original_status)) except AttributeError: self.update_status.update_status( 'No content available for copying') except Exception as e: self.update_status.update_status( f'An error occurred when copying the text') def reverse(self): if self.ticket_poll_timer.isActive(): self.ticket_poll_timer.stop() if self.ticketing: self.ticketing = False self.custom_window.navigator.navigate("ticket_crypto_picker") return self.custom_window.navigator.navigate("currency_selection") def set_ticket_invoice(self, invoice, plan_key): self.ticketing = True self.temp_billing_code = getattr(invoice, 'temp_billing_code', None) raw_currency = getattr(invoice, 'selected_currency', None) or '' self.selected_currency = self._normalize_currency(raw_currency) self.selected_duration = f"Plan {plan_key}" if plan_key else "Plan" self._populate_ticket_fields(invoice) if self.ticket_poll_timer.isActive(): self.ticket_poll_timer.stop() self.ticket_poll_timer.start(3000) self.update_status.update_status('Awaiting ticket payment...') def _populate_ticket_fields(self, invoice): self.text_fields[0].setText(str(getattr(invoice, 'temp_billing_code', '') or '')) self.text_fields[1].setText(self.selected_duration) self.text_fields[1].setStyleSheet('font-size: 13px;') amount = getattr(invoice, 'due_amount', '') or '' self.text_fields[2].setText(str(amount)) addr = str(getattr(invoice, 'address', '') or '') self.full_address = addr metrics = self.text_fields[3].fontMetrics() width = self.text_fields[3].width() elided = metrics.elidedText(addr, QtCore.Qt.TextElideMode.ElideMiddle, width) self.text_fields[3].setText(elided) currency_index_map = {'monero': 0, 'bitcoin': 1, 'lightning': 2, 'litecoin': 3} idx = currency_index_map.get(self.selected_currency, -1) if idx >= 0: for i, btn in enumerate(self.currency_display_buttons): btn.setChecked(i == idx) btn.setEnabled(i == idx) self.qr_code_button.setDisabled(False) def _normalize_currency(self, raw): if not raw: return None raw = str(raw).lower() m = { 'monero': 'monero', 'xmr': 'monero', 'bitcoin': 'bitcoin', 'btc': 'bitcoin', 'lightning': 'lightning', 'btc-ln': 'lightning', 'ln': 'lightning', 'litecoin': 'litecoin', 'ltc': 'litecoin', } return m.get(raw, raw) def _tick_check_paid(self): if not self.temp_billing_code or self._ticket_check_running: return self._ticket_check_running = True self._ticket_worker = TicketingWorkerThread('CHECK_PAID', params={ 'temp_billing_code': self.temp_billing_code }) self._ticket_worker.paid.connect(self._on_ticket_paid) self._ticket_worker.not_paid.connect(self._on_ticket_not_paid) self._ticket_worker.paid_check_failed.connect(self._on_ticket_check_failed) self._ticket_worker.error.connect(self._on_ticket_check_failed) self._ticket_worker.finished.connect(self._on_ticket_check_done) self._ticket_worker.start() def _on_ticket_check_done(self): self._ticket_check_running = False def _on_ticket_paid(self): self.ticket_poll_timer.stop() self.ticketing = False self.update_status.update_status('Payment received.') self.custom_window.navigator.navigate("ticket_prep") prep_page = self.custom_window.navigator.get_cached("ticket_prep") if prep_page is not None: prep_page.start_prep() def _on_ticket_not_paid(self): self.update_status.update_status('Awaiting ticket payment...') def _on_ticket_check_failed(self, msg): self.update_status.update_status(f'Payment check failed: {msg}') def show_qr_code(self): full_amount = self.text_fields[2].text() if hasattr(self, 'full_address') and self.full_address: currency_type = getattr(self, 'selected_currency', None) qr_dialog = QRCodeDialog( self.full_address, full_amount, currency_type, self) qr_dialog.exec() else: self.update_status.update_status( 'No address available for QR code')