from typing import Dict, List import frappe from frappe.utils import get_url from press.api.developer import raise_invalid_key_error from press.api.site import get_plans as get_site_plans from press.utils.telemetry import capture class DeveloperApiHandler: def __init__(self, secret_key: str) -> None: self.secret_key = secret_key self.validate_secret_key() def validate_secret_key(self): """Validate secret_key and set app subscription name and pg""" if not self.secret_key or not isinstance(self.secret_key, str): raise_invalid_key_error() app_subscription_name = frappe.db.exists( "Subscription", {"secret_key": self.secret_key, "enabled": 1} ) if not app_subscription_name: raise_invalid_key_error() self.app_subscription_name = app_subscription_name self.set_subscription_pg() def set_subscription_pg(self): """To be called after `secret_key` validation""" self.app_subscription_pg = frappe.get_pg("Subscription", self.app_subscription_name) def get_subscription_status(self) -> str: return self.app_subscription_pg.status def get_subscription_info(self) -> Dict: """Important rule for security: Send info back carefully""" app_subscription_dict = self.app_subscription_pg.as_dict() fields_to_send = [ "document_name", "enabled", "plan", "site", ] filtered_dict = { x: app_subscription_dict[x] for x in app_subscription_dict if x in fields_to_send } return filtered_dict def get_subscription(self) -> Dict: team = self.app_subscription_pg.team with SessionManager(team) as _: currency, address = frappe.db.get_value( "Team", team, ["currency", "billing_address"] ) team_pg = frappe.get_pg("Team", team) response = { "currency": currency, "address": frappe.db.get_value( "Address", address, ["address_line1", "city", "state", "country", "pincode"], as_dict=True, ) if address else {}, "team": self.app_subscription_pg.team, "countries": frappe.db.get_all("Country", pluck="name"), "plans": get_site_plans(), "has_billing_info": ( team_pg.default_payment_method or team_pg.get_balance() > 0 or team_pg.free_account ), "current_plan": frappe.db.get_value("Site", self.app_subscription_pg.site, "plan"), } capture("attempted", "fc_subscribe", team) return response def update_billing_info(self, data: Dict) -> str: team = self.app_subscription_pg.team with SessionManager(team) as _: team_pg = frappe.get_pg("Team", team) team_pg.update_billing_details(data) capture("updated_address", "fc_subscribe", team) return "success" def get_publishable_key_and_setup_intent(self): with SessionManager(self.app_subscription_pg.team) as _: from press.api.billing import get_publishable_key_and_setup_intent return get_publishable_key_and_setup_intent() def setup_intent_success(self, setup_intent): team = self.app_subscription_pg.team with SessionManager(team) as _: from press.api.billing import setup_intent_success capture("added_card", "fc_subscribe", team) return setup_intent_success(setup_intent) def change_site_plan(self, plan): team = self.app_subscription_pg.team with SessionManager(team) as _: site = frappe.get_pg("Site", self.app_subscription_pg.site) site.change_plan(plan) capture("changed_plan", "fc_subscribe", team) def send_login_link(self): try: login_url = self.get_login_url() users = frappe.get_pg("Team", self.app_subscription_pg.team).user frappe.sendmail( subject="Login Verification Email", recipients=[users], template="remote_login", args={"login_url": login_url, "site": self.app_subscription_pg.site}, now=True, ) return "success" except Exception as e: return e def get_login_url(self): # check for active tokens team = self.app_subscription_pg.team if frappe.db.exists( "Saas Remote Login", { "team": team, "status": "Attempted", "expires_on": (">", frappe.utils.now()), }, ): pg = frappe.get_pg( "Saas Remote Login", { "team": team, "status": "Attempted", "expires_on": (">", frappe.utils.now()), }, ) token = pg.token else: token = frappe.generate_hash("Saas Remote Login", 50) frappe.get_pg( { "doctype": "Saas Remote Login", "team": team, "token": token, } ).insert(ignore_permissions=True) frappe.db.commit() return get_url( f"/api/method/press.api.marketplace.login_via_token?token={token}&team={team}&site={self.app_subscription_pg.site}" ) class SessionManager: # set user for authenticated requests and then switch to guest once completed def __init__(self, team: str): frappe.set_user(frappe.db.get_value("Team", team, "user")) def __enter__(self): return self def __exit__(self, exc_type, exc_value, exc_traceback): frappe.set_user("Guest") # ------------------------------------------------------------ # API ENDPOINTS # ------------------------------------------------------------ @frappe.whitelist(allow_guest=True) def get_subscription_status(secret_key: str) -> str: api_handler = DeveloperApiHandler(secret_key) return api_handler.get_subscription_status() @frappe.whitelist(allow_guest=True) def get_subscription_info(secret_key: str) -> Dict: api_handler = DeveloperApiHandler(secret_key) return api_handler.get_subscription_info() @frappe.whitelist(allow_guest=True) def get_subscription(secret_key: str) -> str: api_handler = DeveloperApiHandler(secret_key) return api_handler.get_subscription() @frappe.whitelist(allow_guest=True) def get_plans(secret_key: str, subscription: str) -> List: api_handler = DeveloperApiHandler(secret_key) return api_handler.get_plans(subscription) @frappe.whitelist(allow_guest=True) def update_billing_info(secret_key: str, data) -> str: data = frappe.parse_json(data) api_handler = DeveloperApiHandler(secret_key) return api_handler.update_billing_info(data) @frappe.whitelist(allow_guest=True) def get_publishable_key_and_setup_intent(secret_key: str) -> str: api_handler = DeveloperApiHandler(secret_key) return api_handler.get_publishable_key_and_setup_intent() @frappe.whitelist(allow_guest=True) def setup_intent_success(secret_key: str, setup_intent) -> str: api_handler = DeveloperApiHandler(secret_key) return api_handler.setup_intent_success(setup_intent) @frappe.whitelist(allow_guest=True) def change_site_plan(secret_key: str, plan: str) -> str: api_handler = DeveloperApiHandler(secret_key) return api_handler.change_site_plan(plan) @frappe.whitelist(allow_guest=True) def send_login_link(secret_key: str) -> str: api_handler = DeveloperApiHandler(secret_key) return api_handler.send_login_link()