extends RefCounted class_name EconomyApiClient ## Helper class for AGP Economy REST endpoints (Wallet, Payments, Marketplace, Payouts). ## ## docs/planning/economy.md US-6 (task cc09c1428892). static func get_api_client(node: Node) -> Node: if node.has_node("/root/ApiClient"): return node.get_node("/root/ApiClient") return null static func generate_idempotency_key() -> String: return "%d-%d" % [Time.get_unix_time_from_system(), randi() % 1000000] # ── Wallet & Currency ── static func get_wallet(api: Node) -> Dictionary: return await api.request(HTTPClient.METHOD_GET, "/api/wallet", "", "application/json", true) static func get_wallet_transactions(api: Node, limit: int = 50, offset: int = 0) -> Dictionary: return await api.request(HTTPClient.METHOD_GET, "/api/wallet/transactions?limit=%d&offset=%d" % [limit, offset], "", "application/json", true) # ── Premium Purchases ── static func purchase_credits(api: Node, credits: int, idempotency_key: String = "") -> Dictionary: var custom_headers := PackedStringArray() var key := idempotency_key if idempotency_key != "" else generate_idempotency_key() custom_headers.append("Idempotency-Key: " + key) var sess_res: Dictionary = await api.request( HTTPClient.METHOD_POST, "/api/payments/purchase", JSON.stringify({"credits": credits}), "application/json", true, custom_headers ) if not sess_res.ok: return sess_res var sess_id: String = str(sess_res.data.get("session_id", "")) if sess_id == "": return {"ok": false, "status": 0, "data": null, "detail": "Invalid session returned"} return await api.request( HTTPClient.METHOD_POST, "/api/payments/confirm", JSON.stringify({"session_id": sess_id}), "application/json", true ) # ── Catalog Items & Listings ── static func get_catalog_items(api: Node, mine: bool = false, item_type: String = "", limit: int = 50, offset: int = 0) -> Dictionary: var q := "/api/marketplace/items?limit=%d&offset=%d" % [limit, offset] if mine: q += "&mine=true" if item_type != "": q += "&item_type=" + item_type.uri_encode() return await api.request(HTTPClient.METHOD_GET, q, "", "application/json", true) static func create_catalog_item(api: Node, name: String, item_type: String, description: String = "", metadata_json: Dictionary = {}) -> Dictionary: var payload := { "name": name, "item_type": item_type, "description": description, "metadata_json": metadata_json } return await api.request(HTTPClient.METHOD_POST, "/api/marketplace/items", JSON.stringify(payload), "application/json", true) static func create_listing(api: Node, catalog_item_id: int, price: int) -> Dictionary: var payload := { "catalog_item_id": catalog_item_id, "price": price } return await api.request(HTTPClient.METHOD_POST, "/api/marketplace/listings", JSON.stringify(payload), "application/json", true) static func search_listings(api: Node, query: String = "", item_type: String = "", min_price: int = -1, max_price: int = -1, limit: int = 50, offset: int = 0) -> Dictionary: var q := "/api/marketplace/listings?limit=%d&offset=%d" % [limit, offset] if query != "": q += "&q=" + query.uri_encode() if item_type != "": q += "&item_type=" + item_type.uri_encode() if min_price >= 0: q += "&min_price=%d" % min_price if max_price >= 0: q += "&max_price=%d" % max_price return await api.request(HTTPClient.METHOD_GET, q, "", "application/json", false) static func get_listing(api: Node, listing_id: int) -> Dictionary: return await api.request(HTTPClient.METHOD_GET, "/api/marketplace/listings/%d" % listing_id, "", "application/json", false) static func update_listing(api: Node, listing_id: int, price: int = -1, status: String = "") -> Dictionary: var payload := {} if price > 0: payload["price"] = price if status != "": payload["status"] = status return await api.request(HTTPClient.METHOD_PATCH, "/api/marketplace/listings/%d" % listing_id, JSON.stringify(payload), "application/json", true) static func delist_listing(api: Node, listing_id: int) -> Dictionary: return await api.request(HTTPClient.METHOD_DELETE, "/api/marketplace/listings/%d" % listing_id, "", "application/json", true) # ── Purchases & Escrow ── static func purchase_listing(api: Node, listing_id: int, idempotency_key: String = "") -> Dictionary: var custom_headers := PackedStringArray() var key := idempotency_key if idempotency_key != "" else generate_idempotency_key() custom_headers.append("Idempotency-Key: " + key) return await api.request( HTTPClient.METHOD_POST, "/api/marketplace/listings/%d/purchase" % listing_id, "", "application/json", true, custom_headers ) static func get_purchases(api: Node, role: String = "all", limit: int = 50, offset: int = 0) -> Dictionary: return await api.request( HTTPClient.METHOD_GET, "/api/marketplace/purchases?role=%s&limit=%d&offset=%d" % [role.uri_encode(), limit, offset], "", "application/json", true ) # ── Creator Balances & Payouts ── static func get_creator_balance(api: Node) -> Dictionary: return await api.request(HTTPClient.METHOD_GET, "/api/marketplace/creator/balance", "", "application/json", true) static func request_payout(api: Node, amount: int, external_ref: String = "") -> Dictionary: var payload := {"amount": amount} if external_ref != "": payload["external_ref"] = external_ref return await api.request(HTTPClient.METHOD_POST, "/api/payouts", JSON.stringify(payload), "application/json", true) static func get_payouts(api: Node, limit: int = 50, offset: int = 0) -> Dictionary: return await api.request(HTTPClient.METHOD_GET, "/api/payouts?limit=%d&offset=%d" % [limit, offset], "", "application/json", true)