forked from Support/sp-hydra-veil-gui
Stable Ticket Invoices for the initial invoice
This commit is contained in:
parent
e071b0f502
commit
307e6800f1
6 changed files with 178 additions and 87 deletions
|
|
@ -7,6 +7,7 @@ from core.models.Result import Result, ResultError
|
|||
from core.errors.logger import logger
|
||||
|
||||
# gui
|
||||
from gui.v2.actions.ticket_actions import fetch_ticket_invoice
|
||||
from gui.v2.infrastructure.ThreadSafetyTool import ThreadSafetyTool
|
||||
from gui.v2.infrastructure.take_plumbing_action import take_action
|
||||
from gui.v2.infrastructure.setup_observers import connection_observer
|
||||
|
|
@ -51,8 +52,15 @@ def gui_starts_invoice(invoice_ui_caller: Callable, timer_resume_caller: Callabl
|
|||
return result
|
||||
billing_data = result.data
|
||||
|
||||
#----------------------------
|
||||
# INVOICE DATA
|
||||
#----------------------------
|
||||
if category == PaymentCategory.INDIVIDUAL:
|
||||
# already conflicting one:
|
||||
elif profile_id and profile_id != payment_cycle.profile_id:
|
||||
if not profile_id:
|
||||
return "Need a profile id for an individual profile."
|
||||
|
||||
if profile_id != payment_cycle.profile_id:
|
||||
logger.info("The new profile id request is different than the pre-existing running one. So we're wiping it and stopping the timer.")
|
||||
worked, message = stop_payment_timer()
|
||||
if not worked:
|
||||
|
|
@ -66,18 +74,12 @@ def gui_starts_invoice(invoice_ui_caller: Callable, timer_resume_caller: Callabl
|
|||
category = PaymentCategory.INDIVIDUAL
|
||||
# billing_data = new_cycle.data
|
||||
|
||||
#----------------------------
|
||||
# INVOICE DATA
|
||||
#----------------------------
|
||||
if category == PaymentCategory.INDIVIDUAL:
|
||||
if profile_id:
|
||||
return fetch_individual_invoice(invoice_ui_caller=invoice_ui_caller, timer_resume_caller=timer_resume_caller, profile_id=profile_id)
|
||||
else:
|
||||
return "Need a profile id for an individual profile."
|
||||
|
||||
|
||||
elif category == PaymentCategory.TICKET:
|
||||
print("ready to start for tickets")
|
||||
|
||||
logger.info("Starting ticketing invoicing..")
|
||||
return fetch_ticket_invoice(invoice_ui_caller=invoice_ui_caller, timer_resume_caller=timer_resume_caller)
|
||||
|
||||
|
||||
def fetch_individual_invoice(invoice_ui_caller: Callable, timer_resume_caller: Callable, profile_id: int) -> str | None:
|
||||
|
|
@ -162,9 +164,8 @@ def save_and_begin_timer(
|
|||
"""
|
||||
|
||||
if ticketing:
|
||||
payment_type = PaymentType.TICKETS
|
||||
logger.error("Not yet setup for ticketing")
|
||||
return False
|
||||
payment_type = PaymentType.TICKET
|
||||
logger.error("Ticketing already saves the billing code on it's own when it recieves it.")
|
||||
|
||||
# NON-ticketing:
|
||||
elif profile_id and expires_at:
|
||||
|
|
|
|||
|
|
@ -1,15 +1,69 @@
|
|||
# core
|
||||
from core.services.payment_phase.payment_state import OverallPaymentCycle, PaymentData, TicketPaymentData
|
||||
from core.models.enum_models import PaymentType
|
||||
from core.controllers.tickets.TicketPayController import initiate_payment
|
||||
from core.services.subscriptions.subscriptions_by_protocol import start_billing_state
|
||||
from core.services.payment_phase.payment_state import OverallPaymentCycle, TicketPaymentData
|
||||
from core.models.enum_models import PaymentType, PaymentCategory
|
||||
from core.controllers.tickets.UseTicketController import get_unused_tickets
|
||||
from core.models.Result import Result, ResultError
|
||||
# from core.controllers.ProfileController import ProfileController
|
||||
from core.errors.logger import logger
|
||||
|
||||
# gui
|
||||
from gui.v2.infrastructure.ThreadSafetyTool import ThreadSafetyTool
|
||||
from gui.v2.infrastructure.take_plumbing_action import take_action
|
||||
from gui.v2.infrastructure.setup_observers import connection_observer, ticket_observer
|
||||
|
||||
from typing import Callable
|
||||
|
||||
|
||||
|
||||
def start_ticket(ui_caller: Callable) -> Result:
|
||||
logger.info("started ticketing billing state")
|
||||
return take_action(
|
||||
action=start_billing_state,
|
||||
ui_caller=ui_caller,
|
||||
category=PaymentCategory.TICKET
|
||||
)
|
||||
|
||||
|
||||
def fetch_ticket_invoice(invoice_ui_caller: Callable, timer_resume_caller: Callable) -> str | None:
|
||||
"""Start the Ticketing process on the background thread with the relevant billing data."""
|
||||
|
||||
logger.info("fetching ticket invoice..")
|
||||
|
||||
payment_cycle = OverallPaymentCycle()
|
||||
|
||||
if not payment_cycle:
|
||||
"No payment cycle, and therefore no data."
|
||||
|
||||
# already existing but wrong format:
|
||||
if payment_cycle.category != PaymentCategory.TICKET:
|
||||
return "Wrong payment cycle category."
|
||||
|
||||
billing_data = payment_cycle.instance
|
||||
|
||||
if not billing_data:
|
||||
return "No billing data"
|
||||
|
||||
# if not isinstance(billing_data, TicketPaymentData):
|
||||
# return "Billing data in the wrong format."
|
||||
|
||||
which_key = billing_data.which_key
|
||||
currency = billing_data.currency
|
||||
how_many_profiles = billing_data.how_many_profiles
|
||||
|
||||
# Fetch (this function will complain if missing data):
|
||||
take_action(
|
||||
action=initiate_payment,
|
||||
ui_caller=invoice_ui_caller,
|
||||
how_many_profiles=how_many_profiles,
|
||||
which_key=which_key,
|
||||
which_cryptocurrency=currency,
|
||||
ticket_observer=ticket_observer,
|
||||
connection_observer=connection_observer,
|
||||
bypass_existing=True,
|
||||
)
|
||||
logger.info("Initiated payment")
|
||||
|
||||
|
||||
def get_ticket_data() -> Result:
|
||||
|
|
|
|||
|
|
@ -182,17 +182,18 @@ class Navigator(QObject):
|
|||
def _on_preload_data_ready(self, name, payload):
|
||||
self._preload_inflight -= 1
|
||||
if name in self._instances:
|
||||
logger.debug(f"[preload] {name:30s} SKIP (already cached, async result dropped)")
|
||||
# logger.debug(f"[preload] {name:30s} SKIP (already cached, async result dropped)")
|
||||
pass
|
||||
else:
|
||||
try:
|
||||
page = self._instantiate(name, prepared=payload)
|
||||
self.register_instance(name, page)
|
||||
self._preload_done += 1
|
||||
logger.debug(f"[preload] {name:30s} OK (async, cached={len(self._instances)}, "
|
||||
f"stack_size={self.page_stack.count()})")
|
||||
# logger.debug(f"[preload] {name:30s} OK (async, cached={len(self._instances)}, "
|
||||
# f"stack_size={self.page_stack.count()})")
|
||||
except Exception as e:
|
||||
self._preload_failed += 1
|
||||
logger.debug(f"[preload] {name:30s} FAIL (async build) {type(e).__name__}: {e}")
|
||||
# logger.debug(f"[preload] {name:30s} FAIL (async build) {type(e).__name__}: {e}")
|
||||
self._maybe_emit_preload_summary()
|
||||
|
||||
def _on_preload_failed(self, name, error):
|
||||
|
|
@ -204,7 +205,7 @@ class Navigator(QObject):
|
|||
self._preload_done += 1
|
||||
else:
|
||||
self._preload_failed += 1
|
||||
self._maybe_emit_preload_summary()
|
||||
# self._maybe_emit_preload_summary()
|
||||
|
||||
def _cleanup_preload_worker(self, name):
|
||||
worker = self._preload_workers.pop(name, None)
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ from core.models.enum_models import Currency
|
|||
from core.services.payment_phase.payment_state import PaymentData
|
||||
|
||||
# gui
|
||||
from gui.v2.ui.pages.Page import Page
|
||||
from gui.v2.ui.pages.Page import Page, DataError
|
||||
|
||||
import os
|
||||
from PyQt6 import QtCore
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
# core
|
||||
from core.services.subscriptions.subscriptions_by_protocol import paying_for_tickets
|
||||
from core.services.subscriptions.subscriptions_by_protocol import paying_for_tickets, get_current_billing_data
|
||||
from core.services.payment_phase.payment_state import PaymentData
|
||||
from core.models.pydantic_models.ServerReply import SubscriptionInvoice
|
||||
from core.models.invoice.TicketInvoice import TicketInvoice
|
||||
from core.models.enum_models import Currency
|
||||
from core.models.Result import Result
|
||||
|
||||
|
|
@ -40,6 +41,12 @@ from PyQt6.QtWidgets import (
|
|||
)
|
||||
from PyQt6.QtCore import Qt
|
||||
from PyQt6.QtGui import QIcon
|
||||
from enum import Enum
|
||||
|
||||
# For formatting errors graphically:
|
||||
class DataError(Enum):
|
||||
FORMAT: "format"
|
||||
BLANK: "blank"
|
||||
|
||||
|
||||
class PaymentDetailsPage(Page):
|
||||
|
|
@ -73,9 +80,12 @@ class PaymentDetailsPage(Page):
|
|||
|
||||
|
||||
def showEvent(self, event):
|
||||
"""Refresh data. This runs every time the widget is displayed"""
|
||||
|
||||
# Call the parent class showEvent first
|
||||
super().showEvent(event)
|
||||
# runs every time the widget is displayed
|
||||
|
||||
self.ticketing = paying_for_tickets()
|
||||
self.begin_invoice()
|
||||
|
||||
|
||||
|
|
@ -172,7 +182,7 @@ class PaymentDetailsPage(Page):
|
|||
|
||||
print(f"profile_id is {self.profile_id}")
|
||||
|
||||
if not self.profile_id:
|
||||
if not self.ticketing and not self.profile_id:
|
||||
self.display_error(
|
||||
whats_wrong=DataError.BLANK,
|
||||
whats_it_called="Profile ID",
|
||||
|
|
@ -281,13 +291,13 @@ class PaymentDetailsPage(Page):
|
|||
)
|
||||
return
|
||||
|
||||
if not isinstance(invoice, SubscriptionInvoice):
|
||||
self.display_error(
|
||||
whats_wrong=DataError.FORMAT,
|
||||
whats_it_called="invoice",
|
||||
the_thing=invoice,
|
||||
)
|
||||
return
|
||||
# if not isinstance(invoice, SubscriptionInvoice):
|
||||
# self.display_error(
|
||||
# whats_wrong=DataError.FORMAT,
|
||||
# whats_it_called="invoice",
|
||||
# the_thing=invoice,
|
||||
# )
|
||||
# return
|
||||
|
||||
self.fill_in_invoice_data(invoice)
|
||||
self.update_status.update_status("Invoice generated. Awaiting payment...")
|
||||
|
|
@ -309,6 +319,8 @@ class PaymentDetailsPage(Page):
|
|||
def fill_in_invoice_data(self, invoice: SubscriptionInvoice):
|
||||
"""Extract and populate invoice data into the labels"""
|
||||
|
||||
print(f"filling in {invoice}")
|
||||
|
||||
# Extract from the already validated object:
|
||||
billing_code = str(invoice.billing_code)
|
||||
duration = str(invoice.expires_at)
|
||||
|
|
@ -409,7 +421,7 @@ class PaymentDetailsPage(Page):
|
|||
self.custom_window.navigator.navigate("menu")
|
||||
|
||||
def _get_currency(self):
|
||||
"""Get the currency from prior cached PaymentData"""
|
||||
"""Get the currency from prior cached Payment Data"""
|
||||
|
||||
if not self.profile_id or not isinstance(self.profile_id, int):
|
||||
self.display_error(
|
||||
|
|
@ -418,23 +430,36 @@ class PaymentDetailsPage(Page):
|
|||
the_thing=self.profile_id,
|
||||
)
|
||||
|
||||
billing_data = PaymentData(instance_id=self.profile_id)
|
||||
# get the singleton (this was created on a previous page)
|
||||
data_result = get_current_billing_data()
|
||||
|
||||
if not billing_data:
|
||||
self.display_error(
|
||||
whats_wrong=DataError.BLANK,
|
||||
whats_it_called="Profile's Billing Data",
|
||||
the_thing=billing_data,
|
||||
)
|
||||
if not data_result.valid:
|
||||
message = data_result.message
|
||||
if not message:
|
||||
message = "Error fetching data"
|
||||
self.show_a_popup(str(message))
|
||||
|
||||
currency = billing_data.currency
|
||||
# get the singleton (this was created on a previous page)
|
||||
billing_singleton = data_result.data
|
||||
|
||||
if not billing_singleton:
|
||||
message = "Error, no billing data"
|
||||
self.show_a_popup(str(message))
|
||||
|
||||
# billing_data = PaymentData(instance_id=self.profile_id)
|
||||
|
||||
# if not billing_data:
|
||||
# self.display_error(
|
||||
# whats_wrong=DataError.BLANK,
|
||||
# whats_it_called="Profile's Billing Data",
|
||||
# the_thing=billing_data,
|
||||
# )
|
||||
|
||||
currency = billing_singleton.currency
|
||||
|
||||
if not currency:
|
||||
self.display_error(
|
||||
whats_wrong=DataError.BLANK,
|
||||
whats_it_called="currency",
|
||||
the_thing=currency,
|
||||
)
|
||||
self.show_a_popup('Missing Currency')
|
||||
|
||||
|
||||
if not isinstance(currency, Currency):
|
||||
self.display_error(
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
# core
|
||||
from core.services.subscriptions.subscriptions_by_protocol import start_billing_state
|
||||
from core.models.enum_models import PaymentCategory
|
||||
from core.models.Result import Result
|
||||
|
||||
# gui
|
||||
# from gui.v2.actions.payment_actions import start_billing_state
|
||||
from gui.v2.actions.ticket_actions import start_ticket
|
||||
from gui.v2.ui.pages.Page import Page
|
||||
from gui.v2.ui.styles.styles import TERMINAL_LIST_QSS
|
||||
from gui.v2.workers.ticketing_worker_thread import TicketingWorkerThread
|
||||
|
|
@ -53,16 +53,7 @@ class PlanPickerPage(Page):
|
|||
|
||||
def start_sync(self):
|
||||
# STATE
|
||||
result = start_billing_state(category=PaymentCategory.TICKET)
|
||||
if not result.valid:
|
||||
error_msg = result.message
|
||||
if error_msg:
|
||||
self.show_a_popup(str(error_msg))
|
||||
else:
|
||||
self.show_a_popup("Error with Starting Billing")
|
||||
|
||||
# cache data to save the choice to it:
|
||||
self.billing_data = result.data
|
||||
result = start_ticket(ui_caller=self.recieve_initial_billing_data)
|
||||
|
||||
# Begin Tickets:
|
||||
self.plan_list.clear()
|
||||
|
|
@ -73,27 +64,18 @@ class PlanPickerPage(Page):
|
|||
self.worker.error.connect(self.on_error)
|
||||
self.worker.start()
|
||||
|
||||
def on_sync_done(self, pricing_data):
|
||||
if isinstance(pricing_data, dict) and pricing_data.get('valid') is False:
|
||||
err = pricing_data.get('error_code', 'unknown')
|
||||
self.status_label.setText(f"Sync failed: {err}")
|
||||
self.update_status.update_status(f"Sync failed: {err}")
|
||||
def on_sync_done(self, pricing_data: Result):
|
||||
if not pricing_data.valid:
|
||||
self.status_label.setText("Pricing data fetch failed.")
|
||||
self.show_a_popup(pricing_data.message)
|
||||
return
|
||||
|
||||
if isinstance(pricing_data, dict) and isinstance(pricing_data.get('data'), dict):
|
||||
plans = pricing_data['data']
|
||||
elif isinstance(pricing_data, dict):
|
||||
plans = pricing_data
|
||||
else:
|
||||
self.status_label.setText("Unexpected pricing data.")
|
||||
return
|
||||
|
||||
self.pricing_data = plans
|
||||
self.populate_plans(plans)
|
||||
self.pricing_data = pricing_data.data
|
||||
self.populate_plans(self.pricing_data)
|
||||
self.status_label.setText("Pick a plan above.")
|
||||
self.update_status.update_status("Plans loaded.")
|
||||
|
||||
def populate_plans(self, pricing_data):
|
||||
def populate_plans(self, pricing_data: dict):
|
||||
self.plan_list.clear()
|
||||
if not isinstance(pricing_data, dict):
|
||||
self.status_label.setText("Unexpected pricing data.")
|
||||
|
|
@ -110,6 +92,20 @@ class PlanPickerPage(Page):
|
|||
item.setData(QtCore.Qt.ItemDataRole.UserRole, plan_key)
|
||||
self.plan_list.addItem(item)
|
||||
|
||||
def recieve_initial_billing_data(self, result: Result):
|
||||
"""Callback to recieve initial billing data instance"""
|
||||
if not result.valid:
|
||||
error_msg = result.message
|
||||
if error_msg:
|
||||
self.show_a_popup(str(error_msg))
|
||||
else:
|
||||
self.show_a_popup("Error with Starting Billing")
|
||||
|
||||
# cache data to save the choice to it:
|
||||
self.billing_data = result.data
|
||||
if self.billing_data:
|
||||
print("got data")
|
||||
|
||||
def on_plan_clicked(self, item):
|
||||
plan_key = item.data(QtCore.Qt.ItemDataRole.UserRole)
|
||||
if not plan_key:
|
||||
|
|
@ -119,14 +115,14 @@ class PlanPickerPage(Page):
|
|||
|
||||
# in temp cache save choice:
|
||||
self.billing_data.which_key = plan_key
|
||||
print("saved the which_key. navigating to currency selection..")
|
||||
|
||||
# go to next page:
|
||||
currency_page = self.custom_window.navigator.get_cached("currency_selection")
|
||||
if currency_page is not None:
|
||||
# currency_page = self.custom_window.navigator.get_cached("currency_selection")
|
||||
# if currency_page is not None:
|
||||
self.custom_window.navigator.navigate("currency_selection")
|
||||
|
||||
|
||||
|
||||
# self.custom_window.navigator.navigate("ticket_crypto_picker")
|
||||
# crypto_page = self.custom_window.navigator.get_cached("ticket_crypto_picker")
|
||||
# if crypto_page is not None:
|
||||
|
|
@ -138,3 +134,17 @@ class PlanPickerPage(Page):
|
|||
|
||||
def reverse(self):
|
||||
self.custom_window.navigator.navigate("id")
|
||||
|
||||
|
||||
# LEGACY:
|
||||
|
||||
# if isinstance(pricing_data, dict) and pricing_data.get('valid') is False:
|
||||
# err = pricing_data.get('error_code', 'unknown')
|
||||
# self.status_label.setText(f"Sync failed: {err}")
|
||||
# self.update_status.update_status(f"Sync failed: {err}")
|
||||
# return
|
||||
# if isinstance(pricing_data, dict) and isinstance(pricing_data.get('data'), dict):
|
||||
# plans = pricing_data['data']
|
||||
# elif isinstance(pricing_data, dict):
|
||||
# plans = pricing_data
|
||||
# else:
|
||||
|
|
|
|||
Loading…
Reference in a new issue