diff --git a/core/models/Result.py b/core/models/Result.py index 92ef491..e4965a0 100644 --- a/core/models/Result.py +++ b/core/models/Result.py @@ -41,6 +41,7 @@ class ResultError(Enum): SERVER_SIDE = "server_side" UNPAID = "unpaid" MTU = "mtu" + ACTIVE_BILLING = "active_billing" NAVIGATE_TO = "navigate_to" UNKNOWN = "unknown" diff --git a/core/models/manage/server_reply_tools.py b/core/models/manage/server_reply_tools.py index bc37e9b..bb310e7 100644 --- a/core/models/manage/server_reply_tools.py +++ b/core/models/manage/server_reply_tools.py @@ -2,7 +2,7 @@ from core.Errors import InvalidSubscriptionError from core.models.manage.validate_dataclass import validate_model from core.models.pydantic_models.ServerReply import ServerReply, ServerError from core.models.Result import Result, ResultError -from core.services.networking.api_requests.ApiResponseModel import ApiResponse +from core.services.networking.api_requests.ApiResponseModel import ApiResponse, ErrorType from core.observers.ConnectionObserver import ConnectionObserver from core.services.networking.httpx import connect from core.errors.logger import logger @@ -121,7 +121,8 @@ def api_call_and_extraction( if not api_response.valid: # legacy system - if api_result.error_type == ErrorType.AUTHENTICATION_ERROR: + if api_response.error_type == ErrorType.AUTHENTICATION_ERROR: + logger.error(f"api_response is {api_response}") logger.error(f"Server is giving an Authentication error for billing ID: {billing_code}") raise InvalidSubscriptionError(f"Invalid Subscription for {billing_code}") diff --git a/core/services/payment_phase/check_payment_loop.py b/core/services/payment_phase/check_payment_loop.py index 9b39e41..1b5dd6c 100644 --- a/core/services/payment_phase/check_payment_loop.py +++ b/core/services/payment_phase/check_payment_loop.py @@ -1,6 +1,5 @@ from core.infrastructure.thread_dispatch import MainThreadDispatcher -from core.services.subscriptions.subscriptions_by_protocol import check_paid, mark_as_paid, mark_global_tickets_paid - +from core.services.subscriptions.subscriptions_by_protocol import check_paid, mark_as_paid from core.controllers.tickets.TicketPayController import check_if_ticket_paid from core.observers.ConnectionObserver import ConnectionObserver @@ -91,12 +90,15 @@ class CheckPaidTimer: # in the background thread: print("checking if paid..") - if self.payment_type == PaymentType.CRYPTO: - result = check_paid(self.billing_code, self.connection_observer) - elif self.payment_type == PaymentType.TICKET: - result = check_if_ticket_paid(self.billing_code, self.connection_observer) - else: - result = Result(valid=False, error_type=ResultError.INVALID_INPUT, message="Invalid payment type chosen to check.") + result = check_paid(self.billing_code, self.connection_observer) + + + # if self.payment_type == PaymentType.CRYPTO: + # result = check_paid(self.billing_code, self.connection_observer) + # elif self.payment_type == PaymentType.TICKET: + # result = check_if_ticket_paid(self.billing_code, self.connection_observer) + # else: + # result = Result(valid=False, error_type=ResultError.INVALID_INPUT, message="Invalid payment type chosen to check.") print("checked") # Bring result to main thread: @@ -132,7 +134,7 @@ class CheckPaidTimer: message = "Paid! This invoice is paid" # UPDATE UI: - self.view_callback(paid=True, stopped=True, message=message) + self.view_callback(paid=True, stopped=True, message=message, billing_code=self.billing_code) # not yet paid: elif result.error_type == ResultError.UNPAID: diff --git a/core/services/payment_phase/payment_state.py b/core/services/payment_phase/payment_state.py index bab9926..3caadcc 100644 --- a/core/services/payment_phase/payment_state.py +++ b/core/services/payment_phase/payment_state.py @@ -153,6 +153,7 @@ class TicketPaymentData(StatePersistenceMixin): use_random: bool = False temp_billing_code: Optional[str] = None billing_pending: bool = True + paid: bool = False def __post_init__(self): # Global mode, assuming tickets here: @@ -196,23 +197,28 @@ class PaymentData(StatePersistenceMixin): @singleton @dataclass -class OverallPaymentCycle(StatePersistenceMixin): - """For ALL payment states, this just keeps track of which one.""" - PAYLOAD_FIELDS = {} - - # metadata on it's own GLOBAL instance: - parent_folder = None - folder_path: str = None - _data_file: str = None +class OverallPaymentCycle(): + """For ALL payment states, this just keeps track of which one is active.""" category: PaymentCategory = PaymentCategory.INDIVIDUAL already_polling: bool = False profile_id: int = None - instance: Any = None + active: bool = False + active_data: Any = None + got_invoice: bool = False + billing_code: str = None + + def begin(self): + if not self.active: + self.active = True def wipe(self): """Wipe all data and start over.""" + self.active = False + self.active_data = None self.category = PaymentCategory.INDIVIDUAL self.already_polling = False self.profile_id = None - self.instance = None \ No newline at end of file + self.got_invoice = None + self.billing_code = None + diff --git a/core/services/subscriptions/subscriptions_by_protocol.py b/core/services/subscriptions/subscriptions_by_protocol.py index af0623e..4d4a0f0 100644 --- a/core/services/subscriptions/subscriptions_by_protocol.py +++ b/core/services/subscriptions/subscriptions_by_protocol.py @@ -1,209 +1,57 @@ +from core.models.manage.server_reply_tools import api_call_and_extraction, ApiCall from core.models.BaseProfile import BaseProfile as Profile from core.services.payment_phase.payment_state import OverallPaymentCycle, PaymentData, TicketPaymentData -from core.services.networking.httpx import connect from core.models.enum_models import ProtocolChoice, PaymentCategory -from core.models.Result import Result, ResultError -from core.models.manage.server_reply_tools import translate_server_failure, invalid_server_format -from core.errors.logger import logger from core.observers.ConnectionObserver import ConnectionObserver from core.models.pydantic_models.type_checker import validate_model -from core.models.pydantic_models.ServerReply import ServerReply, SubscriptionInvoice +from core.models.pydantic_models.ServerReply import SubscriptionInvoice from core.models.Subscription import Subscription from core.Constants import Constants +from core.models.Result import Result, ResultError +from core.errors.logger import logger +# generic from datetime import datetime from typing import Optional -HOW_MANY_PROFILES_DEFAULT = 6 +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 paying_for_tickets() -> bool: - """Returns boolean for if the current billing is tickets""" - - payment_cycle = OverallPaymentCycle() - - if not payment_cycle: - return False - - category = payment_cycle.category - - if category == PaymentCategory.TICKET: - return True - else: - return False +def invalid_server_format() -> Result: + error_msg = "The server gave an invalid response format." + return Result(valid=False, error_type=ResultError.INVALID_API_REPLY, message=error_msg) -def get_current_billing_data() -> Result: +def _api_call_for_invoice(url: str, payload: dict, connection_observer: ConnectionObserver) -> Result: """ - Goal: - Get whatever current billing data is relevant by the overall cycle, for either billing category + Cross-Category Helper function. - Note: - The reason it's broken into a cycle and the actual data, is to keep track of if it's individual or tickets. - And to make sure that only one set of data is polling at once. + 1) Does API call + 2) Filters out bad server replies + 3) Validates the SubscriptionInvoice object + 4) Returns SubscriptionInvoice wrapped in a Result object. """ - # Is there currently a process? - payment_cycle = OverallPaymentCycle() - if not payment_cycle: - error_msg = "There is no current billing cycle." - return Result(valid=False, error_type=ResultError.INVALID, message=error_msg) + logger.info(f"Doing the API call with this payload: {payload}") - 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, message=error_msg) - - return Result(valid=True, data=billing_data) - - -def wipe_billing_cycle(): - """Wipes the entire overall cycle""" - logger.info("Wiping the overall cycle, so this cycle no longer blocks other new ones") - payment_cycle = OverallPaymentCycle() - if payment_cycle: - payment_cycle.wipe() - - -def start_billing_state(category: PaymentCategory, profile_id: Optional[int] = None, wipe_existing: Optional[bool] = False) -> Result: - """Start a new billing process. Wipe the existing one. And never save these 'overall' states to disk.""" - - if not category: - error_msg = "Must pick a payment category to start a billing cycle." - return Result(valid=False, error_type=ResultError.INVALID, message=error_msg) - - if category == PaymentCategory.INDIVIDUAL and not profile_id: - error_msg = "Starting an Individual Profile Billing requires a profile ID." - return Result(valid=False, error_type=ResultError.INVALID, message=error_msg) - - # Is there currently a process? - payment_cycle = OverallPaymentCycle() - - # WIPE IF REQUESTED: - if wipe_existing and payment_cycle: - payment_cycle.wipe() - - if payment_cycle and payment_cycle.already_polling: - error_msg = "Please stop the payment checking already going on before starting another." - return Result(valid=False, error_type=ResultError.INVALID, message=error_msg) - - if payment_cycle: - logger.info("Wiping the current payment state already saved.") - - payment_cycle.category = category - - if category == PaymentCategory.INDIVIDUAL: - payment_cycle.profile_id = profile_id - billing_data = PaymentData(instance_id=profile_id) - payment_cycle.instance = billing_data - return Result(valid=True, data=billing_data) - - elif category == PaymentCategory.TICKET: - 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) - - else: - error_msg = "This is a currently unsupported payment category." - return Result(valid=False, error_type=ResultError.INVALID, message=error_msg) - - -def check_paid(billing_code: str, connection_observer: ConnectionObserver) -> Result: - """Evaluates Payment Status from the Server""" - submit_data = { - "billing_code": billing_code - } - url = f'{Constants.SP_BASE_URL}/v2/check_paid' - - api_response = connect.single_endpoint( - method="post", + result = api_call_and_extraction( + desired_type="data", + method=ApiCall.POST, url=url, - observer=connection_observer, - payload=submit_data, + connection_observer=connection_observer, + payload=payload ) - # Layer 1: API failure: - if not api_response.valid: - error_msg = f"API Error: {api_response.error_type} {api_response.message}" - logger.error(error_msg) - return Result(valid=False, error_type=ResultError.CONNECTION, message=error_msg) - - # Layer 2: Server's Reply: - raw_reply = api_response.data - server_reply = validate_model(ServerReply, raw_reply) - - if not server_reply: - logger.error(f"Invalid structure for ServerReply, which had a raw reply of {raw_reply}") - return invalid_server_format() - - # paid - if server_reply.payment_status: - return Result(valid=True, message="paid") - - # NOT paid (but valid server reply), - if server_reply.valid and not server_reply.payment_status: - return Result(valid=False, error_type=ResultError.UNPAID, message="The API call went through, but this subscription is not yet paid.") - - # invalid server reply, - return translate_server_failure(server_reply) - - -def return_pre_existing_invoice(billing_code: str, connection_observer: ConnectionObserver) -> Result: - """This billing_code already got an invoice, but needs to fetch that data from the server again.""" - - payload = { - "billing_code": billing_code - } - - #----------------------------- - # SEND TO API - #----------------------------- - url = f'{Constants.SP_BASE_URL}/v2/get_invoice' - - api_response = connect.single_endpoint( - method="post", - url=url, - observer=connection_observer, - payload=payload, - ) - - #----------------------------- - # EVALUATE CONNECTION FAILURE - #----------------------------- - # Layer 1: API failure: - if not api_response.valid: - error_msg = f"Connection/500s Error prevented the server from replying: {api_response.error_type} {api_response.message}" - logger.error(error_msg) - return Result(valid=False, error_type=ResultError.CONNECTION, message=error_msg) - - #----------------------------- - # EVALUATE SERVER-SIDE ISSUES (assuming it replied) - #----------------------------- - # Layer 2: Validity of Server's Reply: - raw_reply = api_response.data - logger.info("Validating the Validity of the Server's Reply..") - server_reply = validate_model(ServerReply, raw_reply) - - if not server_reply: - logger.error(f"Invalid Structure for ServerReply! The raw data is {raw_reply}") - return invalid_server_format() - - if not server_reply.valid: - logger.error(f"Server reply was correctly structured, but the actual invoice failed to be created. The raw data is {raw_reply}") - # translate by enum error code: - return translate_server_failure(server_reply) + if not result.valid: + return result #----------------------------- # EVALUATE SERVER'S ACTUAL DATA #----------------------------- - # Layer 3: Payload Data - payload_data = server_reply.data + payload_data = result.data logger.info("Validating the final invoice") final_invoice = validate_model(SubscriptionInvoice, payload_data) @@ -214,9 +62,169 @@ def return_pre_existing_invoice(billing_code: str, connection_observer: Connecti return Result(valid=True, data=final_invoice) -def create_individual_invoice(profile_id: int, connection_observer: ConnectionObserver) -> Result: +def the_same_payment_cycle(payment_cycle: OverallPaymentCycle, profile_id: Optional[int] = None) -> bool: + """Evaluate if its the same cycle.""" + + if profile_id is None: + return False + + if profile_id == payment_cycle.profile_id: + return True + else: + return False + + +def start_new_payment_cycle(category: PaymentCategory, profile_id: Optional[int] = None) -> Result: + + 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) + + # Is there currently a core process? + payment_cycle = OverallPaymentCycle() + + 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 = "There's already a billing process active, do you want to stop that?" + logger.error(error_msg) + return Result(valid=False, error_type=ResultError.ACTIVE_BILLING, message=error_msg) + + logger.info("Process is NOT yet active, starting..") + payment_cycle.begin() + payment_cycle.category = category + + if category == PaymentCategory.INDIVIDUAL: + payment_cycle.profile_id = profile_id + payment_cycle.active_data = PaymentData(instance_id=profile_id) + subscription = get_profile_subscription(profile_id) + if subscription: + billing_code = subscription.billing_code + else: + billing_code = None + + elif category == PaymentCategory.TICKET: + billing_data = TicketPaymentData() + payment_cycle.active_data = billing_data # saves + billing_code = billing_data.temp_billing_code + + 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: + payment_cycle.billing_code = billing_code + payment_cycle.active = True + + return Result(valid=True, data=billing_code) + + +def create_new_invoice(connection_observer: ConnectionObserver) -> Result: + """Requires an active OverallPaymentCycle, that does NOT have an invoice already.""" + + payment_cycle = OverallPaymentCycle() + + if not payment_cycle.active: + error_msg = "Process is not yet active" + logger.error(error_msg) + return Result(valid=False, error_type=ResultError.INVALID_INPUT, message=error_msg) + + if payment_cycle.got_invoice: + error_msg = "Process already got an invoice" + logger.error(error_msg) + return Result(valid=False, error_type=ResultError.INVALID_INPUT, message=error_msg) + + category = payment_cycle.category + + if category == PaymentCategory.INDIVIDUAL: + return create_individual_invoice(payment_cycle=payment_cycle, connection_observer=connection_observer) + + if category == PaymentCategory.TICKET: + return create_ticket_invoice(payment_cycle=payment_cycle, connection_observer=connection_observer) + + else: + return not_supported() + + +def return_pre_existing_invoice(billing_code: str, connection_observer: ConnectionObserver) -> Result: + """This billing_code already got an invoice, but needs to fetch that data from the server again.""" + + logger.info("Fetching a pre-existing invoice..") + payload = { + "billing_code": billing_code + } + + #----------------------------- + # SEND TO API + #----------------------------- + url = f'{Constants.SP_BASE_URL}/v2/get_invoice' + + result = _api_call_for_invoice(url=url, payload=payload, connection_observer=connection_observer) + return result + + +def create_ticket_invoice(payment_cycle: OverallPaymentCycle, connection_observer: ConnectionObserver) -> Result: + + logger.info("Creating a new TICKET invoice..") + billing_data = payment_cycle.active_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 + if billing_code: + logger.info("We already have a temp_billing_code id for this ticket prep.") + return return_pre_existing_invoice(billing_code=billing_code, connection_observer=connection_observer) + + #----------------------------- + # MAKE PAYLOAD. + #----------------------------- + # how_many_profiles = billing_data.how_many_profiles # default is always there + # which_key = billing_data.which_key + # currency = billing_data.currency + + payload = billing_data.prep_payload() + + if not payload: + error_msg = "Unable to save the payload, or missing fields. Check the error log." + logger.error(error_msg) + return Result(valid=False, message=error_msg, error_type=ResultError.FILE_SYSTEM) + + #----------------------------- + # SEND TO API + #----------------------------- + url = f'{Constants.TICKET_API_BASE_URL}/start_payment' + + result = _api_call_for_invoice(url=url, payload=payload, connection_observer=connection_observer) + if not result.valid: + return result + + final_invoice = result.data + + #----------------------------- + # SAVE DATA + #----------------------------- + billing_code = final_invoice.billing_code + + billing_data.temp_billing_code = billing_code + billing_data.save() + + payment_cycle.got_invoice = True + return Result(valid=True, data=final_invoice) + + +def create_individual_invoice(payment_cycle: OverallPaymentCycle, connection_observer: ConnectionObserver) -> Result: """Prepare the payment data payload and send it to the API. Returns an invoice nested inside Result's data""" + profile_id = payment_cycle.profile_id + logger.info(f"creating an individual invoice for profile {profile_id}") + # Get profile object: profile = Profile.find_by_id(profile_id) if not profile: @@ -235,7 +243,7 @@ def create_individual_invoice(profile_id: int, connection_observer: ConnectionOb # MAKE PAYLOAD. PIECE BY PIECE #----------------------------- # get the payment data singleton: - billing_data = PaymentData(instance_id=profile_id) + billing_data = payment_cycle.active_data # make sure the protocol is saved in the singleton already, if not billing_data.protocol_choice: @@ -270,52 +278,128 @@ def create_individual_invoice(profile_id: int, connection_observer: ConnectionOb #----------------------------- url = f'{Constants.SP_BASE_URL}/v2/start_billing' - api_response = connect.single_endpoint( - method="post", + result = _api_call_for_invoice(url=url, payload=payload, connection_observer=connection_observer) + if not result.valid: + return result + + final_invoice = result.data + + #----------------------------- + # SAVE DATA + #----------------------------- + billing_code = final_invoice.billing_code + expires_at = final_invoice.expires_at + + # overall payment cycle: + payment_cycle.billing_code = billing_code + + # individual profile: + profile.subscription = Subscription( + billing_code=billing_code, + expires_at=expires_at, + billing_pending=True + ) + profile.save() + payment_cycle.got_invoice = True + return Result(valid=True, data=final_invoice) + + + +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, - observer=connection_observer, - payload=payload, + connection_observer=connection_observer, + payload=submit_data ) - #----------------------------- - # EVALUATE CONNECTION FAILURE - #----------------------------- - # Layer 1: API failure: - if not api_response.valid: - error_msg = f"Connection/500s Error prevented the server from replying: {api_response.error_type} {api_response.message}" - logger.error(error_msg) - return Result(valid=False, error_type=ResultError.CONNECTION, message=error_msg) + if not result.valid: + return result - #----------------------------- - # EVALUATE SERVER-SIDE ISSUES (assuming it replied) - #----------------------------- - # Layer 2: Validity of Server's Reply: - raw_reply = api_response.data - logger.info("Validating the Validity of the Server's Reply..") - server_reply = validate_model(ServerReply, raw_reply) + payment_status = result.payment_status + + # 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.") - if not server_reply: - logger.error(f"Invalid Structure for ServerReply! The raw data is {raw_reply}") - return invalid_server_format() - if not server_reply.valid: - logger.error(f"Server reply was correctly structured, but the actual invoice failed to be created. The raw data is {raw_reply}") - # translate by enum error code: - return translate_server_failure(server_reply) +def paying_for_tickets() -> bool: + """Returns boolean for if the current billing is tickets""" - #----------------------------- - # EVALUATE SERVER'S ACTUAL DATA - #----------------------------- - # Layer 3: Payload Data - payload_data = server_reply.data - logger.info("Validating the final invoice") - final_invoice = validate_model(SubscriptionInvoice, payload_data) + payment_cycle = OverallPaymentCycle() - if not final_invoice: - logger.error(f"Failed to create an invoice object with this data: {payload_data}") - return invalid_server_format() + if not payment_cycle: + return False + + category = payment_cycle.category + + if category == PaymentCategory.TICKET: + return True + else: + return False + + +def get_current_billing_data() -> Result: + """ + Goal: + Get whatever current billing data is relevant by the overall cycle, for either billing category + + Note: + The reason it's broken into a cycle and the actual data, is to keep track of if it's individual or tickets. + And to make sure that only one set of data is polling at once. + """ + # Is there currently a process? + payment_cycle = OverallPaymentCycle() + + if not payment_cycle: + error_msg = "There is no current billing cycle." + return Result(valid=False, error_type=ResultError.INVALID, message=error_msg) + + billing_data = payment_cycle.active_data + + if not billing_data: + error_msg = "There is no current billing data." + return Result(valid=False, error_type=ResultError.INVALID, message=error_msg) + + return Result(valid=True, data=billing_data) + + +def wipe_billing_cycle(): + """Wipes the entire overall cycle""" + logger.info("Wiping the overall cycle, so this cycle no longer blocks other new ones") + payment_cycle = OverallPaymentCycle() + if payment_cycle: + payment_cycle.wipe() - return Result(valid=True, data=final_invoice) def get_profile_subscription(profile_id: int) -> Subscription: @@ -331,23 +415,50 @@ 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 an individual subscription as paid""" +def mark_as_paid() -> Result: + """Conclude the payment phase""" logger.info("Marking as paid..") - # Get profile object: + payment_cycle = OverallPaymentCycle() + + if not payment_cycle: + error_msg = "There is no current billing cycle." + return Result(valid=False, error_type=ResultError.INVALID, 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, 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 mark_profile_as_paid(profile_id: int): + + logger.info(f"Marking profile_id {str(profile_id)} as paid..") + + # Get real profile object: profile = Profile.find_by_id(profile_id) if not profile: error_msg = f"Invalid profile id of {profile_id}" @@ -433,65 +544,3 @@ def return_pre_existing_payment_data(profile_id: int, subscription: Subscription logger.error(error_msg) return Result(valid=False, message=error_msg, error_type=ResultError.INVALID_INPUT) - -# # Translate Server-side to client-side errors in THIS CONTEXT, -# TRANSLATION_DICT = { -# ServerError.WRONG_LOCATION: ResultError.WRONG_LOCATION, -# ServerError.NODE_ERROR: ResultError.WRONG_LOCATION, -# ServerError.BTCPAY: ResultError.SERVER_SIDE, -# ServerError.INVALID: ResultError.INVALID_INPUT, -# ServerError.MISSING_DATA: ResultError.INVALID_INPUT, -# ServerError.UNPAID: ResultError.UNPAID, -# ServerError.EXPIRED: ResultError.EXPIRED -# } - -# def translate_server_failure(server_reply: ServerReply) -> Result: -# """Translate the server's failure to the UI""" -# logger.error(f"Server-side failure occured. The server claims the reason is of type {server_reply.error_type} with {server_reply.message}") -# client_error = TRANSLATION_DICT[server_reply.error_type] -# return Result(valid=False, error_type=client_error, message=server_reply.message) - - -# def invalid_server_format() -> Result: -# error_msg = "The server gave an invalid response format." -# return Result(valid=False, error_type=ResultError.INVALID_API_REPLY, message=error_msg) - - - -# def create_invoice( -# subscription_plan_id: int, -# location_id: int, -# protocol_choice: ProtocolChoice, -# currency: Currency, -# connection_observer: ConnectionObserver -# ) -> Result: -# submit_data = { -# "subscription_plan_id": subscription_plan_id, -# "location_id": location_id, -# "protocol_choice": protocol_choice.value, -# "currency": currency.value, -# "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.")