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.MISSING_DATA: ResultError.INVALID_INPUT,
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:
print("Starting format_server_response conversion")
"""Turn an API response into a Result Object"""
# 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.")
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}")
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):
error_msg = "Developer Error: You're asking for a data type that isn't in the object!"
logger.error(error_msg)
return Result(valid=False, error_type=ResultError.INVALID_INPUT, message=error_msg)
# Success! Extract and Return Data:
if server_reply.valid:
data = getattr(server_reply, desired_type)
data = getattr(server_reply, desired_type)
if 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}")
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)
# return Result(valid=False, error_type=error_type, message=server_reply.message)
def translate_server_failure(server_reply: ServerReply) -> Result:

View file

@ -60,13 +60,13 @@ def register_wireguard_session(
logger.error(error_msg)
raise ConnectionError(error_msg)
wireguard_configuration = api_result.data
print(f"wireguard_configuration type is {type(wireguard_configuration)}")
# print(f"wireguard_configuration value is {wireguard_configuration}")
# 2nd Layer Translation:
result = format_server_response(api_response=api_result, desired_type="data")
if not result.valid:
# KILLING THE RESULT FOR NOW. Later, replace this error raise with the return of data,
raise InvalidSubscriptionError(result.message)
if isinstance(wireguard_configuration, dict):
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:
raise InvalidSubscriptionError()
@ -76,14 +76,25 @@ def register_wireguard_session(
return Result(valid=True)
# over-engineering:
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.")
# print(f"wireguard_configuration type is {type(wireguard_configuration)}")
# # print(f"wireguard_configuration value is {wireguard_configuration}")
# if isinstance(wireguard_configuration, dict):
# print(f"wireguard_configuration is {type(wireguard_configuration)}")
# print(f"wireguard_configuration is {wireguard_configuration}")
# 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:

View file

@ -194,7 +194,7 @@ def _ensure_wireguard_configured(
logger.info(f"[CONNECTION] Checking if the profile already has a wg config..")
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..")
register_wireguard_session(profile, connection_observer=connection_observer)
registry_result = register_wireguard_session(profile, connection_observer=connection_observer)
def _establish_with_renegotiation(

View file

@ -1,5 +1,5 @@
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
@ -24,13 +24,13 @@ MAX_FAILED_CONNECTIONS = 8
class CheckPaidTimer:
_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"""
if cls._instance is None:
cls._instance = super().__new__(cls)
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
if hasattr(self, '_initialized'):
return
@ -42,6 +42,7 @@ class CheckPaidTimer:
self.view_callback = view_callback
self.ui_dispatcher = ui_dispatcher
self.billing_code = billing_code
self.profile_id = profile_id
self.connection_observer = connection_observer
self.payment_type = payment_type
self.failed_connections = 0
@ -99,7 +100,15 @@ class CheckPaidTimer:
if result.valid:
print("PAID! Stopping timer")
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)
# 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.Constants import Constants
from datetime import datetime
def check_paid(billing_code: str, connection_observer: ConnectionObserver) -> Result:
"""Evaluates Payment Status from the Server"""
@ -17,7 +18,6 @@ def check_paid(billing_code: str, connection_observer: ConnectionObserver) -> Re
"billing_code": billing_code
}
url = f'{Constants.SP_BASE_URL}/v2/check_paid'
print(f"url is {url}")
api_response = connect.single_endpoint(
method="post",
@ -162,7 +162,24 @@ def get_profile_subscription(profile_id: int) -> 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."""
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
)
profile.save()
return Result(valid=True)
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)}"
logger.error(error_msg)
return Result(valid=False, message=error_msg, error_type=ResultError.INVALID_INPUT)
return Result(valid=True)
# # get the payment data singleton:
# billing_data = PaymentData(instance_id=profile_id)
# # 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)
# # 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 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.")
# # already saved:
# return Result(valid=True, message="Already saved.")
def return_pre_existing_payment_data(profile_id: int, subscription: SubscriptionInvoice) -> Result: