Resume Payments for individual profiles stable. Also fixed a bug with errors in filtering invalid replies to getting new wireguard conifgs

This commit is contained in:
SimplifiedPrivacy 2026-10-07 18:33:16 -04:00
parent 52b3e1ba94
commit 430696b480
5 changed files with 96 additions and 50 deletions

View file

@ -14,10 +14,12 @@ TRANSLATION_DICT = {
ServerError.INVALID: ResultError.INVALID_INPUT, ServerError.INVALID: ResultError.INVALID_INPUT,
ServerError.MISSING_DATA: ResultError.INVALID_INPUT, ServerError.MISSING_DATA: ResultError.INVALID_INPUT,
ServerError.UNPAID: ResultError.SUBSCRIPTION, ServerError.UNPAID: ResultError.SUBSCRIPTION,
ServerError.SERVER_SIDE: ResultError.SERVER_SIDE ServerError.SERVER_SIDE: ResultError.SERVER_SIDE,
ServerError.EXPIRED: ResultError.SUBSCRIPTION
} }
def format_server_response(api_response: ApiResponse, desired_type: str) -> Result: def format_server_response(api_response: ApiResponse, desired_type: str) -> Result:
print("Starting format_server_response conversion")
"""Turn an API response into a Result Object""" """Turn an API response into a Result Object"""
# if it's in object format, then convert it, # if it's in object format, then convert it,
@ -33,28 +35,35 @@ def format_server_response(api_response: ApiResponse, desired_type: str) -> Resu
logger.error("Can't convert server response.") logger.error("Can't convert server response.")
return invalid_server_format() return invalid_server_format()
conversion_worked, server_reply = validate_model(ServerReply, raw_reply) validated_model, server_reply = validate_model(ServerReply, raw_reply)
if not conversion_worked: if not validated_model:
logger.error(f"Invalid structure for ServerReply. The original raw reply was {raw_reply}") logger.error(f"Invalid structure for ServerReply. The original raw reply was {raw_reply}")
return invalid_server_format(error_msg=server_reply) return invalid_server_format(error_msg=server_reply)
# Sanity check: # Failure:
if not server_reply.valid:
logger.error(f"invalid server reply: {server_reply.error_type}")
logger.error(f"invalid server reply: {server_reply.message}")
final_return = translate_server_failure(server_reply)
print(f"final_return is {final_return}")
return final_return # result object
print("Server claims the result was valid, let's check if it has the data type")
if not hasattr(server_reply, desired_type): if not hasattr(server_reply, desired_type):
error_msg = "Developer Error: You're asking for a data type that isn't in the object!" error_msg = "Developer Error: You're asking for a data type that isn't in the object!"
logger.error(error_msg) logger.error(error_msg)
return Result(valid=False, error_type=ResultError.INVALID_INPUT, message=error_msg) return Result(valid=False, error_type=ResultError.INVALID_INPUT, message=error_msg)
# Success! Extract and Return Data: data = getattr(server_reply, desired_type)
if server_reply.valid: if data:
data = getattr(server_reply, desired_type)
return Result(valid=True, data=data) return Result(valid=True, data=data)
else:
return Result(valid=False, error_type=ResultError.MISSING_DATA, message="Server Replied it was valid, but failed to provide the data.")
# Failure:
logger.error(f"invalid server reply: {server_reply.error_type}") # return Result(valid=False, error_type=error_type, message=server_reply.message)
logger.error(f"invalid server reply: {server_reply.message}")
error_type = translate_server_failure(server_reply.error_type)
return Result(valid=False, error_type=error_type, message=server_reply.message)
def translate_server_failure(server_reply: ServerReply) -> Result: def translate_server_failure(server_reply: ServerReply) -> Result:

View file

@ -60,13 +60,13 @@ def register_wireguard_session(
logger.error(error_msg) logger.error(error_msg)
raise ConnectionError(error_msg) raise ConnectionError(error_msg)
wireguard_configuration = api_result.data # 2nd Layer Translation:
print(f"wireguard_configuration type is {type(wireguard_configuration)}") result = format_server_response(api_response=api_result, desired_type="data")
# print(f"wireguard_configuration value is {wireguard_configuration}") if not result.valid:
# KILLING THE RESULT FOR NOW. Later, replace this error raise with the return of data,
if isinstance(wireguard_configuration, dict): raise InvalidSubscriptionError(result.message)
logger.error("Server isn't returning a WG config, it's trying to tell you a ServerReply message...")
return handle_config_error(wireguard_configuration) wireguard_configuration = result.data
if wireguard_configuration is None: if wireguard_configuration is None:
raise InvalidSubscriptionError() raise InvalidSubscriptionError()
@ -76,14 +76,25 @@ def register_wireguard_session(
return Result(valid=True) return Result(valid=True)
# over-engineering:
def handle_config_error(wireguard_configuration: str|dict) -> Result: # print(f"wireguard_configuration type is {type(wireguard_configuration)}")
"""The server gave the wireguard config in an invalid format. First convert it to get the error.""" # # print(f"wireguard_configuration value is {wireguard_configuration}")
server_reply = format_server_response(wireguard_configuration, "message")
if server_reply.valid: # if isinstance(wireguard_configuration, dict):
return server_reply # print(f"wireguard_configuration is {type(wireguard_configuration)}")
else: # print(f"wireguard_configuration is {wireguard_configuration}")
return Result(valid=False, error_type=ResultError.NODE_ERROR, message="server-side error with wireguard config format.") # logger.error("Server isn't returning a WG config, it's trying to tell you a ServerReply message...")
# return format_server_response(wireguard_configuration, "message")
# def handle_config_error(wireguard_configuration: str|dict) -> Result:
# """The server gave the wireguard config in an invalid format. First convert it to get the error."""
# server_reply = format_server_response(wireguard_configuration, "message")
# if server_reply.valid:
# return server_reply
# else:
# return Result(valid=False, error_type=ResultError.NODE_ERROR, message="server-side error with wireguard config format.")
def _generate_wireguard_keys() -> dict: def _generate_wireguard_keys() -> dict:

View file

@ -194,7 +194,7 @@ def _ensure_wireguard_configured(
logger.info(f"[CONNECTION] Checking if the profile already has a wg config..") logger.info(f"[CONNECTION] Checking if the profile already has a wg config..")
if not profile.has_wireguard_configuration(): if not profile.has_wireguard_configuration():
logger.info(f"[CONNECTION] Profile does NOT have WG keys and it needs it. Registering a NEW wg session..") logger.info(f"[CONNECTION] Profile does NOT have WG keys and it needs it. Registering a NEW wg session..")
register_wireguard_session(profile, connection_observer=connection_observer) registry_result = register_wireguard_session(profile, connection_observer=connection_observer)
def _establish_with_renegotiation( def _establish_with_renegotiation(

View file

@ -1,5 +1,5 @@
from core.infrastructure.thread_dispatch import MainThreadDispatcher from core.infrastructure.thread_dispatch import MainThreadDispatcher
from core.services.subscriptions.subscriptions_by_protocol import check_paid 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.controllers.tickets.TicketPayController import check_if_ticket_paid
@ -24,13 +24,13 @@ MAX_FAILED_CONNECTIONS = 8
class CheckPaidTimer: class CheckPaidTimer:
_instance = None # CLASS VARIABLE - survives across __init__ calls _instance = None # CLASS VARIABLE - survives across __init__ calls
def __new__(cls, view_callback: Callable, billing_code: str, ui_dispatcher: MainThreadDispatcher, payment_type: PaymentType, connection_observer: ConnectionObserver): def __new__(cls, view_callback: Callable, billing_code: str, ui_dispatcher: MainThreadDispatcher, payment_type: PaymentType, connection_observer: ConnectionObserver, profile_id: int):
"""Called before __init__ — controls instance creation""" """Called before __init__ — controls instance creation"""
if cls._instance is None: if cls._instance is None:
cls._instance = super().__new__(cls) cls._instance = super().__new__(cls)
return cls._instance return cls._instance
def __init__(self, view_callback: Callable, billing_code: str, ui_dispatcher: MainThreadDispatcher, payment_type: PaymentType, connection_observer: ConnectionObserver): def __init__(self, view_callback: Callable, billing_code: str, ui_dispatcher: MainThreadDispatcher, payment_type: PaymentType, connection_observer: ConnectionObserver, profile_id: int):
# Guard: makes sure this entire class only runs once # Guard: makes sure this entire class only runs once
if hasattr(self, '_initialized'): if hasattr(self, '_initialized'):
return return
@ -42,6 +42,7 @@ class CheckPaidTimer:
self.view_callback = view_callback self.view_callback = view_callback
self.ui_dispatcher = ui_dispatcher self.ui_dispatcher = ui_dispatcher
self.billing_code = billing_code self.billing_code = billing_code
self.profile_id = profile_id
self.connection_observer = connection_observer self.connection_observer = connection_observer
self.payment_type = payment_type self.payment_type = payment_type
self.failed_connections = 0 self.failed_connections = 0
@ -99,7 +100,15 @@ class CheckPaidTimer:
if result.valid: if result.valid:
print("PAID! Stopping timer") print("PAID! Stopping timer")
self.stop_polling() self.stop_polling()
message = "paid"
# SAVE:
marked = mark_as_paid(profile_id=self.profile_id)
if not marked.valid:
message = marked.message
else:
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)
# not yet paid: # not yet paid:

View file

@ -10,6 +10,7 @@ from core.models.pydantic_models.ServerReply import ServerReply, ServerError, Su
from core.models.Subscription import Subscription from core.models.Subscription import Subscription
from core.Constants import Constants from core.Constants import Constants
from datetime import datetime
def check_paid(billing_code: str, connection_observer: ConnectionObserver) -> Result: def check_paid(billing_code: str, connection_observer: ConnectionObserver) -> Result:
"""Evaluates Payment Status from the Server""" """Evaluates Payment Status from the Server"""
@ -17,7 +18,6 @@ def check_paid(billing_code: str, connection_observer: ConnectionObserver) -> Re
"billing_code": billing_code "billing_code": billing_code
} }
url = f'{Constants.SP_BASE_URL}/v2/check_paid' url = f'{Constants.SP_BASE_URL}/v2/check_paid'
print(f"url is {url}")
api_response = connect.single_endpoint( api_response = connect.single_endpoint(
method="post", method="post",
@ -162,7 +162,24 @@ def get_profile_subscription(profile_id: int) -> Subscription:
return profile.subscription return profile.subscription
def save_billing_code(profile_id: int, billing_code: str, expires_at: str) -> Result: def mark_as_paid(profile_id: int) -> Result:
"""Conclude the payment phase, by marking the sub as paid"""
logger.info("Marking as paid..")
# Get profile object:
profile = Profile.find_by_id(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
profile.save()
return Result(valid=True)
def save_billing_code(profile_id: int, billing_code: str, expires_at: datetime) -> Result:
"""Checks if the billing code is already saved, and if not, saves it.""" """Checks if the billing code is already saved, and if not, saves it."""
logger.info("Saving billing code..") logger.info("Saving billing code..")
@ -187,32 +204,32 @@ def save_billing_code(profile_id: int, billing_code: str, expires_at: str) -> Re
billing_pending=True billing_pending=True
) )
profile.save() profile.save()
return Result(valid=True)
except Exception as e: except Exception as e:
error_msg = f"Legacy system wouldn't create or save Subscription object with {str(billing_code)} and {str(expires_at)} or type {type(expires_at)}, with error: {str(e)}" error_msg = f"Legacy system wouldn't create or save Subscription object with {str(billing_code)} and {str(expires_at)} or type {type(expires_at)}, with error: {str(e)}"
logger.error(error_msg) logger.error(error_msg)
return Result(valid=False, message=error_msg, error_type=ResultError.INVALID_INPUT) return Result(valid=False, message=error_msg, error_type=ResultError.INVALID_INPUT)
return Result(valid=True)
# # get the payment data singleton: # # get the payment data singleton:
# billing_data = PaymentData(instance_id=profile_id) # billing_data = PaymentData(instance_id=profile_id)
# # new # # new
# if billing_data.billing_code is None: # if billing_data.billing_code is None:
# billing_data.billing_code = billing_code # billing_data.billing_code = billing_code
# billing_data.save() # billing_data.save()
# logger.info("saved billing_code") # logger.info("saved billing_code")
# return Result(valid=True) # return Result(valid=True)
# # Already exists and is different: # # Already exists and is different:
# if regular_billing.billing_code != billing_code: # if regular_billing.billing_code != billing_code:
# error_msg = "Critical Error! Billing codes do NOT match" # error_msg = "Critical Error! Billing codes do NOT match"
# logger.error(error_msg) # logger.error(error_msg)
# return Result(valid=False, error_type=ResultError.UNKNOWN, message=error_msg) # return Result(valid=False, error_type=ResultError.UNKNOWN, message=error_msg)
# # already saved: # # already saved:
# return Result(valid=True, message="Already saved.") # return Result(valid=True, message="Already saved.")
def return_pre_existing_payment_data(profile_id: int, subscription: SubscriptionInvoice) -> Result: def return_pre_existing_payment_data(profile_id: int, subscription: SubscriptionInvoice) -> Result: