forked from Support/sp-hydra-veil-gui
217 lines
7.6 KiB
Python
217 lines
7.6 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_individual_invoice, save_billing_code, get_profile_subscription, start_billing_state, wipe_billing_cycle
|
|
from core.models.enum_models import PaymentType, PaymentCategory
|
|
from core.models.Result import Result, ResultError
|
|
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
|
|
|
|
# generic
|
|
from typing import Callable, Optional
|
|
|
|
|
|
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
|
|
)
|
|
|
|
|
|
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()
|
|
|
|
# start a new individual one if not existing:
|
|
if not payment_cycle:
|
|
logger.info("There's no current payment cycle")
|
|
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."
|
|
|
|
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. So we're starting a new cycle.."
|
|
logger.info(msg)
|
|
result = new_individual_billing(profile_id=profile_id)
|
|
if not result.valid:
|
|
return result
|
|
billing_data = result.data
|
|
|
|
# already conflicting one:
|
|
elif profile_id and 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
|
|
|
|
#----------------------------
|
|
# 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")
|
|
|
|
|
|
|
|
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
|
|
)
|
|
|
|
#----------------------------
|
|
# FILTER ALREADY POLLING
|
|
#----------------------------
|
|
payment_cycle = OverallPaymentCycle()
|
|
if payment_cycle.already_polling:
|
|
logger.info("This cycle is already polling, so we don't need to start another poll")
|
|
return None
|
|
|
|
#----------------------------
|
|
# FILTER LACK OF DATA
|
|
#----------------------------
|
|
subscription = get_profile_subscription(profile_id)
|
|
|
|
if not subscription:
|
|
logger.info(f"Started a New Invoice for Profile {profile_id}..")
|
|
return None
|
|
|
|
if not subscription.billing_pending:
|
|
logger.error("This function should not have been called if the invoice is already paid.")
|
|
return "Invoice already paid."
|
|
|
|
if not subscription.billing_code:
|
|
return "Previous subscription didn't have a billing code."
|
|
|
|
#----------------------------
|
|
# CHECK PAID ON PRE-EXISTING INVOICE
|
|
#----------------------------
|
|
logger.info("Resuming a pre-existing payment..")
|
|
|
|
# Setup PyQt6 GUI as mainthread to bring back to:
|
|
pyqt_threader = ThreadSafetyTool()
|
|
|
|
# update the global singleton:
|
|
payment_cycle.already_polling = True
|
|
|
|
# setup the timer with the callback:
|
|
# it doesn't return result here. It calls the view_callback function.
|
|
check_paid_timer = CheckPaidTimer(
|
|
view_callback=timer_resume_caller,
|
|
billing_code=subscription.billing_code,
|
|
ui_dispatcher=pyqt_threader,
|
|
payment_type=PaymentType.CRYPTO,
|
|
connection_observer=connection_observer,
|
|
profile_id=profile_id
|
|
)
|
|
return None
|
|
|
|
|
|
|
|
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.TICKETS
|
|
logger.error("Not yet setup for ticketing")
|
|
return False
|
|
|
|
# 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
|
|
|
|
|
|
def stop_payment_timer() -> tuple:
|
|
"""Gets the single instance of the payment checker timer, and stops it."""
|
|
try:
|
|
# Wipe the cycle, so this is not the active poller (blocking others)
|
|
wipe_billing_cycle()
|
|
|
|
# stop the timer:
|
|
check_paid_timer = CheckPaidTimer.get_instance()
|
|
if check_paid_timer:
|
|
check_paid_timer.stop_polling()
|
|
|
|
content_to_display = "Stopped! You can resume a different profile's invoice now"
|
|
return True, content_to_display
|
|
|
|
except Exception as e:
|
|
error_msg = f"Unable to stop the payment timer: {str(e)}"
|
|
logger.error(error_msg)
|
|
return False, error_msg
|
|
|
|
|
|
|