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:
parent
e296e212db
commit
1f4b37dbe9
12 changed files with 408 additions and 122 deletions
|
|
@ -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
|
||||
|
||||
|
|
|
|||
10
core/errors/error_shortcuts.py
Normal file
10
core/errors/error_shortcuts.py
Normal 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)
|
||||
|
||||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
114
core/services/payment_phase/payment_update_tools.py
Normal file
114
core/services/payment_phase/payment_update_tools.py
Normal 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.")
|
||||
|
||||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
Loading…
Reference in a new issue