diff --git a/core/models/pydantic_models/ServerReply.py b/core/models/pydantic_models/ServerReply.py index b607d03..191711d 100644 --- a/core/models/pydantic_models/ServerReply.py +++ b/core/models/pydantic_models/ServerReply.py @@ -7,6 +7,7 @@ from datetime import datetime from enum import Enum from typing import Any, Optional + class ServerError(str, Enum): """Why the Server's Operation was False.""" NONE = "none" @@ -24,8 +25,9 @@ class ServerError(str, Enum): SERVER_SIDE = "server_side" +# @validate_types + @dataclass_json -@validate_types @dataclass class ServerReply(): """Server-Client Communication Format.""" @@ -38,7 +40,12 @@ class ServerReply(): boolean_result: Optional[bool] = None conf: Optional[str] = None payment_status: Optional[bool] = None - expires_at: Optional[datetime] = None + expires_at: Optional[datetime] = field( + default=None, + metadata=config( + decoder=lambda x: datetime.fromisoformat(x.replace('Z', '+00:00')) if x else None + ) + ) diff --git a/core/services/payment_phase/payment_update_tools.py b/core/services/payment_phase/payment_update_tools.py index 580c6a4..2c87e87 100644 --- a/core/services/payment_phase/payment_update_tools.py +++ b/core/services/payment_phase/payment_update_tools.py @@ -9,6 +9,7 @@ from core.errors.logger import logger from core.errors.error_shortcuts import not_supported from core.observers.Notifications import Notifications, EventType +from typing import Optional def _mark_profile_as_paid(profile_id: int): """Save to disk that an individual profile is paid.""" @@ -68,7 +69,7 @@ def mark_as_paid() -> Result: return Result(valid=True) -def check_paid(billing_code: str, connection_observer: ConnectionObserver) -> Result: +def check_paid(billing_code: str, connection_observer: ConnectionObserver, get_expired_date: Optional[bool] = False) -> Result: """ Purpose: Evaluates Payment Status for EITHER payment category @@ -95,22 +96,51 @@ def check_paid(billing_code: str, connection_observer: ConnectionObserver) -> Re "billing_code": billing_code } - result = api_call_and_extraction( - desired_type="payment_status", + #----------------- + # Just get paid or not + #----------------- + if not get_expired_date: + + 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.") + + + #----------------- + # Find out if Paid & Get expiration: + #----------------- + # Get entire ServerReply + server_reply = api_call_and_extraction( + desired_type=None, method=ApiCall.POST, url=url, connection_observer=connection_observer, - payload=submit_data + payload=submit_data, + return_server_reply=True # get server reply ) - if not result.valid: - return result + if not server_reply.valid: + return server_reply - 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.") + payment_status = server_reply.payment_status + expires_at = server_reply.expires_at + if payment_status and expires_at: + return Result(valid=True, data=expires_at, message="Paid with an expiration") + + return Result(valid=False, error_type=ResultError.UNPAID, message="The API call went through, but this subscription is not yet paid.") \ No newline at end of file diff --git a/core/services/payment_phase/pre_existing.py b/core/services/payment_phase/pre_existing.py new file mode 100644 index 0000000..b8d63a1 --- /dev/null +++ b/core/services/payment_phase/pre_existing.py @@ -0,0 +1,38 @@ +from core.services.payment_phase.payment_update_tools import check_paid +from core.services.subscriptions.subscriptions_by_protocol import save_billing_code, wipe_billing_cycle_IF_its_this_profile +from core.services.payment_phase.payment_state import OverallPaymentCycle +from core.models.Result import Result +from core.errors.logger import logger +from core.observers.ConnectionObserver import ConnectionObserver + + +def check_then_save_billing_code(profile_id: int, billing_code: str, connection_observer: ConnectionObserver) -> Result: + """Check if a billing code is paid for, and if it is, then save it and wipe the profile's billing cycle""" + + logger.info(f"Checking payment status of a pre-existing billing code for profile {str(profile_id)}") + + payment_result = check_paid(billing_code=billing_code, connection_observer=connection_observer, get_expired_date=True) + + if not payment_result.valid: + logger.error(f"Check of paid came back that it's NOT paid, despite the user using a pre-existing billing code. {payment_result.message}") + return payment_result + + expires_at = payment_result.data + + saved = save_billing_code( + profile_id=profile_id, + billing_code=billing_code, + expires_at=expires_at, + which_ticket=None, + billing_pending=False + ) + + if saved.valid: + print("save worked, we are clearing the billing cycle") + wiped = wipe_billing_cycle_IF_its_this_profile(profile_id) + if not wiped.valid: + logger.error(f"Unable to wipe the current billing cycle: {wiped.message}") + return wiped + else: + logger.error(f"failed to save the new billing code, returning the saved result {saved}") + return saved \ No newline at end of file diff --git a/core/services/profiles/profile_connections.py b/core/services/profiles/profile_connections.py index e7427be..400a8df 100644 --- a/core/services/profiles/profile_connections.py +++ b/core/services/profiles/profile_connections.py @@ -14,6 +14,8 @@ from core.observers.TicketObserver import TicketObserver from core.services.assassin.ticket_respawn import respawn_profile from core.services.assassin import assassin_tools from core.errors.logger import logger +from core.services.payment_phase.payment_state import OverallPaymentCycle +from core.services.subscriptions.subscriptions_by_protocol import wipe_billing_cycle_IF_its_this_profile from core.Errors import InvalidSubscriptionError, MissingSubscriptionError, ConnectionTerminationError, ProfileActivationError, ProfileDeactivationError, MissingLocationError, ConnectionUnprotectedError, EndpointVerificationError, ProfileStateConflictError from core.controllers.ApplicationController import ApplicationController @@ -310,6 +312,14 @@ def destroy_profile( 2) Disabling it, 3) Destroying it on disk & cache """ + #################################### + # ACTIVE BILLING STATE: + #################################### + wiped = wipe_billing_cycle_IF_its_this_profile(profile.id) + if not wiped.valid: + logger.error(f"Unable to wipe the current billing cycle: {wiped.message}") + return wiped + #################################### # RESPAWN TICKET #################################### diff --git a/core/services/subscriptions/subscriptions_by_protocol.py b/core/services/subscriptions/subscriptions_by_protocol.py index 11f62f9..1408425 100644 --- a/core/services/subscriptions/subscriptions_by_protocol.py +++ b/core/services/subscriptions/subscriptions_by_protocol.py @@ -474,6 +474,20 @@ def get_current_billing_data(category: Optional[PaymentCategory] = None, profile return Result(valid=True, data=billing_data) + +def wipe_billing_cycle_IF_its_this_profile(profile_id) -> Result: + # Overall State: + payment_cycle = OverallPaymentCycle() + if not payment_cycle: + return Result(valid=True) + + if payment_cycle.billing_code != profile_id: + return Result(valid=True) + + return wipe_billing_cycle(inserted_profile_id=profile.id) + + + def wipe_billing_cycle(inserted_profile_id: Optional[int] = None) -> Result: """Wipes the entire overall cycle""" logger.info("Wiping the overall cycle, so this cycle no longer blocks other new ones") @@ -573,10 +587,10 @@ def wipe_subscription(profile_id: int) -> Result: @type_checker -def save_billing_code(profile_id: int, billing_code: str, expires_at: datetime, which_ticket: Optional[int] = None) -> Result: +def save_billing_code(profile_id: int, billing_code: str, expires_at: datetime, which_ticket: Optional[int] = None, billing_pending: bool = True) -> Result: """Checks if the billing code is already saved, and if not, saves it.""" - logger.info("Saving billing code..") + logger.info(f"Saving billing code with the billing_pending as {billing_pending}") PRE_REQS = [profile_id, billing_code, expires_at] if None in PRE_REQS: @@ -595,7 +609,7 @@ def save_billing_code(profile_id: int, billing_code: str, expires_at: datetime, profile.subscription = Subscription( billing_code=billing_code, expires_at=expires_at, - billing_pending=True + billing_pending=billing_pending ) if which_ticket: profile.ticket = which_ticket