sp-hydra-veil-core/core/services/subscriptions/subscriptions.py

180 lines
No EOL
5.8 KiB
Python

from core.services.networking.httpx import connect
from core.services.networking.api_requests.ApiResponseModel import ApiResponse, ErrorType
from core.models.invoice.Invoice import Invoice
from core.models.invoice.PaymentMethod import PaymentMethod
from core.models.session.SessionProfile import SessionProfile
from core.models.system.SystemProfile import SystemProfile
from core.Errors import MissingSubscriptionError, InvalidSubscriptionError
# from core.services.WebServiceApiService import WebServiceApiService
from core.controllers.ConnectionController import ConnectionController
from core.observers.ConnectionObserver import ConnectionObserver
from core.Constants import Constants
from core.models.Subscription import Subscription
from core.models.SubscriptionPlan import SubscriptionPlan
from core.errors.logger import logger
from typing import Union, Optional
import re
def activate_subscription(
profile: Union[SessionProfile, SystemProfile],
connection_observer: Optional[ConnectionObserver] = None
) -> bool:
"""
Purpose:
Ensure subscription is ready.
Features:
Idempotent.
Checks local first
Confusion:
This returns True both if was already active or just activated.
Returns:
Returns True if subscription was already active.
Returns True if subscription was just activated.
Errors:
True.
Raises if subscription is missing or invalid. (not false)
"""
if not profile.has_subscription():
raise MissingSubscriptionError()
# Already activated—nothing to do
if profile.subscription.has_been_activated():
return True
# ==================================================
# ENCRYPTED PROXY
# ==================================================
if profile.connection.code in ("vless", "hysteria2"):
logger.info("Getting an Encrypted proxy subscription code")
subscription = get_encrypted_proxy_billing_code(
location_id=profile.location.id,
subscription_plan_id=profile.subscription.id,
connection_observer=connection_observer
)
# ==================================================
# WIREGUARD AND GENERIC SOCKS5 PROXY
# ==================================================
else:
logger.info("Getting a Wireguard or regular proxy subscription DATED EXPIRED")
# Fetch and activate
subscription = get_subscription(
billing_code=profile.subscription.billing_code,
connection_observer=connection_observer
)
# legacy
# subscription = ConnectionController.with_preferred_connection(
# profile.subscription.billing_code,
# task=WebServiceApiService.get_subscription,
# connection_observer=connection_observer
# )
if subscription is None:
raise InvalidSubscriptionError()
profile.subscription = subscription
profile.save()
return True
def is_subscription_ready(profile: Union[SessionProfile, SystemProfile]) -> bool:
"""Check if subscription exists and is activated (no side effects)."""
return (
profile.has_subscription()
and profile.subscription.has_been_activated()
)
def get_subscription(billing_code: str, connection_observer: ConnectionObserver) -> Subscription:
billing_code = billing_code.replace('-', '').upper()
billing_code_fragments = re.findall('....?', billing_code)
billing_code = '-'.join(billing_code_fragments)
url = f'{Constants.SP_API_BASE_URL}/subscriptions/current'
api_response = connect.single_endpoint(
method="get",
url=url,
observer=connection_observer,
payload=None,
billing_code=billing_code
)
if api_response.valid:
raw_json = api_response.data
subscription = raw_json['data']
return Subscription(billing_code, Subscription.from_iso_format(subscription['expires_at']))
else:
logger.error(f"API Reply of {api_response.error_type}")
return None
def get_invoice(billing_code: str, connection_observer: ConnectionObserver) -> Invoice:
url = f'{Constants.SP_API_BASE_URL}/invoices/current'
api_response = connect.single_endpoint(
method="get",
url=url,
observer=connection_observer,
payload=None,
billing_code=billing_code
)
if api_response.valid:
raw_json = api_response.data
response_data = raw_json['data']
invoice = {
'status': response_data['status'],
'expires_at': response_data['expires_at']
}
payment_methods = []
for payment_method in response_data['payment_methods']:
payment_methods.append(PaymentMethod(payment_method['code'], payment_method['name'], payment_method['address'], payment_method['payment_link'], payment_method['rate'], payment_method['amount'], payment_method['due']))
return Invoice(billing_code, invoice['status'], invoice['expires_at'], tuple[PaymentMethod](payment_methods))
else:
return None
def get_encrypted_proxy_billing_code(
location_id: int,
subscription_plan_id: int,
connection_observer: ConnectionObserver
) -> str:
url = f'{Constants.SP_API_BASE_URL}/api/v1/subscriptions'
payload = {
"subscription_plan_id": subscription_plan_id,
"location_id": location_id
}
api_response = connect.single_endpoint(
method="post",
url=url,
observer=connection_observer,
payload=payload,
billing_code=None
)
if api_response.valid:
raw_reply = api_response.data
data = raw_reply['data']
billing_code = data.get('billing_code', None)
return billing_code
else:
logger.error(f"API Error: {api_response.error_type}")
return None