New refactored Use of tickets and payment of tickets loops/state, integrated with existing system

This commit is contained in:
SimplifiedPrivacy 2026-10-11 12:33:21 -04:00
parent 6a445eba41
commit 10e46cd8ea
8 changed files with 97 additions and 39 deletions

View file

@ -179,3 +179,7 @@ def change_systemwide_connection(profile: Profile, new_value: str) -> Result:
profile.connection.code = new_value profile.connection.code = new_value
# final_data = "edit_session" # final_data = "edit_session"
return Result(valid=True, data=profile) return Result(valid=True, data=profile)
def save_billing_code(profile: Profile, billing_code: str) -> bool:

View file

@ -109,7 +109,7 @@ def initiate_payment(
payload = { payload = {
"which_key": which_key, "which_key": which_key,
"payment_type": "crypto", "payment_type": "crypto",
"which_cryptocurrency": which_cryptocurrency, "currency": which_cryptocurrency,
"how_many_profiles": how_many_profiles, "how_many_profiles": how_many_profiles,
} }

View file

@ -6,7 +6,8 @@ ENUM_MAP = {
ResultError.NEED_SYNC: (new_sync), ResultError.NEED_SYNC: (new_sync),
ResultError.INVALID_API_REPLY: None, ResultError.INVALID_API_REPLY: None,
ResultError.ENCRYPTION: None, ResultError.ENCRYPTION: None,
# ResultError.NO_TICKETS: (start_billing), # ResultError.NO_TICKET: (start_billing),
# ResultError.NO_PROFILE: (go_to_new_profile_page), # ResultError.NO_PROFILE: (go_to_new_profile_page),
# ResultError.ALREADY_EXISTS: (get_invoice), # bypass_existing: True # ResultError.ALREADY_EXISTS: (get_invoice), # bypass_existing: True
# ResultError.BILLING_CODE_EXISTS: (get_invoice) # bypass_existing: True # ResultError.BILLING_CODE_EXISTS: (get_invoice) # bypass_existing: True

View file

@ -21,6 +21,7 @@ class ResultError(Enum):
SUBSCRIPTION = "subscription" SUBSCRIPTION = "subscription"
EXPIRED = "expired" # meant for invoice EXPIRED = "expired" # meant for invoice
TICKET = "ticket" TICKET = "ticket"
NO_TICKET = "no_ticket"
ALREADY_EXISTS = "already_exists" ALREADY_EXISTS = "already_exists"
BILLING_CODE_EXISTS = "billing_code_exists" BILLING_CODE_EXISTS = "billing_code_exists"
PROCESS_GOT_KILLED = "process_got_killed" PROCESS_GOT_KILLED = "process_got_killed"

View file

@ -1,5 +1,5 @@
from core.infrastructure.thread_dispatch import MainThreadDispatcher 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 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 core.models.Result import Result, ResultError
# from infrastructure.observer_service import notify # from infrastructure.observer_service import notify
from core.errors.logger import logger from core.errors.logger import logger
from core.models.Result import Result, ResultError
from threading import Thread from threading import Thread
from typing import Callable from typing import Callable
@ -112,8 +113,19 @@ class CheckPaidTimer:
# Reset instance: # Reset instance:
CheckPaidTimer._instance = None CheckPaidTimer._instance = None
# SAVE: # SAVE INDIVIDUAL:
if self.payment_type == PaymentType.CRYPTO:
marked = mark_as_paid(profile_id=self.profile_id) 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: if not marked.valid:
message = marked.message message = marked.message
else: else:

View file

@ -9,7 +9,7 @@ import json
import os import os
from functools import wraps from functools import wraps
from enum import Enum from enum import Enum
from typing import Any from typing import Any, Optional
def singleton(cls): def singleton(cls):
instances = {} instances = {}
@ -100,7 +100,10 @@ class StatePersistenceMixin(ValidatedDataclass):
def load(self): def load(self):
"""Load state from file, if each item passes a type check. With Enum translation""" """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): if not os.path.exists(self._data_file):
logger.info("does NOT exist already")
return return
with open(self._data_file, "r") as f: with open(self._data_file, "r") as f:
@ -108,18 +111,20 @@ class StatePersistenceMixin(ValidatedDataclass):
for field in fields(self): for field in fields(self):
value = data.get(field.name) value = data.get(field.name)
logger.info(f"testing {field.name} of {value}")
if value is None: if value is None:
continue continue
# Translation: If the expected type is an Enum, convert the raw value back into an Enum member # 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): if isinstance(field.type, type) and issubclass(field.type, Enum):
logger.info("testing enum")
try: try:
value = field.type(value) value = field.type(value)
except ValueError: except ValueError:
logger.error(f"Invalid enum value {value} for {field.name}") logger.error(f"Invalid enum value {value} for {field.name}")
continue 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): if isinstance(value, field.type):
setattr(self, field.name, value) setattr(self, field.name, value)
@ -133,30 +138,32 @@ HOW_MANY_PROFILES_DEFAULT = 6
@singleton @singleton
@dataclass @dataclass
class TicketPaymentData(StatePersistenceMixin): 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: # metadata on it's own GLOBAL instance:
parent_folder = None
folder_path: str = None folder_path: str = None
_data_file: str = None _data_file: str = None
# actual payload data: # actual payload data:
# billing_code: str = None
currency: Currency = None currency: Currency = None
payment_type: PaymentType = PaymentType.CRYPTO payment_type: PaymentType = PaymentType.CRYPTO
# for only tickets:
which_key: str = None which_key: str = None
how_many_profiles: int = HOW_MANY_PROFILES_DEFAULT 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): def __post_init__(self):
# Global mode, assuming tickets here: # Global mode, assuming tickets here:
self.folder_path = Constants.HV_TICKETING_CONFIG_HOME 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() self.load()
@singleton @singleton
@dataclass @dataclass
class PaymentData(StatePersistenceMixin): class PaymentData(StatePersistenceMixin):
@ -175,7 +182,6 @@ class PaymentData(StatePersistenceMixin):
location_id: int = None location_id: int = None
protocol_choice: ProtocolChoice = None protocol_choice: ProtocolChoice = None
def __post_init__(self): def __post_init__(self):
# Localized mode: each ID gets its own folder # Localized mode: each ID gets its own folder
self.parent_folder = Constants.HV_PROFILE_CONFIG_HOME self.parent_folder = Constants.HV_PROFILE_CONFIG_HOME
@ -183,6 +189,8 @@ class PaymentData(StatePersistenceMixin):
os.makedirs(self.folder_path, exist_ok=True) os.makedirs(self.folder_path, exist_ok=True)
self._data_file = os.path.join(self.folder_path, "billing.json") 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() self.load()

View file

@ -100,11 +100,12 @@ def start_billing_state(category: PaymentCategory, profile_id: Optional[int] = N
return Result(valid=True, data=billing_data) return Result(valid=True, data=billing_data)
elif category == PaymentCategory.TICKET: elif category == PaymentCategory.TICKET:
billing_data = TicketPaymentData( billing_data = TicketPaymentData()
which_key=None, # billing_data = TicketPaymentData(
how_many_profiles=HOW_MANY_PROFILES_DEFAULT, # which_key=None,
payment_type=None # how_many_profiles=HOW_MANY_PROFILES_DEFAULT,
) # payment_type=None
# )
payment_cycle.instance = billing_data payment_cycle.instance = billing_data
return Result(valid=True, data=billing_data) return Result(valid=True, data=billing_data)
@ -330,8 +331,19 @@ def get_profile_subscription(profile_id: int) -> Subscription:
return profile.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: 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..") 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) 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: PRE_REQS = [profile, subscription]
# billing_data = PaymentData(instance_id=profile_id) if None in PRE_REQS:
return False
# # new profile.subscription = subscription
# if billing_data.billing_code is None: profile.save()
# billing_data.billing_code = billing_code return True
# 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.")
def return_pre_existing_payment_data(profile_id: int, subscription: SubscriptionInvoice) -> Result: 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" # "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.")

View file

@ -7,6 +7,7 @@ if TYPE_CHECKING:
from core.services.using_tickets.send_unblinded import send_unblinded_ticket_to_server 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.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.services.prepare_tickets.ticket_tracker import update_ticket_tracker
from core.models.Subscription import Subscription
from core.models.Result import Result, ResultError from core.models.Result import Result, ResultError
from core.errors.logger import logger from core.errors.logger import logger
from core.Constants import Constants from core.Constants import Constants
@ -71,6 +72,7 @@ def use_ticket_orchestrator(
################################### ###################################
valid = raw_data_dict.get("valid", None) valid = raw_data_dict.get("valid", None)
billing_code = raw_data_dict.get("billing_code", 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) message = raw_data_dict.get("message", None)
logger.info(f"Was the ticket use valid? {valid}") logger.info(f"Was the ticket use valid? {valid}")
@ -94,9 +96,13 @@ def use_ticket_orchestrator(
billing_code=billing_code 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, # 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 Result(valid=True, data=subscription)
# return {"valid": True, "data": billing_code}
# EXPIRED # EXPIRED
if not valid and message == "expired": if not valid and message == "expired":