hydraveil-gui/gui/v2/actions/payment_actions.py

218 lines
7.8 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.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
# 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
#----------------------------
# INVOICE DATA
#----------------------------
if category == PaymentCategory.INDIVIDUAL:
# already conflicting one:
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:
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
return fetch_individual_invoice(invoice_ui_caller=invoice_ui_caller, timer_resume_caller=timer_resume_caller, profile_id=profile_id)
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
)
#----------------------------
# 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.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
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