From 10e46cd8eadd6d2ad9385b09c466719264a0ce07 Mon Sep 17 00:00:00 2001 From: SimplifiedPrivacy Date: Sun, 11 Oct 2026 12:33:21 -0400 Subject: [PATCH] New refactored Use of tickets and payment of tickets loops/state, integrated with existing system --- .../profile_state/update_profile.py | 4 ++ .../tickets/TicketPayController.py | 2 +- core/infrastructure/action_registry.py | 3 +- core/models/Result.py | 1 + .../payment_phase/check_payment_loop.py | 18 ++++- core/services/payment_phase/payment_state.py | 26 ++++--- .../subscriptions_by_protocol.py | 72 +++++++++++++------ .../using_tickets/use_ticket_orchestrator.py | 10 ++- 8 files changed, 97 insertions(+), 39 deletions(-) diff --git a/core/controllers/profile_state/update_profile.py b/core/controllers/profile_state/update_profile.py index 0cbf204..ac75a1d 100644 --- a/core/controllers/profile_state/update_profile.py +++ b/core/controllers/profile_state/update_profile.py @@ -179,3 +179,7 @@ def change_systemwide_connection(profile: Profile, new_value: str) -> Result: profile.connection.code = new_value # final_data = "edit_session" return Result(valid=True, data=profile) + + +def save_billing_code(profile: Profile, billing_code: str) -> bool: + \ No newline at end of file diff --git a/core/controllers/tickets/TicketPayController.py b/core/controllers/tickets/TicketPayController.py index d0c289c..f938618 100644 --- a/core/controllers/tickets/TicketPayController.py +++ b/core/controllers/tickets/TicketPayController.py @@ -109,7 +109,7 @@ def initiate_payment( payload = { "which_key": which_key, "payment_type": "crypto", - "which_cryptocurrency": which_cryptocurrency, + "currency": which_cryptocurrency, "how_many_profiles": how_many_profiles, } diff --git a/core/infrastructure/action_registry.py b/core/infrastructure/action_registry.py index e1f6b1a..fb9fd59 100644 --- a/core/infrastructure/action_registry.py +++ b/core/infrastructure/action_registry.py @@ -6,7 +6,8 @@ ENUM_MAP = { ResultError.NEED_SYNC: (new_sync), ResultError.INVALID_API_REPLY: None, ResultError.ENCRYPTION: None, - # ResultError.NO_TICKETS: (start_billing), + # ResultError.NO_TICKET: (start_billing), + # ResultError.NO_PROFILE: (go_to_new_profile_page), # ResultError.ALREADY_EXISTS: (get_invoice), # bypass_existing: True # ResultError.BILLING_CODE_EXISTS: (get_invoice) # bypass_existing: True diff --git a/core/models/Result.py b/core/models/Result.py index 7cdf256..e31af79 100644 --- a/core/models/Result.py +++ b/core/models/Result.py @@ -21,6 +21,7 @@ class ResultError(Enum): SUBSCRIPTION = "subscription" EXPIRED = "expired" # meant for invoice TICKET = "ticket" + NO_TICKET = "no_ticket" ALREADY_EXISTS = "already_exists" BILLING_CODE_EXISTS = "billing_code_exists" PROCESS_GOT_KILLED = "process_got_killed" diff --git a/core/services/payment_phase/check_payment_loop.py b/core/services/payment_phase/check_payment_loop.py index c836a6c..9b39e41 100644 --- a/core/services/payment_phase/check_payment_loop.py +++ b/core/services/payment_phase/check_payment_loop.py @@ -1,5 +1,5 @@ from core.infrastructure.thread_dispatch import MainThreadDispatcher -from core.services.subscriptions.subscriptions_by_protocol import check_paid, mark_as_paid +from core.services.subscriptions.subscriptions_by_protocol import check_paid, mark_as_paid, mark_global_tickets_paid from core.controllers.tickets.TicketPayController import check_if_ticket_paid @@ -8,6 +8,7 @@ from core.models.enum_models import PaymentType from core.models.Result import Result, ResultError # from infrastructure.observer_service import notify from core.errors.logger import logger +from core.models.Result import Result, ResultError from threading import Thread from typing import Callable @@ -112,8 +113,19 @@ class CheckPaidTimer: # Reset instance: CheckPaidTimer._instance = None - # SAVE: - marked = mark_as_paid(profile_id=self.profile_id) + # SAVE INDIVIDUAL: + if self.payment_type == PaymentType.CRYPTO: + marked = mark_as_paid(profile_id=self.profile_id) + + # SAVE TICKETS PAID: + elif self.payment_type == PaymentType.TICKET: + marked = mark_global_tickets_paid() + + else: + logger.error("Invalid payment type picked.") + marked = Result(valid=False, error_type=ResultError.INVALID_INPUT, message="Unsupported protocol.") + + # Either way evaluate: if not marked.valid: message = marked.message else: diff --git a/core/services/payment_phase/payment_state.py b/core/services/payment_phase/payment_state.py index 8be6568..bab9926 100644 --- a/core/services/payment_phase/payment_state.py +++ b/core/services/payment_phase/payment_state.py @@ -9,7 +9,7 @@ import json import os from functools import wraps from enum import Enum -from typing import Any +from typing import Any, Optional def singleton(cls): instances = {} @@ -100,7 +100,10 @@ class StatePersistenceMixin(ValidatedDataclass): def load(self): """Load state from file, if each item passes a type check. With Enum translation""" + logger.info("Starting load of pre-existing data") + if not os.path.exists(self._data_file): + logger.info("does NOT exist already") return with open(self._data_file, "r") as f: @@ -108,18 +111,20 @@ class StatePersistenceMixin(ValidatedDataclass): for field in fields(self): value = data.get(field.name) + logger.info(f"testing {field.name} of {value}") if value is None: continue # Translation: If the expected type is an Enum, convert the raw value back into an Enum member if isinstance(field.type, type) and issubclass(field.type, Enum): + logger.info("testing enum") try: value = field.type(value) except ValueError: logger.error(f"Invalid enum value {value} for {field.name}") continue - # Now the type check works because 'value' is now an actual Enum member + # type check works because 'value' is now an actual Enum member if isinstance(value, field.type): setattr(self, field.name, value) @@ -133,30 +138,32 @@ HOW_MANY_PROFILES_DEFAULT = 6 @singleton @dataclass class TicketPaymentData(StatePersistenceMixin): - PAYLOAD_FIELDS = {"billing_code", "currency", "which_key", "how_many_profiles", "payment_type"} + PAYLOAD_FIELDS = {"currency", "which_key", "how_many_profiles", "payment_type"} # metadata on it's own GLOBAL instance: - parent_folder = None folder_path: str = None _data_file: str = None # actual payload data: - # billing_code: str = None currency: Currency = None payment_type: PaymentType = PaymentType.CRYPTO - - # for only tickets: which_key: str = None how_many_profiles: int = HOW_MANY_PROFILES_DEFAULT + use_random: bool = False + temp_billing_code: Optional[str] = None + billing_pending: bool = True + def __post_init__(self): # Global mode, assuming tickets here: self.folder_path = Constants.HV_TICKETING_CONFIG_HOME - self._data_file = "billing.json" + os.makedirs(self.folder_path, exist_ok=True) + self._data_file = os.path.join(self.folder_path, "billing_choices.json") self.load() + @singleton @dataclass class PaymentData(StatePersistenceMixin): @@ -175,13 +182,14 @@ class PaymentData(StatePersistenceMixin): location_id: int = None protocol_choice: ProtocolChoice = None - def __post_init__(self): # Localized mode: each ID gets its own folder self.parent_folder = Constants.HV_PROFILE_CONFIG_HOME self.folder_path = os.path.join(self.parent_folder, str(self.instance_id)) os.makedirs(self.folder_path, exist_ok=True) self._data_file = os.path.join(self.folder_path, "billing.json") + + logger.info(f"full path of self._data_file {self._data_file}") self.load() diff --git a/core/services/subscriptions/subscriptions_by_protocol.py b/core/services/subscriptions/subscriptions_by_protocol.py index 3a684dd..af0623e 100644 --- a/core/services/subscriptions/subscriptions_by_protocol.py +++ b/core/services/subscriptions/subscriptions_by_protocol.py @@ -100,11 +100,12 @@ def start_billing_state(category: PaymentCategory, profile_id: Optional[int] = N return Result(valid=True, data=billing_data) elif category == PaymentCategory.TICKET: - billing_data = TicketPaymentData( - which_key=None, - how_many_profiles=HOW_MANY_PROFILES_DEFAULT, - payment_type=None - ) + billing_data = TicketPaymentData() + # billing_data = TicketPaymentData( + # which_key=None, + # how_many_profiles=HOW_MANY_PROFILES_DEFAULT, + # payment_type=None + # ) payment_cycle.instance = billing_data return Result(valid=True, data=billing_data) @@ -330,8 +331,19 @@ def get_profile_subscription(profile_id: int) -> Subscription: return profile.subscription +def mark_global_tickets_paid() -> Result: + """Conclude the payment phase, by marking the global tickets as paid""" + try: + ticket_payments = TicketPaymentData() + ticket_payments.billing_pending = False + ticket_payments.save() + return Result(valid=True) + except Exception as e: + return Result(valid=False, error_type=ResultError.UNKNOWN, message=str(e)) + + def mark_as_paid(profile_id: int) -> Result: - """Conclude the payment phase, by marking the sub as paid""" + """Conclude the payment phase, by marking an individual subscription as paid""" logger.info("Marking as paid..") @@ -379,25 +391,17 @@ def save_billing_code(profile_id: int, billing_code: str, expires_at: datetime) return Result(valid=False, message=error_msg, error_type=ResultError.INVALID_INPUT) +# this can be modified to accept an id, once the UI has a cache layer: +def save_subscription(profile: Profile, subscription: Subscription) -> bool: + """Saves subscription with the code and date.""" -# # get the payment data singleton: -# billing_data = PaymentData(instance_id=profile_id) + PRE_REQS = [profile, subscription] + if None in PRE_REQS: + return False -# # new -# if billing_data.billing_code is None: -# billing_data.billing_code = billing_code -# billing_data.save() -# logger.info("saved billing_code") -# return Result(valid=True) - -# # Already exists and is different: -# if regular_billing.billing_code != billing_code: -# error_msg = "Critical Error! Billing codes do NOT match" -# logger.error(error_msg) -# return Result(valid=False, error_type=ResultError.UNKNOWN, message=error_msg) - -# # already saved: -# return Result(valid=True, message="Already saved.") + profile.subscription = subscription + profile.save() + return True def return_pre_existing_payment_data(profile_id: int, subscription: SubscriptionInvoice) -> Result: @@ -469,3 +473,25 @@ def return_pre_existing_payment_data(profile_id: int, subscription: Subscription # "payment_type": "crypto" # } + + +# Was cut from the end of save a billing code: + +# # get the payment data singleton: +# billing_data = PaymentData(instance_id=profile_id) + +# # new +# if billing_data.billing_code is None: +# billing_data.billing_code = billing_code +# billing_data.save() +# logger.info("saved billing_code") +# return Result(valid=True) + +# # Already exists and is different: +# if regular_billing.billing_code != billing_code: +# error_msg = "Critical Error! Billing codes do NOT match" +# logger.error(error_msg) +# return Result(valid=False, error_type=ResultError.UNKNOWN, message=error_msg) + +# # already saved: +# return Result(valid=True, message="Already saved.") diff --git a/core/services/using_tickets/use_ticket_orchestrator.py b/core/services/using_tickets/use_ticket_orchestrator.py index 4f56921..3f4ce02 100644 --- a/core/services/using_tickets/use_ticket_orchestrator.py +++ b/core/services/using_tickets/use_ticket_orchestrator.py @@ -7,6 +7,7 @@ if TYPE_CHECKING: from core.services.using_tickets.send_unblinded import send_unblinded_ticket_to_server from core.utils.basic_operations.write_string_to_text_file import write_string_to_text_file from core.services.prepare_tickets.ticket_tracker import update_ticket_tracker +from core.models.Subscription import Subscription from core.models.Result import Result, ResultError from core.errors.logger import logger from core.Constants import Constants @@ -71,6 +72,7 @@ def use_ticket_orchestrator( ################################### valid = raw_data_dict.get("valid", None) billing_code = raw_data_dict.get("billing_code", None) + expires_at = raw_data_dict.get("expires_at", None) message = raw_data_dict.get("message", None) logger.info(f"Was the ticket use valid? {valid}") @@ -94,9 +96,13 @@ def use_ticket_orchestrator( billing_code=billing_code ) + subscription = Subscription( + billing_code=billing_code, + expires_at=expires_at, + ) + # even if not saved return it back, so it can be saved with the profile by other functions, - return Result(valid=True, data=billing_code) - # return {"valid": True, "data": billing_code} + return Result(valid=True, data=subscription) # EXPIRED if not valid and message == "expired":