forked from Support/sp-hydra-veil-gui
Improved multi-payment cycle state management feedback
This commit is contained in:
parent
f7d792745a
commit
2cf3b60540
8 changed files with 167 additions and 43 deletions
|
|
@ -1,8 +1,10 @@
|
|||
# core
|
||||
from core.controllers.ConfigurationController import ConfigurationController
|
||||
from core.services.prepare_tickets.ticket_tracker import get_a_random_ticket
|
||||
from core.services.profiles.profile_connections import enable_profile #, disable_profile
|
||||
from core.controllers.ProfileController import ProfileController
|
||||
from core.models.Configuration import Configuration, ConnectionChoice
|
||||
from core.services.subscriptions.subscriptions_by_protocol import get_profile_subscription, start_new_payment_cycle, wipe_subscription
|
||||
from core.services.subscriptions.subscriptions_by_protocol import get_profile_subscription, start_new_payment_cycle, wipe_subscription, wipe_billing_cycle
|
||||
from core.utils.check_if_time_lapsed import check_if_time_lapsed
|
||||
from core.models.enum_models import PaymentCategory
|
||||
from core.utils.type_checker import type_checker
|
||||
|
|
@ -15,6 +17,7 @@ from core.Errors import (
|
|||
)
|
||||
|
||||
# gui
|
||||
from gui.v2.actions.ticket_actions import get_ticket_data, use_a_ticket
|
||||
from gui.v2.infrastructure.action_registry import register_action
|
||||
from gui.v2.infrastructure.take_plumbing_action import take_action
|
||||
from gui.v2.infrastructure.screen_size import get_max_screensize
|
||||
|
|
@ -43,6 +46,7 @@ def get_profile(profile_id: int):
|
|||
else:
|
||||
return Result(valid=True, data=profile)
|
||||
|
||||
|
||||
def get_ignored_exceptions(ignore_state_conflict: Optional[bool] = False):
|
||||
ignored_as_list = []
|
||||
config = Configuration.get()
|
||||
|
|
@ -73,25 +77,91 @@ def wipe_expired_subscription(profile_id: int) -> Result:
|
|||
return wiped
|
||||
|
||||
|
||||
def filter_subscription(profile_id: int) -> Result:
|
||||
@register_action(ResultError.NO_TICKET)
|
||||
def start_and_navigate_to_ticketing():
|
||||
"""Starts a new ticketing cycle. (assumes the user opted in)."""
|
||||
start_result = start_new_payment_cycle(category=PaymentCategory.TICKET)
|
||||
if start_result.valid:
|
||||
logger.info("creating a new payment cycle was valid, it worked.")
|
||||
return Result(valid=False, error_type=ResultError.NAVIGATE_TO, navigate_to="plan_picker")
|
||||
else:
|
||||
logger.info(f"Returning the error of start_result {start_result}")
|
||||
return start_result
|
||||
|
||||
|
||||
def random_ticket_dispatch(profile_id: int, passed_ui_caller: Callable) -> Result:
|
||||
"""
|
||||
User explicitly wants a random ticket.
|
||||
The reason this is in GUI repo, is because use_a_ticket is.
|
||||
"""
|
||||
|
||||
random_ticket_result = get_a_random_ticket()
|
||||
|
||||
if not random_ticket_result.valid:
|
||||
return random_ticket_result
|
||||
|
||||
which_ticket = random_ticket_result.data
|
||||
|
||||
# this is a redundant sanity check:
|
||||
if not which_ticket:
|
||||
Result(valid=False, error_type=ResultError.NO_TICKET, message="No tickets left. Would you like to get some?")
|
||||
|
||||
# calls GUI repo function:
|
||||
return use_a_ticket(profile_id=profile_id, which_ticket=which_ticket, ui_caller=passed_ui_caller)
|
||||
|
||||
|
||||
def dispatch_ticketing(profile_id: int, passed_ui_caller: Callable) -> Result:
|
||||
"""Evaluate if a random ticket will be used."""
|
||||
|
||||
use_random_setting = ConfigurationController.use_random
|
||||
|
||||
if use_random_setting:
|
||||
return random_ticket_dispatch(profile_id=profile_id, passed_ui_caller=passed_ui_caller)
|
||||
|
||||
# User does NOT want an automated selection, so they can pick from their tickets:
|
||||
do_they_have_tickets = get_ticket_data()
|
||||
|
||||
if do_they_have_tickets.valid:
|
||||
return Result(valid=False, error_type=ResultError.NAVIGATE_TO, navigate_to="ticket_or_billing_choice")
|
||||
|
||||
else:
|
||||
# user can pick between tickets or individual:
|
||||
return navigate_to_new_sub_page()
|
||||
|
||||
|
||||
def filter_subscription(profile_id: int, passed_ui_caller: Callable) -> Result:
|
||||
"""Evaluate if this profile has a subscription that's ready to connect."""
|
||||
|
||||
logger.info(f"Checking the subscription on profile id {profile_id}")
|
||||
subscription = get_profile_subscription(profile_id=profile_id)
|
||||
|
||||
#-----------------------------
|
||||
# No Subscription
|
||||
#-----------------------------
|
||||
if not subscription or not subscription.billing_code:
|
||||
# auto check tickets would be here.
|
||||
tickets_setup = ConfigurationController.are_tickets_setup()
|
||||
if tickets_setup:
|
||||
# already opted into ticketing:
|
||||
return dispatch_ticketing(profile_id=profile_id, passed_ui_caller=passed_ui_caller)
|
||||
else:
|
||||
# Starting fresh. Pick a billing type:
|
||||
return navigate_to_new_sub_page()
|
||||
|
||||
#-----------------------------
|
||||
# Pending Subscription
|
||||
#-----------------------------
|
||||
if subscription.billing_pending:
|
||||
start_result = start_new_payment_cycle(category=PaymentCategory.INDIVIDUAL, profile_id=profile_id)
|
||||
if start_result.valid:
|
||||
logger.info("creating a new payment cycle was valid, it worked.")
|
||||
return Result(valid=False, error_type=ResultError.NAVIGATE_TO, navigate_to="payment_details", message="Finish the subscription payment")
|
||||
return Result(valid=False, error_type=ResultError.NAVIGATE_TO, navigate_to="payment_details", message="Please finish your payment.")
|
||||
else:
|
||||
logger.info(f"Returning the error of start_result {start_result}")
|
||||
return start_result
|
||||
|
||||
#-----------------------------
|
||||
# Expired Subscription
|
||||
#-----------------------------
|
||||
expiration_date = subscription.expires_at
|
||||
if not expiration_date:
|
||||
# auto check tickets would be here.
|
||||
|
|
@ -101,7 +171,9 @@ def filter_subscription(profile_id: int) -> Result:
|
|||
if expired:
|
||||
return Result(valid=False, error_type=ResultError.EXPIRED_SUB, message="This subscription is expired, would you like to wipe it and get a new one?")
|
||||
|
||||
# has a valid sub:
|
||||
#-----------------------------
|
||||
# Proceed with Valid Sub
|
||||
#-----------------------------
|
||||
logger.info(f"Profile id {profile_id} has a valid subscription ready to go..")
|
||||
return Result(valid=True)
|
||||
|
||||
|
|
@ -183,10 +255,10 @@ def literally_connect(
|
|||
else:
|
||||
return Result(valid=True)
|
||||
except Exception as error:
|
||||
return _enable_error(error=error, profile_id=profile_id)
|
||||
return enable_error(error=error, profile_id=profile_id)
|
||||
|
||||
|
||||
def _enable_error(error: Type[Exception], profile_id: int) -> Result:
|
||||
def enable_error(error: Type[Exception], profile_id: int) -> Result:
|
||||
"""Translate core errors into actions and/or messages via Result Objects."""
|
||||
logger.error(str(error))
|
||||
|
||||
|
|
@ -234,7 +306,7 @@ def _enable_error(error: Type[Exception], profile_id: int) -> Result:
|
|||
return Result(valid=False, error_type=ResultError.UNKNOWN, message="An unknown error occured.")
|
||||
|
||||
|
||||
def _connect_profile(profile_id: int) -> Result:
|
||||
def _connect_profile(profile_id: int, passed_ui_caller: Callable) -> Result:
|
||||
"""Background Threaded Orchestration to Start a Connection"""
|
||||
|
||||
# # Step 1: Get the Profile
|
||||
|
|
@ -245,7 +317,7 @@ def _connect_profile(profile_id: int) -> Result:
|
|||
# profile = profile_result.data
|
||||
|
||||
# Step 2: Subscription filter:
|
||||
sub_result = filter_subscription(profile_id=profile_id)
|
||||
sub_result = filter_subscription(profile_id=profile_id, passed_ui_caller=passed_ui_caller)
|
||||
if not sub_result.valid:
|
||||
return sub_result
|
||||
|
||||
|
|
@ -259,13 +331,28 @@ def _connect_profile(profile_id: int) -> Result:
|
|||
return literally_connect(profile_id=profile_id)
|
||||
|
||||
|
||||
# def basic_test():
|
||||
# print("returning the basic test")
|
||||
# return Result(valid=False, error_type=ResultError.ACTIVE_BILLING, message="hello")
|
||||
|
||||
# @type_checker
|
||||
# def connect_profile(profile_id: int, ui_caller: Callable) -> Result:
|
||||
# """Type check before we thread"""
|
||||
# take_action(
|
||||
# action=basic_test,
|
||||
# ui_caller=ui_caller
|
||||
# )
|
||||
# return Result(valid=True)
|
||||
|
||||
|
||||
@type_checker
|
||||
def connect_profile(profile_id: int, ui_caller: Callable) -> Result:
|
||||
"""Type check before we thread"""
|
||||
take_action(
|
||||
action=_connect_profile,
|
||||
ui_caller=ui_caller,
|
||||
profile_id=profile_id
|
||||
profile_id=profile_id,
|
||||
passed_ui_caller=ui_caller
|
||||
)
|
||||
return Result(valid=True)
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,8 @@ from core.services.payment_phase.payment_state import OverallPaymentCycle, Payme
|
|||
from core.services.payment_phase.check_payment_loop import CheckPaidTimer
|
||||
from core.services.subscriptions.subscriptions_by_protocol import (
|
||||
create_new_invoice, save_billing_code,
|
||||
get_profile_subscription, wipe_billing_cycle
|
||||
get_profile_subscription, wipe_billing_cycle,
|
||||
start_new_payment_cycle
|
||||
)
|
||||
from core.models.enum_models import PaymentType, PaymentCategory
|
||||
from core.models.Result import Result, ResultError
|
||||
|
|
@ -15,10 +16,20 @@ 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
|
||||
from core.utils.type_checker import type_checker
|
||||
|
||||
# generic
|
||||
from typing import Callable, Optional
|
||||
|
||||
@type_checker
|
||||
def start_individual_payment(profile_id: int, ui_caller: Callable) -> Result:
|
||||
logger.info(f"Attempting to start individual billing state for profile {str(profile_id)}")
|
||||
take_action(
|
||||
action=start_new_payment_cycle,
|
||||
ui_caller=ui_caller,
|
||||
category=PaymentCategory.INDIVIDUAL,
|
||||
profile_id=profile_id
|
||||
)
|
||||
|
||||
def gui_starts_invoice(invoice_ui_caller: Callable, timer_resume_caller: Callable, profile_id: Optional[int] = None) -> str | None:
|
||||
|
||||
|
|
@ -109,16 +120,21 @@ def stop_payment_timer() -> Result:
|
|||
"""Gets the single instance of the payment checker timer, and stops it."""
|
||||
logger.info("Stopping the check paid timer & wiping the cycle..")
|
||||
try:
|
||||
# Wipe the cycle, so this is not the active poller (blocking others)
|
||||
wipe_billing_cycle()
|
||||
# Wipe the cycle via core, and stop the timer, so this is not the active poller (blocking others)
|
||||
wiped_result = wipe_billing_cycle()
|
||||
|
||||
# stop the timer:
|
||||
check_paid_timer = CheckPaidTimer.get_instance()
|
||||
if check_paid_timer:
|
||||
check_paid_timer.stop_polling()
|
||||
# check_paid_timer = CheckPaidTimer.get_instance()
|
||||
# if check_paid_timer:
|
||||
# check_paid_timer.stop_polling()
|
||||
|
||||
if wiped_result.valid:
|
||||
content_to_display = "Stopped! You can resume a different profile's invoice now"
|
||||
return Result(valid=True, message=content_to_display)
|
||||
else:
|
||||
error_msg = "Unable to stop the payment timer and wipe the profile inside stop_payment_timer. But didn't get an actual error."
|
||||
logger.error(error_msg)
|
||||
return Result(valid=False, error_type=ResultError.UNKNOWN, message=error_msg)
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"Unable to stop the payment timer: {str(e)}"
|
||||
|
|
|
|||
|
|
@ -22,8 +22,8 @@ def start_ticket(ui_caller: Callable) -> Result:
|
|||
logger.info("started ticketing billing state")
|
||||
return take_action(
|
||||
action=start_new_payment_cycle,
|
||||
category=PaymentCategory.TICKET
|
||||
# ui_caller=ui_caller,
|
||||
category=PaymentCategory.TICKET,
|
||||
ui_caller=ui_caller
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,16 +1,17 @@
|
|||
from core.models.Result import Result, ResultError
|
||||
from core.controllers.SyncController import new_sync
|
||||
|
||||
from core.services.subscriptions.subscriptions_by_protocol import wipe_billing_cycle
|
||||
# from gui.v2.actions.payment_actions import stop_payment_timer
|
||||
|
||||
ENUM_MAP = {
|
||||
ResultError.UNKNOWN: None,
|
||||
ResultError.NEED_SYNC: (new_sync),
|
||||
ResultError.ACTIVE_BILLING: (wipe_billing_cycle),
|
||||
ResultError.INVALID_API_REPLY: None,
|
||||
ResultError.ENCRYPTION: None,
|
||||
# ResultError.ACTIVE_BILLING: (stop_payment_timer),
|
||||
ResultError.ENCRYPTION: None
|
||||
}
|
||||
|
||||
|
||||
def register_action(error_code):
|
||||
def wrapper(func):
|
||||
ENUM_MAP[error_code] = (func)
|
||||
|
|
@ -20,8 +21,13 @@ def register_action(error_code):
|
|||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# Add later potentially:
|
||||
|
||||
# ResultError.ACTIVE_BILLING: (stop_payment_timer),
|
||||
# ResultError.NO_TICKET: (start_billing),
|
||||
# ResultError.NO_PROFILE: (go_to_new_profile_page),
|
||||
# ResultError.ALREADY_EXISTS: (get_invoice), # bypass_existing: True
|
||||
|
|
|
|||
|
|
@ -8,14 +8,16 @@ from PyQt6.QtGui import QIcon, QPixmap
|
|||
from PyQt6.QtCore import Qt
|
||||
from PyQt6 import QtCore
|
||||
|
||||
from core.services.subscriptions.subscriptions_by_protocol import start_new_payment_cycle
|
||||
from core.models.enum_models import PaymentCategory
|
||||
# from core.services.payment_phase.ticket_config_tools import do_we_have_billing_id
|
||||
from core.services.prepare_tickets.ticket_tracker import delete_ticket_data
|
||||
from core.models.Result import Result, ResultError
|
||||
|
||||
|
||||
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.actions.menu_tasks import enable_profile
|
||||
from gui.v2.actions.payment_actions import start_individual_payment
|
||||
|
||||
|
||||
HOW_MANY_PROFILES_DEFAULT = 6
|
||||
|
|
@ -158,17 +160,21 @@ class IdPage(Page):
|
|||
def go_single_profile(self):
|
||||
self.text_edit.clear()
|
||||
print("starting a single profile payment cycle")
|
||||
result = start_new_payment_cycle(
|
||||
category=PaymentCategory.INDIVIDUAL,
|
||||
profile_id=int(self.update_status.current_profile_id)
|
||||
start_individual_payment(
|
||||
profile_id=int(self.update_status.current_profile_id),
|
||||
ui_caller=self.single_profile_callback
|
||||
)
|
||||
|
||||
|
||||
def single_profile_callback(self, result: Result):
|
||||
if not result:
|
||||
print("no result.")
|
||||
return
|
||||
|
||||
if result.valid:
|
||||
billing_code = result.data
|
||||
print(f"billing_code is {billing_code}")
|
||||
# billing_data = result.data
|
||||
# billing_code = billing_data.billing_code
|
||||
# print(f"billing_code is {billing_code}")
|
||||
self.custom_window.navigator.navigate("duration_selection")
|
||||
else:
|
||||
self.show_a_popup(str(result.message))
|
||||
|
|
@ -180,7 +186,9 @@ class IdPage(Page):
|
|||
if not result.valid:
|
||||
self.show_a_popup(str(result.message))
|
||||
|
||||
billing_code = result.billing_code
|
||||
payment_cycle = result.data
|
||||
billing_code = payment_cycle.billing_code
|
||||
|
||||
if billing_code:
|
||||
self._prompt_existing_tickets(billing_code)
|
||||
else:
|
||||
|
|
|
|||
|
|
@ -94,6 +94,10 @@ class PlanPickerPage(Page):
|
|||
|
||||
def recieve_initial_billing_data(self, result: Result):
|
||||
"""Callback to recieve initial billing data instance"""
|
||||
|
||||
if not isinstance(result, Result):
|
||||
self.show_a_popup("Developer Error: recieve_initial_billing_data should not have gotten a non-result object.")
|
||||
|
||||
if not result.valid:
|
||||
error_msg = result.message
|
||||
if error_msg:
|
||||
|
|
@ -104,7 +108,7 @@ class PlanPickerPage(Page):
|
|||
# cache data to save the choice to it:
|
||||
self.billing_data = result.data
|
||||
if self.billing_data:
|
||||
print("got data")
|
||||
print("got pre-existing data")
|
||||
|
||||
def on_plan_clicked(self, item):
|
||||
plan_key = item.data(QtCore.Qt.ItemDataRole.UserRole)
|
||||
|
|
@ -114,6 +118,9 @@ class PlanPickerPage(Page):
|
|||
print(f"plan_key is {plan_key}")
|
||||
|
||||
# in temp cache save choice:
|
||||
if not self.billing_data:
|
||||
self.show_a_popup("Developer Error that ticket flow didn't produce a billing_data object in plan picker page.")
|
||||
|
||||
self.billing_data.which_key = plan_key
|
||||
print("saved the which_key. navigating to currency selection..")
|
||||
|
||||
|
|
|
|||
|
|
@ -66,9 +66,9 @@ class TicketCryptoPickerPage(Page):
|
|||
currency = selected_button.property('currency')
|
||||
self.start_initiate_payment(currency)
|
||||
|
||||
def check_if_paid_for_existing(self, temp_billing_code):
|
||||
def check_if_paid_for_existing(self, billing_code):
|
||||
self.update_status.update_status("Checking if paid...")
|
||||
result = check_if_paid(temp_billing_code, ticket_observer, connection_observer)
|
||||
result = check_if_paid(billing_code, ticket_observer, connection_observer)
|
||||
if isinstance(result, dict) and result.get('valid') and result.get('payment_status') == 'paid':
|
||||
self.update_status.update_status("Already Paid!")
|
||||
self.custom_window.navigator.navigate("ticket_prep")
|
||||
|
|
@ -78,8 +78,8 @@ class TicketCryptoPickerPage(Page):
|
|||
else:
|
||||
self.update_status.update_status("Not yet paid.")
|
||||
clipboard = QApplication.clipboard()
|
||||
clipboard.setText(temp_billing_code)
|
||||
not_paid_msg = f"The billing code is not yet showing paid for {temp_billing_code}. Right now, your clipboard has the billing code, to paste it in any text editor. If you did pay, either wait longer for blockchain confirmation, or contact customer support with the code in your clipboard now."
|
||||
clipboard.setText(billing_code)
|
||||
not_paid_msg = f"The billing code is not yet showing paid for {billing_code}. Right now, your clipboard has the billing code, to paste it in any text editor. If you did pay, either wait longer for blockchain confirmation, or contact customer support with the code in your clipboard now."
|
||||
info = QMessageBox(self)
|
||||
info.setWindowTitle("Not Paid")
|
||||
info.setText(not_paid_msg)
|
||||
|
|
@ -120,10 +120,10 @@ class TicketCryptoPickerPage(Page):
|
|||
self._prompt_wipe_existing(invoice)
|
||||
return
|
||||
elif error_code == ResultError.BILLING_CODE_EXISTS and not self.bypass_existing:
|
||||
temp_billing_code = getattr(invoice, 'temp_billing_code', None)
|
||||
print(f"temp_billing_code is {temp_billing_code}")
|
||||
if temp_billing_code:
|
||||
self._prompt_wipe_billingcode(temp_billing_code)
|
||||
billing_code = getattr(invoice, 'billing_code', None)
|
||||
print(f"billing_code is {billing_code}")
|
||||
if billing_code:
|
||||
self._prompt_wipe_billingcode(billing_code)
|
||||
return
|
||||
else:
|
||||
self._prompt_wipe_billingcode("NONE")
|
||||
|
|
@ -151,7 +151,7 @@ class TicketCryptoPickerPage(Page):
|
|||
else:
|
||||
self.update_status.update_status("Cancelled.")
|
||||
|
||||
def _prompt_wipe_billingcode(self, temp_billing_code):
|
||||
def _prompt_wipe_billingcode(self, billing_code):
|
||||
msg = QMessageBox(self)
|
||||
msg.setWindowTitle("Existing billing code found")
|
||||
msg.setText("You already have a ticket billing code. Do you want to use it? Only hit YES if you ALREADY paid.")
|
||||
|
|
@ -161,7 +161,7 @@ class TicketCryptoPickerPage(Page):
|
|||
result = msg.exec()
|
||||
if result == QMessageBox.StandardButton.Yes:
|
||||
self.update_status.update_status("Reusing same code")
|
||||
self.check_if_paid_for_existing(temp_billing_code)
|
||||
self.check_if_paid_for_existing(billing_code)
|
||||
else:
|
||||
self.bypass_existing = True
|
||||
delete_ticket_data()
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ class TicketingWorkerThread(QThread):
|
|||
)
|
||||
self.invoice_ready.emit(result)
|
||||
elif self.action == 'CHECK_PAID':
|
||||
result = check_if_paid(self.params['temp_billing_code'], ticket_observer, connection_observer)
|
||||
result = check_if_paid(self.params['billing_code'], ticket_observer, connection_observer)
|
||||
if isinstance(result, dict) and result.get('valid') and result.get('payment_status') == 'paid':
|
||||
self.paid.emit()
|
||||
elif isinstance(result, dict) and result.get('valid'):
|
||||
|
|
|
|||
Loading…
Reference in a new issue