Improved Multi-payment-cycle management. Moved check_paid and mark_paid to isolated service. Setup primary configuration to track ticket setup

This commit is contained in:
SimplifiedPrivacy 2026-10-17 11:33:39 -04:00
parent e296e212db
commit 1f4b37dbe9
12 changed files with 408 additions and 122 deletions

View file

@ -152,3 +152,28 @@ class ConfigurationController:
configuration.singbox = new_value
configuration.save()
return True
@staticmethod
def change_random_ticket(new_value: bool) -> bool:
configuration = ConfigurationController.get()
configuration.use_random = new_value
configuration.save()
return True
@staticmethod
def use_random() -> bool:
configuration = ConfigurationController.get()
return configuration.use_random
@staticmethod
def tickets_setup(new_value: bool) -> bool:
configuration = ConfigurationController.get()
configuration.tickets_setup = new_value
configuration.save()
return True
@staticmethod
def are_tickets_setup() -> bool:
configuration = ConfigurationController.get()
return configuration.tickets_setup

View file

@ -0,0 +1,10 @@
from core.models.Result import Result, ResultError
from core.errors.logger import logger
def not_supported():
NOT_SUPPORTED_PAYMENT = "This is not a supported payment category"
logger.error(NOT_SUPPORTED_PAYMENT)
return Result(valid=False, error_type=ResultError.INVALID_INPUT, message=NOT_SUPPORTED_PAYMENT)

View file

@ -51,27 +51,35 @@ class ThreadPlumbing:
def _on_complete(self, future: Future):
"""Handle thread completion"""
try:
logger.info("The plumbing module started it's 'on complete' phase..")
logger.info("The plumbing module started it's 'on complete' phase with this result:")
result = future.result()
if not isinstance(result, Result):
result = convert_to_result(result)
logger.info(result)
if not result.valid:
if not result.function_choice:
result.function_choice = resolve_prescription(result, self.ENUM_MAP, *self.args, **self.kwargs)
logger.info(f"result.function_choice is {result.function_choice}")
else:
logger.info("Using the pre-existing function choice for this error.")
except Exception as e:
error_msg = f"There was an error: {str(e)}"
# notify(error_msg)
logger.error(error_msg)
result = Result(valid=False, error_type=ResultError.UNKNOWN, message=str(e))
# Back to main thread
print(f"result in plumbing is {result}")
self.ui_dispatcher.run_on_main_thread(self._on_result_ready, result)
finally:
# Back to main thread
print(f"result in plumbing is {result}")
self.ui_dispatcher.run_on_main_thread(self._on_result_ready, result)
def _on_result_ready(self, result: Result):
"""Decide: retry, show popup, or final callback"""
logger.info(f"We are on the main thread with result {result}")
if result.valid or self.attempt_count >= MAX_ATTEMPTS:
if self.ui_caller:
print("calling back with result")
@ -89,11 +97,11 @@ class ThreadPlumbing:
if self.ui_caller:
self.ui_caller(result)
# notify(f"Error prompt {self.attempt_count} of {MAX_ATTEMPTS}")
# Show popup and wait for user choice
choice = bool(result.function_choice)
print(f"is there a choice? {choice}")
logger.info(f"On the main thread, the choice is {choice}")
popup = self.error_implementation(
choice=bool(result.function_choice),
title=f"Error: {result.error_type.value}",
@ -131,17 +139,21 @@ def resolve_prescription(result: Result, ENUM_MAP: dict, *args, **kwargs):
And then add the args from either the original caller's args,
or the defaults for that error type
"""
logger.info(f"resolve_prescription is being called for {result}.")
# Priority 1: If the function explicitly provided a full partial, use it.
if result.function_choice:
logger.info("The result object itself has a function choice!")
return result.function_choice
# Priority 2: Look up the Enum map
# logger.info(f"self.ENUM_MAP is {self.ENUM_MAP}")
logger.info("checking enum map")
if result.error_type not in ENUM_MAP:
logger.info(f"No solution in the ENUM_MAP of {ENUM_MAP}")
return None # No solution
enum_value = ENUM_MAP[result.error_type]
logger.info(f"got enum_value {enum_value}")
# Handle the None case (no solution)
if enum_value is None:

View file

@ -36,6 +36,7 @@ import shutil
import tempfile
from sqlalchemy.orm import Session
from enum import Enum
import inspect
from core.models.manage.session_management import init_session, get_session

View file

@ -33,7 +33,8 @@ class Configuration(BaseModel):
dns: Optional[bool] = False
did_sudo_setup: Optional[bool] = False
singbox: Optional[str] = None
tickets_setup: Optional[bool] = False
use_random: Optional[bool] = False
@field_validator('last_synced_at', mode='before')

View file

@ -26,6 +26,8 @@ class ResultError(Enum):
STATE_CONFLICT = "state_conflict"
TICKET = "ticket"
NO_TICKET = "no_ticket"
RANDOM_TICKETS_OFF = "random_tickets_off"
TICKET_SETUP = "ticket_setup"
ALREADY_EXISTS = "already_exists"
BILLING_CODE_EXISTS = "billing_code_exists"
PROCESS_GOT_KILLED = "process_got_killed"

View file

@ -135,7 +135,7 @@ def api_call_and_extraction(
#-----------------------------
# Layer 2: Validity of Server's Reply:
pre_validated_reply = api_response.data
logger.info("Validating the Validity of the Server's Reply..")
logger.info(f"Validating the Validity of the Server's Reply {pre_validated_reply}")
worked, server_reply = validate_model(ServerReply, pre_validated_reply)
error_msg = f"Validation of the Server's Reply had issues! Pre-validate: {pre_validated_reply}. Post validate: {server_reply}"
@ -152,7 +152,7 @@ def api_call_and_extraction(
logger.info("Validation successfull. Checking if the server's reply says the operation worked..")
if not server_reply.valid:
error_message = f"Server reply was correctly structured, but the actual invoice failed to be created. The raw data is {raw_reply}"
error_message = f"Server reply was correctly structured, but the actual invoice failed to be created. The raw data is {server_reply}"
logger.error(error_message)
# translate by enum error code:
return translate_server_failure(server_reply)

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.payment_phase.payment_update_tools import check_paid, mark_as_paid
from core.controllers.tickets.TicketPayController import check_if_ticket_paid
from core.observers.ConnectionObserver import ConnectionObserver

View file

@ -151,7 +151,7 @@ class TicketPaymentData(StatePersistenceMixin):
how_many_profiles: int = HOW_MANY_PROFILES_DEFAULT
use_random: bool = False
temp_billing_code: Optional[str] = None
billing_code: Optional[str] = None
billing_pending: bool = True
paid: bool = False
@ -204,7 +204,7 @@ class OverallPaymentCycle():
already_polling: bool = False
profile_id: int = None
active: bool = False
active_data: Any = None
billing_data: Any = None
got_invoice: bool = False
billing_code: str = None
@ -215,7 +215,7 @@ class OverallPaymentCycle():
def wipe(self):
"""Wipe all data and start over."""
self.active = False
self.active_data = None
self.billing_data = None
self.category = PaymentCategory.INDIVIDUAL
self.already_polling = False
self.profile_id = None

View file

@ -0,0 +1,114 @@
from core.models.manage.server_reply_tools import api_call_and_extraction, ApiCall
from core.controllers.ProfileController import ProfileController
from core.services.payment_phase.payment_state import OverallPaymentCycle
from core.models.enum_models import PaymentCategory
from core.observers.ConnectionObserver import ConnectionObserver
from core.Constants import Constants
from core.models.Result import Result, ResultError
from core.errors.logger import logger
from core.errors.error_shortcuts import not_supported
def _mark_profile_as_paid(profile_id: int):
"""Save to disk that an individual profile is paid."""
logger.info(f"Marking profile_id {str(profile_id)} as paid..")
# Get real profile object:
profile = ProfileController.get(profile_id)
if not profile:
error_msg = f"Invalid profile id of {profile_id}"
logger.error(error_msg)
return Result(valid=False, message=error_msg, error_type=ResultError.INVALID_INPUT)
profile.subscription.billing_pending = False
ProfileController.update(profile)
return Result(valid=True)
def mark_as_paid() -> Result:
"""Conclude the payment phase for ALL profiles."""
logger.info("Marking as paid..")
payment_cycle = OverallPaymentCycle()
if not payment_cycle:
error_msg = "There is no current billing cycle."
return Result(valid=False, error_type=ResultError.INVALID_INPUT, message=error_msg)
billing_data = payment_cycle.instance
if not billing_data:
error_msg = "There is no current billing data."
return Result(valid=False, error_type=ResultError.INVALID_INPUT, message=error_msg)
category = payment_cycle.category
if category == PaymentCategory.INDIVIDUAL:
profile_id = billing_data.instance_id
result = _mark_profile_as_paid(profile_id)
if not result.valid:
return result
elif category == PaymentCategory.TICKET:
# saving global ticket data:
billing_data.billing_pending = False
billing_data.paid = True
billing_data.save()
else:
return not_supported()
# finally:
payment_cycle.wipe()
return Result(valid=True)
def check_paid(billing_code: str, connection_observer: ConnectionObserver) -> Result:
"""
Purpose:
Evaluates Payment Status for EITHER payment category
Requires:
Valid OverallPaymentCycle
billing_code is provided by the timer for BOTH types.
"""
payment_cycle = OverallPaymentCycle()
category = payment_cycle.category
logger.info(f"Checking paid with category of {category}")
# Get endpoint:
if category == PaymentCategory.INDIVIDUAL:
url = f'{Constants.SP_BASE_URL}/v2/check_paid'
elif category == PaymentCategory.TICKET:
url = f'{Constants.TICKET_API_BASE_URL}/check_ticket_paid'
else:
return Result(valid=False, error_type=ResultError.INVALID_INPUT, message="This is not a supported payment category to check")
submit_data = {
"billing_code": billing_code
}
result = api_call_and_extraction(
desired_type="payment_status",
method=ApiCall.POST,
url=url,
connection_observer=connection_observer,
payload=submit_data
)
if not result.valid:
return result
payment_status = result.data
# paid
if payment_status:
return Result(valid=True, message="paid")
else:
return Result(valid=False, error_type=ResultError.UNPAID, message="The API call went through, but this subscription is not yet paid.")

View file

@ -1,3 +1,4 @@
from core.controllers.ConfigurationController import ConfigurationController
from core.utils.basic_operations.write_or_read_from_json import read_entire_json, update_json, write_json_to_file
from core.utils.basic_operations.does_file_exist import does_file_exist
from core.errors.exceptions import *
@ -11,6 +12,52 @@ from pathlib import Path
import shutil
def does_ticket_tracker_exist() -> tuple:
if os.path.exists(Constants.TICKET_TRACKER_PATH):
return (True, Constants.TICKET_TRACKER_PATH)
else:
return (False, Constants.TICKET_TRACKER_PATH)
def ticket_tracker_exists() -> bool:
return os.path.exists(Constants.TICKET_TRACKER_PATH)
def ticket_config_exists():
filepath = f"{Constants.HV_TICKETING_CONFIG_HOME}/billing_choices.json"
return does_file_exist(filepath)
def get_a_random_ticket() -> Result:
"""Get a random ticket"""
results = get_all_unused_tickets()
valid = results.get("valid")
tickets = results.get("data")
if valid and tickets:
one_random_ticket = tickets.random()
return Result(valid=True, data=one_random_ticket)
message = result.get("message")
if message == "no_tickets_left":
return Result(valid=False, error_type=ResultError.NO_TICKET, message="You do not have any tickets left, would you like to start a new phase to get them?")
elif message == "invalid_data":
return Result(valid=False, error_type=ResultError.TICKET_SETUP, message="The ticket tracker file is either missing values or corrupted.")
elif not ticket_config_exists():
return Result(valid=False, error_type=ResultError.TICKET_SETUP, message="You have not yet setup the ticketing config.")
elif not does_ticket_tracker_exist():
return Result(valid=False, error_type=ResultError.TICKET_SETUP, message="You have not yet setup the ticket tracker. The file is gone, corrupted, or missing.")
else:
return Result(valid=False, error_type=ResultError.TICKET_SETUP, message="For unknown reasons, we are unable to read the ticket tracker.")
def delete_ticket_data():
ticket_folders = [Constants.HV_TICKETING_CONFIG_HOME, Constants.HV_TICKETING_DATA_HOME]
quantity_to_delete = len(ticket_folders)
@ -66,13 +113,6 @@ def is_given_ticket_able_to_be_read(which_ticket: int) -> bool:
return False
def does_ticket_tracker_exist() -> tuple:
if os.path.exists(Constants.TICKET_TRACKER_PATH):
return (True, Constants.TICKET_TRACKER_PATH)
else:
return (False, Constants.TICKET_TRACKER_PATH)
def get_data_for_a_single_ticket(which_ticket_as_int: int) -> tuple:
data = read_entire_json(Constants.TICKET_TRACKER_PATH)

View file

@ -1,3 +1,4 @@
from core.services.payment_phase.check_payment_loop import CheckPaidTimer
from core.models.manage.server_reply_tools import api_call_and_extraction, ApiCall
from core.controllers.ProfileController import ProfileController
from core.models.BaseProfile import BaseProfile as Profile
@ -10,16 +11,12 @@ from core.models.Subscription import Subscription
from core.Constants import Constants
from core.models.Result import Result, ResultError
from core.errors.logger import logger
from core.errors.error_shortcuts import not_supported
# generic
from datetime import datetime
from typing import Optional
def not_supported():
NOT_SUPPORTED_PAYMENT = "This is not a supported payment category"
logger.error(NOT_SUPPORTED_PAYMENT)
return Result(valid=False, error_type=ResultError.INVALID_INPUT, message=NOT_SUPPORTED_PAYMENT)
def invalid_server_format() -> Result:
error_msg = "The server gave an invalid response format."
@ -91,53 +88,125 @@ def is_payment_active_for(profile_id: int) -> tuple:
return False, False
def start_new_payment_cycle(category: PaymentCategory, profile_id: Optional[int] = None) -> Result:
def error_about_mismatch(category: PaymentCategory) -> Result:
"""Display an error for the billing process already running, with a choice to end it"""
if category == PaymentCategory.INDIVIDUAL:
error_msg = "You want an individual process, but there's already a ticketing billing process active right now, do you want to stop that?"
else:
error_msg = "You want a ticketing process, but there's already an individual billing process active right now, do you want to stop that?"
logger.error(error_msg)
return Result(valid=False, error_type=ResultError.ACTIVE_BILLING, message=error_msg)
def evaluate_cycle(payment_cycle: OverallPaymentCycle, profile_id: int) -> Result:
if the_same_payment_cycle(payment_cycle=payment_cycle, profile_id=profile_id):
logger.info("It's the same as before, returning the data")
return Result(valid=True, data=payment_cycle.billing_data)
else:
error_msg = f"You activated profile {profile_id}, but profile {payment_cycle.billing_data.instance_id} has active billing, stop that?"
logger.error(error_msg)
return Result(valid=False, error_type=ResultError.ACTIVE_BILLING, message=error_msg)
def start_new_payment_cycle(category: PaymentCategory, profile_id: Optional[int] = None) -> Result:
"""
Purpose:
Starts the overall payment cycle, and the individual category's billing data.
On Conflict:
If there's already a cycle running, this function addresses that,
by returning the cycle if it's the same, or offering to end it if different.
"""
# PRE-REQS:
if not profile_id and category == PaymentCategory.INDIVIDUAL:
error_msg = "Need a profile id for an individual profile."
logger.error(error_msg)
return Result(valid=False, error_type=ResultError.INVALID_INPUT, message=error_msg)
logger.info("Got pre-reqs for individual.")
# Is there currently a core process?
# This is the singleton:
payment_cycle = OverallPaymentCycle()
logger.info(f"payment_cycle is {payment_cycle}")
if payment_cycle.active:
if the_same_payment_cycle(payment_cycle=payment_cycle, profile_id=profile_id):
logger.info("It's the same as before, returning the code")
return Result(valid=True, data=payment_cycle.billing_code)
else:
error_msg = f"There's already a billing process active, do you want to stop that?"
logger.error(error_msg)
# ----------------------------
# MISMATCH IN CATEGORY FROM WHAT EXISTS:
# ----------------------------
if category != payment_cycle.category:
return error_about_mismatch(category)
logger.info("Made it past the mismatch check.")
# ----------------------------
# ALREADY ACTIVE INDIVIDUAL CYCLE:
# ----------------------------
if all([ # all are true:
payment_cycle.billing_data,
payment_cycle.active,
payment_cycle.category == PaymentCategory.INDIVIDUAL,
category == PaymentCategory.INDIVIDUAL
]):
profile_id_already_running = payment_cycle.billing_data.instance_id
if profile_id_already_running:
logger.info(f"Triggering the evaluate_cycle reply because of {profile_id_already_running} running already")
return evaluate_cycle(payment_cycle=payment_cycle, profile_id=profile_id)
# ----------------------------
# ALREADY POLLING TIMER:
# ----------------------------
try:
timer_loop = CheckPaidTimer.get_instance()
if timer_loop.is_running:
error_msg = "There's already a billing cycle that's actively checking if paid. Do you want to stop that?"
return Result(valid=False, error_type=ResultError.ACTIVE_BILLING, message=error_msg)
except Exception as e:
logger.info("All good, the timer isn't running, and we were afraid it was.")
logger.info("Process is NOT yet active, starting..")
payment_cycle.begin()
payment_cycle.category = category
# ----------------------------
# START INDIVIDUAL:
# ----------------------------
if category == PaymentCategory.INDIVIDUAL:
payment_cycle.profile_id = profile_id
payment_cycle.active_data = PaymentData(instance_id=profile_id)
payment_cycle.billing_data = PaymentData(instance_id=profile_id)
subscription = get_profile_subscription(profile_id=profile_id, save_pending=True) # save to disk that it's pending
if subscription:
billing_code = subscription.billing_code
else:
billing_code = None
logger.info("Individual profile initial setup complete.")
# ----------------------------
# START TICKETS:
# ----------------------------
elif category == PaymentCategory.TICKET:
billing_data = TicketPaymentData()
payment_cycle.active_data = billing_data # saves
billing_code = billing_data.temp_billing_code
payment_cycle.billing_data = billing_data # saves
billing_code = billing_data.billing_code # might be empty if new
logger.info("Ticketing initial setup complete.")
else:
error_msg = f"Invalid PaymentCategory selected of {str(category)}"
logger.error(error_msg)
return Result(valid=False, error_type=ResultError.INVALID_INPUT, message=error_msg)
# save in cache:
# ----------------------------
# COMMON FOR BOTH CATEGORIES:
# ----------------------------
payment_cycle.billing_code = billing_code
payment_cycle.active = True
return Result(valid=True, data=billing_code)
logger.info(f"Returning the entire payment_cycle")
return Result(valid=True, data=payment_cycle)
# else:
# return Result(valid=False, error_type=ResultError.UNKNOWN, message="We failed to create new billing cycle data.")
def create_new_invoice(connection_observer: ConnectionObserver) -> Result:
@ -187,16 +256,16 @@ def return_pre_existing_invoice(billing_code: str, connection_observer: Connecti
def create_ticket_invoice(payment_cycle: OverallPaymentCycle, connection_observer: ConnectionObserver) -> Result:
logger.info("Creating a new TICKET invoice..")
billing_data = payment_cycle.active_data
billing_data = payment_cycle.billing_data
if not billing_data:
error_msg = "You are currently missing billing data to begin an invoice"
return Result(valid=False, error_type=ResultError.INVALID_INPUT, message=error_msg)
# already has billing id:
billing_code = billing_data.temp_billing_code
billing_code = billing_data.billing_code
if billing_code:
logger.info("We already have a temp_billing_code id for this ticket prep.")
logger.info("We already have a billing_code id for this ticket prep.")
return return_pre_existing_invoice(billing_code=billing_code, connection_observer=connection_observer)
#-----------------------------
@ -229,7 +298,7 @@ def create_ticket_invoice(payment_cycle: OverallPaymentCycle, connection_observe
#-----------------------------
billing_code = final_invoice.billing_code
billing_data.temp_billing_code = billing_code
billing_data.billing_code = billing_code
billing_data.save()
payment_cycle.got_invoice = True
@ -260,7 +329,7 @@ def create_individual_invoice(payment_cycle: OverallPaymentCycle, connection_obs
# MAKE PAYLOAD. PIECE BY PIECE
#-----------------------------
# get the payment data singleton:
billing_data = payment_cycle.active_data
billing_data = payment_cycle.billing_data
# make sure the protocol is saved in the singleton already,
if not billing_data.protocol_choice:
@ -322,51 +391,51 @@ def create_individual_invoice(payment_cycle: OverallPaymentCycle, connection_obs
def check_paid(billing_code: str, connection_observer: ConnectionObserver) -> Result:
"""
Purpose:
Evaluates Payment Status for EITHER payment category
# def check_paid(billing_code: str, connection_observer: ConnectionObserver) -> Result:
# """
# Purpose:
# Evaluates Payment Status for EITHER payment category
Requires:
Valid OverallPaymentCycle
billing_code is provided by the timer for BOTH types.
"""
# Requires:
# Valid OverallPaymentCycle
# billing_code is provided by the timer for BOTH types.
# """
payment_cycle = OverallPaymentCycle()
category = payment_cycle.category
# payment_cycle = OverallPaymentCycle()
# category = payment_cycle.category
logger.info(f"Checking paid with category of {category}")
# logger.info(f"Checking paid with category of {category}")
# Get endpoint:
if category == PaymentCategory.INDIVIDUAL:
url = f'{Constants.SP_BASE_URL}/v2/check_paid'
elif category == PaymentCategory.TICKET:
url = f'{Constants.TICKET_API_BASE_URL}/check_ticket_paid'
else:
return Result(valid=False, error_type=ResultError.INVALID_INPUT, message="This is not a supported payment category to check")
# # Get endpoint:
# if category == PaymentCategory.INDIVIDUAL:
# url = f'{Constants.SP_BASE_URL}/v2/check_paid'
# elif category == PaymentCategory.TICKET:
# url = f'{Constants.TICKET_API_BASE_URL}/check_ticket_paid'
# else:
# return Result(valid=False, error_type=ResultError.INVALID_INPUT, message="This is not a supported payment category to check")
submit_data = {
"billing_code": billing_code
}
# submit_data = {
# "billing_code": billing_code
# }
result = api_call_and_extraction(
desired_type="payment_status",
method=ApiCall.POST,
url=url,
connection_observer=connection_observer,
payload=submit_data
)
# result = api_call_and_extraction(
# desired_type="payment_status",
# method=ApiCall.POST,
# url=url,
# connection_observer=connection_observer,
# payload=submit_data
# )
if not result.valid:
return result
# if not result.valid:
# return result
payment_status = result.data
# payment_status = result.data
# paid
if payment_status:
return Result(valid=True, message="paid")
else:
return Result(valid=False, error_type=ResultError.UNPAID, message="The API call went through, but this subscription is not yet paid.")
# # paid
# if payment_status:
# return Result(valid=True, message="paid")
# else:
# return Result(valid=False, error_type=ResultError.UNPAID, message="The API call went through, but this subscription is not yet paid.")
def paying_for_tickets() -> bool:
@ -404,7 +473,7 @@ def get_current_billing_data(category: Optional[PaymentCategory] = None) -> Resu
# if not payment_cycle and category:
billing_data = payment_cycle.active_data
billing_data = payment_cycle.billing_data
if not billing_data:
error_msg = "There is no current billing data."
@ -416,9 +485,21 @@ def get_current_billing_data(category: Optional[PaymentCategory] = None) -> Resu
def wipe_billing_cycle() -> Result:
"""Wipes the entire overall cycle"""
logger.info("Wiping the overall cycle, so this cycle no longer blocks other new ones")
# Timer:
try:
timer_loop = CheckPaidTimer.get_instance()
if timer_loop.is_running:
timer_loop.stop_polling()
except Exception as e:
logger.info("Skipping Timer since it's not yet created.")
# Overall State:
payment_cycle = OverallPaymentCycle()
if payment_cycle:
payment_cycle.wipe()
# This has to return a Result object, even if not checked, because of it's use in plumbing,
return Result(valid=True)
@ -462,59 +543,59 @@ def wipe_subscription(profile_id: int) -> Result:
return Result(valid=True)
def mark_as_paid() -> Result:
"""Conclude the payment phase"""
# def mark_as_paid() -> Result:
# """Conclude the payment phase"""
logger.info("Marking as paid..")
# logger.info("Marking as paid..")
payment_cycle = OverallPaymentCycle()
# payment_cycle = OverallPaymentCycle()
if not payment_cycle:
error_msg = "There is no current billing cycle."
return Result(valid=False, error_type=ResultError.INVALID_INPUT, message=error_msg)
# if not payment_cycle:
# error_msg = "There is no current billing cycle."
# return Result(valid=False, error_type=ResultError.INVALID_INPUT, message=error_msg)
billing_data = payment_cycle.instance
# billing_data = payment_cycle.instance
if not billing_data:
error_msg = "There is no current billing data."
return Result(valid=False, error_type=ResultError.INVALID_INPUT, message=error_msg)
# if not billing_data:
# error_msg = "There is no current billing data."
# return Result(valid=False, error_type=ResultError.INVALID_INPUT, message=error_msg)
category = payment_cycle.category
# category = payment_cycle.category
if category == PaymentCategory.INDIVIDUAL:
profile_id = billing_data.instance_id
result = mark_profile_as_paid(profile_id)
if not result.valid:
return result
# if category == PaymentCategory.INDIVIDUAL:
# profile_id = billing_data.instance_id
# result = mark_profile_as_paid(profile_id)
# if not result.valid:
# return result
elif category == PaymentCategory.TICKET:
# saving global ticket data:
billing_data.billing_pending = False
billing_data.paid = True
billing_data.save()
# elif category == PaymentCategory.TICKET:
# # saving global ticket data:
# billing_data.billing_pending = False
# billing_data.paid = True
# billing_data.save()
else:
return not_supported()
# else:
# return not_supported()
# finally:
payment_cycle.wipe()
return Result(valid=True)
# # finally:
# payment_cycle.wipe()
# return Result(valid=True)
def mark_profile_as_paid(profile_id: int):
# def mark_profile_as_paid(profile_id: int):
logger.info(f"Marking profile_id {str(profile_id)} as paid..")
# logger.info(f"Marking profile_id {str(profile_id)} as paid..")
# Get real profile object:
profile = ProfileController.get(profile_id)
if not profile:
error_msg = f"Invalid profile id of {profile_id}"
logger.error(error_msg)
return Result(valid=False, message=error_msg, error_type=ResultError.INVALID_INPUT)
# # Get real profile object:
# profile = ProfileController.get(profile_id)
# if not profile:
# error_msg = f"Invalid profile id of {profile_id}"
# logger.error(error_msg)
# return Result(valid=False, message=error_msg, error_type=ResultError.INVALID_INPUT)
profile.subscription.billing_pending = False
ProfileController.update(profile)
return Result(valid=True)
# profile.subscription.billing_pending = False
# ProfileController.update(profile)
# return Result(valid=True)
def save_billing_code(profile_id: int, billing_code: str, expires_at: datetime) -> Result: