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
# final_data = "edit_session"
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 = {
"which_key": which_key,
"payment_type": "crypto",
"which_cryptocurrency": which_cryptocurrency,
"currency": which_cryptocurrency,
"how_many_profiles": how_many_profiles,
}

View file

@ -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

View file

@ -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"

View file

@ -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:

View file

@ -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,7 +182,6 @@ 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
@ -183,6 +189,8 @@ class PaymentData(StatePersistenceMixin):
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()

View file

@ -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.")

View file

@ -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":