forked from Support/sp-hydra-veil-gui
401 lines
13 KiB
Python
401 lines
13 KiB
Python
# core
|
|
from core.services.payment_phase.payment_state import OverallPaymentCycle, PaymentData, TicketPaymentData
|
|
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,
|
|
start_new_payment_cycle, return_pre_existing_invoice,
|
|
wipe_subscription,
|
|
)
|
|
from core.models.enum_models import PaymentType, PaymentCategory
|
|
from core.models.Result import Result, ResultError
|
|
from core.errors.logger import logger
|
|
from core.utils.type_checker import type_checker
|
|
from core.observers.Notifications import Notifications, EventType
|
|
|
|
|
|
# gui
|
|
from gui.v2.infrastructure.action_registry import register_action
|
|
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 gui.v2.actions.error_handlers import navigate_to_new_sub_page
|
|
|
|
# generic
|
|
from typing import Callable, Optional
|
|
|
|
|
|
|
|
def _wipe_and_navigate(profile_id: Optional[int] = None):
|
|
cleared_cycle = wipe_billing_cycle(profile_id)
|
|
if cleared_cycle:
|
|
return navigate_to_new_sub_page()
|
|
else:
|
|
return cleared_cycle
|
|
|
|
|
|
|
|
@register_action(ResultError.EXPIRED_INVOICE)
|
|
def reset_individual_billing_cycle() -> Result:
|
|
payment_cycle = OverallPaymentCycle()
|
|
|
|
if not payment_cycle.category == PaymentCategory.INDIVIDUAL:
|
|
return Result(valid=False, error_type=ResultError.INVALID_INPUT, message="This function isn't for tickets.")
|
|
|
|
billing_data = payment_cycle.billing_data
|
|
|
|
if not billing_data:
|
|
return _wipe_and_navigate()
|
|
|
|
profile_id = billing_data.instance_id
|
|
|
|
if profile_id:
|
|
return wipe_subscription(profile_id)
|
|
else:
|
|
return _wipe_and_navigate()
|
|
|
|
|
|
# @register_action(ResultError.EXPIRED_SUB)
|
|
# def wipe_expired_subscription(profile_id: int) -> Result:
|
|
# """Call core to wipe a subscription, then navigate to the billing page"""
|
|
# logger.info(f"Wiping the subscription for {profile_id}")
|
|
|
|
# wiped_result = wipe_subscription(profile_id)
|
|
# if wiped_result.valid:
|
|
# logger.info("Successfully wiped")
|
|
|
|
|
|
|
|
# payment_cycle = OverallPaymentCycle()
|
|
# payment_cycle.wipe_billing_cycle()
|
|
|
|
|
|
# # also clear the whole billing cycle, so it's ready for an individual purchase,
|
|
# return _wipe_and_navigate(profile_id)
|
|
# else:
|
|
# return wiped_result
|
|
|
|
|
|
@type_checker
|
|
def connect_with_subscription_already(profile_id: int, billing_code: str, ui_caller: Callable) -> Result:
|
|
logger.info(f"Using a pre-existing billing code for profile {str(profile_id)}")
|
|
|
|
|
|
|
|
@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:
|
|
|
|
logger.info(f"gui_starts_invoice is being called with profile id {profile_id}")
|
|
|
|
# Is there currently a process?
|
|
payment_cycle = OverallPaymentCycle()
|
|
|
|
if not payment_cycle:
|
|
error_msg = "There is no payment cycle yet"
|
|
logger.error(error_msg)
|
|
return error_msg
|
|
|
|
if not payment_cycle.active:
|
|
error_msg = "Process is not yet active"
|
|
logger.error(error_msg)
|
|
return error_msg
|
|
|
|
if payment_cycle.got_invoice:
|
|
print("Cycle already got an invoice")
|
|
billing_code = payment_cycle.billing_code
|
|
|
|
if not billing_code:
|
|
error_msg = "Did not save the invoice from last time."
|
|
logger.error(error_msg)
|
|
# insert function here to get it via the data inside.
|
|
return error_msg
|
|
else:
|
|
pre_existing = return_pre_existing_invoice(billing_code=billing_code, connection_observer=connection_observer)
|
|
logger.info(f"Inside GUI's start of invoice, with {pre_existing.error_type}")
|
|
if not pre_existing.valid and pre_existing.error_type == ResultError.PAID and payment_cycle.category == PaymentCategory.TICKET:
|
|
logger.info("Navigating to ticket prep page")
|
|
return Result(valid=True, error_type=NAVIGATE_TO, navigate_to="ticket_prep")
|
|
else:
|
|
logger.info("returning normally..")
|
|
return pre_existing
|
|
|
|
notification = "Fetching Invoice.."
|
|
Notifications.emit(EventType.STATUS_UPDATE, notification)
|
|
|
|
take_action(
|
|
action=create_new_invoice,
|
|
ui_caller=invoice_ui_caller,
|
|
# profile_id=profile_id,
|
|
connection_observer=connection_observer
|
|
)
|
|
|
|
|
|
def start_polling(billing_code: str, profile_id: int, ui_caller: Callable) -> str | None:
|
|
"""Kickstart an independent timer on a background thread to check if it's paid."""
|
|
|
|
payment_cycle = OverallPaymentCycle()
|
|
|
|
if not payment_cycle.active:
|
|
logger.error("Not yet active to start polling.")
|
|
|
|
#----------------------------
|
|
# FILTER ALREADY POLLING
|
|
#----------------------------
|
|
if payment_cycle.already_polling:
|
|
error_msg = "This cycle is already polling, so we don't need to start another poll"
|
|
logger.info(error_msg)
|
|
return None
|
|
|
|
#----------------------------
|
|
# FILTER LACK OF DATA
|
|
#----------------------------
|
|
if not billing_code:
|
|
return "Requires a billing code"
|
|
|
|
#----------------------------
|
|
# CHECK PAID ON PRE-EXISTING INVOICE
|
|
#----------------------------
|
|
logger.info("Starting a check paid timer..")
|
|
|
|
# Setup PyQt6 GUI as mainthread to bring back to:
|
|
pyqt_threader = ThreadSafetyTool()
|
|
|
|
# update the global singleton:
|
|
payment_cycle.already_polling = True
|
|
|
|
notification = "Checking if Paid.."
|
|
Notifications.emit(EventType.STATUS_UPDATE, notification)
|
|
|
|
# setup the timer with the callback:
|
|
# it doesn't return result here. It calls the view_callback function.
|
|
check_paid_timer = CheckPaidTimer(
|
|
view_callback=ui_caller,
|
|
billing_code=billing_code,
|
|
ui_dispatcher=pyqt_threader,
|
|
payment_type=PaymentType.CRYPTO,
|
|
connection_observer=connection_observer,
|
|
profile_id=profile_id
|
|
)
|
|
check_paid_timer.start_polling()
|
|
return None
|
|
|
|
@register_action(ResultError.BILLING_CONFLICT)
|
|
def stop_current() -> Result:
|
|
wiped_result = wipe_billing_cycle()
|
|
|
|
logger.info("Stopping current billing cycle!")
|
|
|
|
# if not wiped_result.valid:
|
|
# return wiped_result
|
|
|
|
# new_cycle_result = start_new_payment_cycle(category=PaymentCategory.INDIVIDUAL, profile_id=profile_id)
|
|
|
|
# if not new_cycle_result.valid:
|
|
# return new_cycle_result
|
|
|
|
notification = "Stopped Checking if Paid"
|
|
Notifications.emit(EventType.STATUS_UPDATE, notification)
|
|
return Result(valid=True, error_type=ResultError.NAVIGATE_TO, navigate_to="menu")
|
|
|
|
|
|
@register_action(ResultError.ACTIVE_BILLING)
|
|
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 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()
|
|
|
|
if wiped_result.valid:
|
|
content_to_display = "Stopped! You can resume a different profile's invoice now"
|
|
notification = "Stopped Checking if Paid"
|
|
Notifications.emit(EventType.STATUS_UPDATE, notification)
|
|
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)}"
|
|
logger.error(error_msg)
|
|
return Result(valid=False, error_type=ResultError.UNKNOWN, message=error_msg)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# def new_individual_billing(profile_id: int):
|
|
# """Wipes pre-existing global payment cycle. And Calls upon core to setup a new individual profile cycle & data state."""
|
|
# logger.info("starting a new individual profile state. And wiping the old one")
|
|
# return start_billing_state(
|
|
# category=PaymentCategory.INDIVIDUAL,
|
|
# profile_id=profile_id,
|
|
# wipe_existing=True
|
|
# )
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# billing_data = payment_cycle.instance
|
|
# category = payment_cycle.category
|
|
|
|
# if not billing_data:
|
|
# msg = "There is a payment cycle, but no current billing data in it."
|
|
# print(msg)
|
|
# return msg
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# #----------------------------
|
|
# # INDIVIDUAL
|
|
# #----------------------------
|
|
# if category == PaymentCategory.INDIVIDUAL:
|
|
# # already conflicting one:
|
|
# if not profile_id:
|
|
# return "Need a profile id for an individual profile."
|
|
|
|
# take_action(
|
|
# action=create_individual_invoice,
|
|
# ui_caller=invoice_ui_caller,
|
|
# profile_id=profile_id,
|
|
# connection_observer=connection_observer
|
|
# )
|
|
# #----------------------------
|
|
# # TICKETS
|
|
# #----------------------------
|
|
# elif category == PaymentCategory.TICKET:
|
|
# logger.info("Starting ticketing invoicing..")
|
|
# take_action(
|
|
# action=create_individual_invoice,
|
|
# ui_caller=invoice_ui_caller,
|
|
# connection_observer=connection_observer
|
|
# )
|
|
|
|
# else:
|
|
# error_msg = "Unsupported Payment Category"
|
|
# logger.error(error_msg)
|
|
# return error_msg
|
|
|
|
# # Finally:
|
|
# payment_cycle.got_invoice = True
|
|
# return None
|
|
|
|
|
|
|
|
|
|
|
|
# return fetch_individual_invoice(invoice_ui_caller=invoice_ui_caller, timer_resume_caller=timer_resume_caller, profile_id=profile_id)
|
|
|
|
|
|
|
|
# # 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:
|
|
# # return message
|
|
|
|
# # # start over:
|
|
# # payment_cycle.wipe()
|
|
# # new_data = new_individual_billing(profile_id)
|
|
# # if not new_data.valid:
|
|
# # return new_data # result object
|
|
# # category = PaymentCategory.INDIVIDUAL
|
|
# # # billing_data = new_cycle.data
|
|
|
|
|
|
|
|
# elif category == PaymentCategory.TICKET:
|
|
# 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:
|
|
# """
|
|
# Purpose:
|
|
# Create a new invoice if we don't have one yet, or re-uses the saved data.
|
|
# And starts polling if it's not already.
|
|
|
|
# Context:
|
|
# For individual profiles.
|
|
|
|
# Returns:
|
|
# Returns any errors as strings for UI to display.
|
|
# """
|
|
# # Fetch:
|
|
# take_action(
|
|
# action=create_individual_invoice,
|
|
# ui_caller=invoice_ui_caller,
|
|
# profile_id=profile_id,
|
|
# connection_observer=connection_observer
|
|
# )
|
|
|
|
|
|
# def save_and_begin_timer(
|
|
# ticketing: bool,
|
|
# gui_callback: Callable,
|
|
# billing_code: str,
|
|
# profile_id: Optional[int] = None,
|
|
# expires_at: Optional[str] = None,
|
|
# ) -> bool:
|
|
# """
|
|
# First saves the billing code & profile expiration, if it's an individual (non-ticket) crypto-based one.
|
|
# Then it Starts a loop of checking if the invoice is paid and saves it.
|
|
# """
|
|
|
|
# if ticketing:
|
|
# 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:
|
|
# payment_type = PaymentType.CRYPTO
|
|
# saved = save_billing_code(profile_id=profile_id, billing_code=billing_code, expires_at=expires_at)
|
|
# if not saved.valid:
|
|
# return False
|
|
# else:
|
|
# logger.error("You need a profile id and expires_at for individual profiles")
|
|
# return False
|
|
|
|
# # Setup PyQt6 GUI as mainthread to bring back to:
|
|
# pyqt_threader = ThreadSafetyTool()
|
|
|
|
# # setup the timer with the callback:
|
|
# # it doesn't return result here. It calls the gui_callback function.
|
|
# check_paid_timer = CheckPaidTimer(
|
|
# view_callback=gui_callback,
|
|
# billing_code=billing_code,
|
|
# ui_dispatcher=pyqt_threader,
|
|
# payment_type=payment_type,
|
|
# connection_observer=connection_observer,
|
|
# profile_id=profile_id
|
|
# )
|
|
# logger.info("starting polling from GUI action..")
|
|
# check_paid_timer.start_polling()
|
|
# return True # this is just that it started
|