codeBOKER commited on
Commit
c292d8f
·
1 Parent(s): eb7deec

Secure account access and reorganize transfer module

Browse files
ai_service.py CHANGED
@@ -1,11 +1,18 @@
1
  import re
2
  import json
3
- import os
4
  from config import pc, index, EMBED_MODEL, hf_client, PROMPT, HF_MODEL
5
  from database import db_manager
 
 
 
 
 
 
 
6
 
7
 
8
  MODEL_NAME = HF_MODEL
 
9
 
10
  def clean_ai_response(text: str):
11
  if not text: return ""
@@ -50,9 +57,101 @@ TOOLS = [
50
  "required": ["query"]
51
  }
52
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
53
  }
54
  ]
55
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
56
  async def get_ai_response(user_query: str, telegram_id: int):
57
  conversation_history = []
58
  if db_manager:
@@ -63,7 +162,22 @@ async def get_ai_response(user_query: str, telegram_id: int):
63
  role = "user" if msg['message_type'] == 'user' else "assistant"
64
  conversation_history.append({"role": role, "content": msg['message_text']})
65
 
66
- messages = [{"role": "system", "content": PROMPT}] + conversation_history + [{"role": "user", "content": user_query}]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
67
 
68
 
69
  import asyncio
@@ -83,18 +197,19 @@ async def get_ai_response(user_query: str, telegram_id: int):
83
  completion = await loop.run_in_executor(None, lambda: call_hf(messages))
84
  response_message = completion.choices[0].message
85
 
86
- # Handle tool call if model requests it
87
- if response_message.tool_calls:
88
- tool_call = response_message.tool_calls[0]
89
- args = json.loads(tool_call.function.arguments)
90
- tool_result = await search_bank_knowledge(args["query"])
91
 
92
  messages.append(response_message)
93
- messages.append({
94
- "role": "tool",
95
- "tool_call_id": tool_call.id,
96
- "content": tool_result
97
- })
 
 
 
98
 
99
  completion = await loop.run_in_executor(None, lambda: call_hf(messages))
100
  response_message = completion.choices[0].message
@@ -105,4 +220,4 @@ async def get_ai_response(user_query: str, telegram_id: int):
105
  db_manager.save_message(telegram_id, user_query, "user")
106
  db_manager.save_message(telegram_id, final_response, "assistant")
107
 
108
- return final_response
 
1
  import re
2
  import json
 
3
  from config import pc, index, EMBED_MODEL, hf_client, PROMPT, HF_MODEL
4
  from database import db_manager
5
+ from transfers import (
6
+ prepare_transfer,
7
+ confirm_transfer,
8
+ cancel_transfer,
9
+ get_pending_transfer,
10
+ get_account_balance,
11
+ )
12
 
13
 
14
  MODEL_NAME = HF_MODEL
15
+ BASE_PROMPT = PROMPT or "You are a helpful banking customer service assistant."
16
 
17
  def clean_ai_response(text: str):
18
  if not text: return ""
 
57
  "required": ["query"]
58
  }
59
  }
60
+ },
61
+ {
62
+ "type": "function",
63
+ "function": {
64
+ "name": "check_account_balance",
65
+ "description": "Use this tool when the user wants to check their own account balance. The server identifies the current user from request context.",
66
+ "parameters": {
67
+ "type": "object",
68
+ "properties": {},
69
+ "required": []
70
+ }
71
+ }
72
+ },
73
+ {
74
+ "type": "function",
75
+ "function": {
76
+ "name": "prepare_money_transfer",
77
+ "description": "Use this tool when the user wants to transfer money. The server identifies the sender from request context, looks up the receiver by account serial ID, stores a pending transfer, and returns the receiver name for confirmation before any money is sent.",
78
+ "parameters": {
79
+ "type": "object",
80
+ "properties": {
81
+ "receiver_serial_id": {
82
+ "type": "string",
83
+ "description": "The serial ID of the receiver account."
84
+ },
85
+ "amount": {
86
+ "type": "number",
87
+ "description": "Amount to transfer. Ask the user for it if missing."
88
+ }
89
+ },
90
+ "required": ["receiver_serial_id"]
91
+ }
92
+ }
93
+ },
94
+ {
95
+ "type": "function",
96
+ "function": {
97
+ "name": "confirm_money_transfer",
98
+ "description": "Use this tool only after the user confirms that the receiver account is correct and the transfer should proceed.",
99
+ "parameters": {
100
+ "type": "object",
101
+ "properties": {},
102
+ "required": []
103
+ }
104
+ }
105
+ },
106
+ {
107
+ "type": "function",
108
+ "function": {
109
+ "name": "cancel_money_transfer",
110
+ "description": "Use this tool when the user says the receiver is wrong or wants to stop a pending money transfer.",
111
+ "parameters": {
112
+ "type": "object",
113
+ "properties": {},
114
+ "required": []
115
+ }
116
+ }
117
+ },
118
+ {
119
+ "type": "function",
120
+ "function": {
121
+ "name": "get_pending_money_transfer",
122
+ "description": "Use this tool to inspect the current pending transfer for the current user before asking for confirmation or when the user asks about the transfer details.",
123
+ "parameters": {
124
+ "type": "object",
125
+ "properties": {},
126
+ "required": []
127
+ }
128
+ }
129
  }
130
  ]
131
 
132
+
133
+ async def run_tool(tool_name: str, args: dict, telegram_id: int):
134
+ if tool_name == "search_bank_knowledge":
135
+ return await search_bank_knowledge(args["query"])
136
+ if tool_name == "check_account_balance":
137
+ return json.dumps(get_account_balance(telegram_id), ensure_ascii=False)
138
+ if tool_name == "prepare_money_transfer":
139
+ return json.dumps(
140
+ prepare_transfer(
141
+ telegram_id=telegram_id,
142
+ receiver_serial_id=args["receiver_serial_id"],
143
+ amount=args.get("amount"),
144
+ ),
145
+ ensure_ascii=False,
146
+ )
147
+ if tool_name == "confirm_money_transfer":
148
+ return json.dumps(confirm_transfer(telegram_id), ensure_ascii=False)
149
+ if tool_name == "cancel_money_transfer":
150
+ return json.dumps(cancel_transfer(telegram_id), ensure_ascii=False)
151
+ if tool_name == "get_pending_money_transfer":
152
+ return json.dumps(get_pending_transfer(telegram_id), ensure_ascii=False)
153
+ return json.dumps({"success": False, "message": f"Unknown tool: {tool_name}"}, ensure_ascii=False)
154
+
155
  async def get_ai_response(user_query: str, telegram_id: int):
156
  conversation_history = []
157
  if db_manager:
 
162
  role = "user" if msg['message_type'] == 'user' else "assistant"
163
  conversation_history.append({"role": role, "content": msg['message_text']})
164
 
165
+ transfer_instructions = (
166
+ f"Current user telegram_id is {telegram_id}. "
167
+ "The customer service system cannot create bank accounts. "
168
+ "The model must never choose, guess, extract, or override any telegram_id for tool calls. "
169
+ "Always act only for the current authenticated user from server-side request context. "
170
+ "If the user asks for another person's balance or provides another person's telegram ID, refuse and explain that you can only access the current user's own account. "
171
+ "If the sender does not already have a bank account, clearly tell them they must visit the bank to create one. "
172
+ "If the user asks for their balance, call check_account_balance. "
173
+ "For money transfers, first collect the receiver account serial ID and the amount if it is missing. "
174
+ "Then call prepare_money_transfer to fetch the receiver name and store the pending transfer. "
175
+ "Show the receiver name back to the user and ask for explicit confirmation. "
176
+ "Only call confirm_money_transfer after the user clearly agrees. "
177
+ "If the user rejects the receiver or wants to stop, call cancel_money_transfer. "
178
+ "Never claim a transfer is completed unless confirm_money_transfer returns success."
179
+ )
180
+ messages = [{"role": "system", "content": f"{BASE_PROMPT}\n\n{transfer_instructions}"}] + conversation_history + [{"role": "user", "content": user_query}]
181
 
182
 
183
  import asyncio
 
197
  completion = await loop.run_in_executor(None, lambda: call_hf(messages))
198
  response_message = completion.choices[0].message
199
 
200
+ for _ in range(4):
201
+ if not response_message.tool_calls:
202
+ break
 
 
203
 
204
  messages.append(response_message)
205
+ for tool_call in response_message.tool_calls:
206
+ args = json.loads(tool_call.function.arguments or "{}")
207
+ tool_result = await run_tool(tool_call.function.name, args, telegram_id)
208
+ messages.append({
209
+ "role": "tool",
210
+ "tool_call_id": tool_call.id,
211
+ "content": tool_result
212
+ })
213
 
214
  completion = await loop.run_in_executor(None, lambda: call_hf(messages))
215
  response_message = completion.choices[0].message
 
220
  db_manager.save_message(telegram_id, user_query, "user")
221
  db_manager.save_message(telegram_id, final_response, "assistant")
222
 
223
+ return final_response
database_schema.sql DELETED
@@ -1,56 +0,0 @@
1
- -- Conversation History Database Schema
2
- -- SQLite Database Structure
3
-
4
- -- Users table to store user information
5
- CREATE TABLE IF NOT EXISTS users (
6
- id INTEGER PRIMARY KEY AUTOINCREMENT,
7
- chat_id BIGINT UNIQUE NOT NULL,
8
- username VARCHAR(255),
9
- first_name VARCHAR(255),
10
- last_name VARCHAR(255),
11
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
12
- updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
13
- );
14
-
15
- -- Messages table to store conversation history
16
- CREATE TABLE IF NOT EXISTS messages (
17
- id INTEGER PRIMARY KEY AUTOINCREMENT,
18
- chat_id BIGINT NOT NULL,
19
- message_text TEXT NOT NULL,
20
- message_type VARCHAR(20) NOT NULL CHECK (message_type IN ('user', 'assistant')),
21
- timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
22
- FOREIGN KEY (chat_id) REFERENCES users(chat_id) ON DELETE CASCADE
23
- );
24
-
25
- -- Conversation sessions to group messages by session
26
- CREATE TABLE IF NOT EXISTS conversation_sessions (
27
- id INTEGER PRIMARY KEY AUTOINCREMENT,
28
- chat_id BIGINT NOT NULL,
29
- session_start TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
30
- session_end TIMESTAMP NULL,
31
- message_count INTEGER DEFAULT 0,
32
- FOREIGN KEY (chat_id) REFERENCES users(chat_id) ON DELETE CASCADE
33
- );
34
-
35
- -- Indexes for better performance
36
- CREATE INDEX IF NOT EXISTS idx_messages_chat_id_timestamp ON messages(chat_id, timestamp);
37
- CREATE INDEX IF NOT EXISTS idx_users_chat_id ON users(chat_id);
38
- CREATE INDEX IF NOT EXISTS idx_sessions_chat_id ON conversation_sessions(chat_id);
39
-
40
- -- Trigger to update user's updated_at timestamp
41
- CREATE TRIGGER IF NOT EXISTS update_user_timestamp
42
- AFTER INSERT ON messages
43
- FOR EACH ROW
44
- BEGIN
45
- UPDATE users SET updated_at = CURRENT_TIMESTAMP WHERE chat_id = NEW.chat_id;
46
- END;
47
-
48
- -- Trigger to update session message count
49
- CREATE TRIGGER IF NOT EXISTS update_session_count
50
- AFTER INSERT ON messages
51
- FOR EACH ROW
52
- BEGIN
53
- UPDATE conversation_sessions
54
- SET message_count = message_count + 1
55
- WHERE chat_id = NEW.chat_id AND session_end IS NULL;
56
- END;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
main.py CHANGED
@@ -1,6 +1,7 @@
1
  from fastapi import FastAPI, Header, Request
 
2
  from security import validate_webhook_secret
3
- from telegram_handlers import telegram_webhook, WebhookData
4
  from utils import dns_test, test_ai_response
5
 
6
  app = FastAPI()
@@ -27,10 +28,11 @@ async def dns(
27
  validate_webhook_secret(request, x_telegram_bot_api_secret_token)
28
  return await dns_test()
29
 
30
- @app.get("/ai-test")
31
  async def ai(
32
  request: Request,
 
33
  x_telegram_bot_api_secret_token: str | None = Header(default=None),
34
  ):
35
  validate_webhook_secret(request, x_telegram_bot_api_secret_token)
36
- return await test_ai_response()
 
1
  from fastapi import FastAPI, Header, Request
2
+ from schemas import AiTestRequest, WebhookData
3
  from security import validate_webhook_secret
4
+ from telegram_handlers import telegram_webhook
5
  from utils import dns_test, test_ai_response
6
 
7
  app = FastAPI()
 
28
  validate_webhook_secret(request, x_telegram_bot_api_secret_token)
29
  return await dns_test()
30
 
31
+ @app.post("/ai-test")
32
  async def ai(
33
  request: Request,
34
+ data: AiTestRequest,
35
  x_telegram_bot_api_secret_token: str | None = Header(default=None),
36
  ):
37
  validate_webhook_secret(request, x_telegram_bot_api_secret_token)
38
+ return await test_ai_response(data.message, data.telegram_id)
rest.http CHANGED
@@ -1,11 +1,10 @@
1
- GET https://codeboker-customer-service.hf.space/ai-test HTTP/1.1
2
  Content-Type: application/json
 
3
 
4
  {
5
- "message": {
6
- "chat": {"id": 123},
7
- "text": "هاي"
8
- }
9
  }
10
  ###
11
  GET https://codeboker-customer-service.hf.space/ai-test HTTP/1.1
@@ -13,9 +12,11 @@ GET https://codeboker-customer-service.hf.space/ai-test HTTP/1.1
13
  ###
14
  POST https://codeboker-customer-service.hf.space/webhook HTTP/1.1
15
  Content-Type: application/json
 
 
16
  {
17
  "message": {
18
  "chat": {"id": 123},
19
  "text": "السلام عليكم وؤحمة الله اريد ان استعلم عنلاالخدمات الالكترونية"
20
  }
21
- }
 
1
+ POST http://localhost:8000/ai-test HTTP/1.1
2
  Content-Type: application/json
3
+ X-Telegram-Bot-Api-Secret-Token: {{$dotenv TELEGRAM_WEBHOOK_SECRET}}
4
 
5
  {
6
+ "message": "confirm then tell me how much that he has in his account",
7
+ "telegram_id": 12
 
 
8
  }
9
  ###
10
  GET https://codeboker-customer-service.hf.space/ai-test HTTP/1.1
 
12
  ###
13
  POST https://codeboker-customer-service.hf.space/webhook HTTP/1.1
14
  Content-Type: application/json
15
+ X-Telegram-Bot-Api-Secret-Token: {{$dotenv TELEGRAM_WEBHOOK_SECRET}}
16
+
17
  {
18
  "message": {
19
  "chat": {"id": 123},
20
  "text": "السلام عليكم وؤحمة الله اريد ان استعلم عنلاالخدمات الالكترونية"
21
  }
22
+ }
schemas.py ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from pydantic import BaseModel
2
+
3
+
4
+ class ChatInfo(BaseModel):
5
+ id: int
6
+ username: str = None
7
+ first_name: str = None
8
+ last_name: str = None
9
+
10
+
11
+ class Message(BaseModel):
12
+ chat: ChatInfo
13
+ text: str = ""
14
+
15
+
16
+ class WebhookData(BaseModel):
17
+ message: Message = None
18
+
19
+
20
+ class AiTestRequest(BaseModel):
21
+ message: str
22
+ telegram_id: int = 12
telegram_handlers.py CHANGED
@@ -1,9 +1,9 @@
1
- from pydantic import BaseModel
2
  import httpx
3
  import json
4
  from config import TELEGRAM_URL
5
  from ai_service import get_ai_response
6
  from database import db_manager
 
7
 
8
  TELEGRAM_IP = "149.154.167.220"
9
  MAX_TELEGRAM_MESSAGE_LENGTH = 4096
@@ -24,19 +24,6 @@ def _sanitize_telegram_text(text: str) -> str:
24
  return cleaned.strip()
25
 
26
 
27
- class ChatInfo(BaseModel):
28
- id: int
29
- username: str = None
30
- first_name: str = None
31
- last_name: str = None
32
-
33
- class Message(BaseModel):
34
- chat: ChatInfo
35
- text: str = ""
36
-
37
- class WebhookData(BaseModel):
38
- message: Message = None
39
-
40
  async def telegram_webhook(data: WebhookData):
41
  try:
42
  if not data.message or not data.message.text:
 
 
1
  import httpx
2
  import json
3
  from config import TELEGRAM_URL
4
  from ai_service import get_ai_response
5
  from database import db_manager
6
+ from schemas import WebhookData
7
 
8
  TELEGRAM_IP = "149.154.167.220"
9
  MAX_TELEGRAM_MESSAGE_LENGTH = 4096
 
24
  return cleaned.strip()
25
 
26
 
 
 
 
 
 
 
 
 
 
 
 
 
 
27
  async def telegram_webhook(data: WebhookData):
28
  try:
29
  if not data.message or not data.message.text:
transfers/__init__.py ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ from .service import (
2
+ cancel_transfer,
3
+ confirm_transfer,
4
+ get_account_balance,
5
+ get_pending_transfer,
6
+ prepare_transfer,
7
+ )
8
+
transfers/mock_bank_accounts.json ADDED
@@ -0,0 +1,68 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "accounts": [
3
+ {
4
+ "telegram_id": 12,
5
+ "account_id": "ACC-00012",
6
+ "serial_id": "1001",
7
+ "name": "Test Sender",
8
+ "balance": 1900.0,
9
+ "currency": "YER"
10
+ },
11
+ {
12
+ "telegram_id": 991001,
13
+ "account_id": "ACC-991001",
14
+ "serial_id": "2001",
15
+ "name": "Ahmed Salem",
16
+ "balance": 1800.0,
17
+ "currency": "YER"
18
+ },
19
+ {
20
+ "telegram_id": 991002,
21
+ "account_id": "ACC-991002",
22
+ "serial_id": "2002",
23
+ "name": "Mona Ali",
24
+ "balance": 800.0,
25
+ "currency": "YER"
26
+ },
27
+ {
28
+ "telegram_id": 991003,
29
+ "account_id": "ACC-991003",
30
+ "serial_id": "2003",
31
+ "name": "Khaled Omar",
32
+ "balance": 300.0,
33
+ "currency": "YER"
34
+ }
35
+ ],
36
+ "transactions": [
37
+ {
38
+ "transaction_id": "TX-20260410193334671954",
39
+ "telegram_id": 12,
40
+ "sender_serial_id": "1001",
41
+ "receiver_serial_id": "2001",
42
+ "receiver_name": "Ahmed Salem",
43
+ "amount": 200.0,
44
+ "currency": "YER",
45
+ "created_at": "2026-04-10T19:33:34.671995"
46
+ },
47
+ {
48
+ "transaction_id": "TX-20260410193344247493",
49
+ "telegram_id": 12,
50
+ "sender_serial_id": "1001",
51
+ "receiver_serial_id": "2001",
52
+ "receiver_name": "Ahmed Salem",
53
+ "amount": 200.0,
54
+ "currency": "YER",
55
+ "created_at": "2026-04-10T19:33:44.247529"
56
+ },
57
+ {
58
+ "transaction_id": "TX-20260410193354495302",
59
+ "telegram_id": 12,
60
+ "sender_serial_id": "1001",
61
+ "receiver_serial_id": "2001",
62
+ "receiver_name": "Ahmed Salem",
63
+ "amount": 200.0,
64
+ "currency": "YER",
65
+ "created_at": "2026-04-10T19:33:54.495350"
66
+ }
67
+ ]
68
+ }
transfers/pending_transfers.json ADDED
@@ -0,0 +1 @@
 
 
1
+ {}
transfers/service.py ADDED
@@ -0,0 +1,257 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ from datetime import datetime
3
+ from pathlib import Path
4
+ from threading import Lock
5
+ from typing import Any, Dict, Optional
6
+
7
+
8
+ DATA_FILE = Path(__file__).with_name("mock_bank_accounts.json")
9
+ PENDING_FILE = Path(__file__).with_name("pending_transfers.json")
10
+ _LOCK = Lock()
11
+
12
+
13
+ def _read_json(path: Path, default: Any) -> Any:
14
+ if not path.exists():
15
+ return default
16
+
17
+ with path.open("r", encoding="utf-8") as file:
18
+ return json.load(file)
19
+
20
+
21
+ def _write_json(path: Path, data: Any) -> None:
22
+ with path.open("w", encoding="utf-8") as file:
23
+ json.dump(data, file, ensure_ascii=True, indent=2)
24
+
25
+
26
+ def _load_bank_data() -> Dict[str, Any]:
27
+ return _read_json(DATA_FILE, {"accounts": [], "transactions": []})
28
+
29
+
30
+ def _save_bank_data(data: Dict[str, Any]) -> None:
31
+ _write_json(DATA_FILE, data)
32
+
33
+
34
+ def _load_pending_transfers() -> Dict[str, Any]:
35
+ return _read_json(PENDING_FILE, {})
36
+
37
+
38
+ def _save_pending_transfers(data: Dict[str, Any]) -> None:
39
+ _write_json(PENDING_FILE, data)
40
+
41
+
42
+ def _find_account_by_telegram_id(accounts: list[Dict[str, Any]], telegram_id: int) -> Optional[Dict[str, Any]]:
43
+ return next((account for account in accounts if account.get("telegram_id") == telegram_id), None)
44
+
45
+
46
+ def _find_account_by_serial_id(accounts: list[Dict[str, Any]], serial_id: str) -> Optional[Dict[str, Any]]:
47
+ normalized_id = str(serial_id).strip()
48
+ return next((account for account in accounts if str(account.get("serial_id")) == normalized_id), None)
49
+
50
+
51
+ def get_sender_account(telegram_id: int) -> Dict[str, Any]:
52
+ with _LOCK:
53
+ data = _load_bank_data()
54
+ sender = _find_account_by_telegram_id(data["accounts"], telegram_id)
55
+ if sender:
56
+ return {
57
+ "success": True,
58
+ "account": sender,
59
+ }
60
+
61
+ return {
62
+ "success": False,
63
+ "message": "You do not have an account in the bank system. Please visit the bank to create an account first.",
64
+ }
65
+
66
+
67
+ def get_account_balance(telegram_id: int) -> Dict[str, Any]:
68
+ sender_result = get_sender_account(telegram_id)
69
+ if not sender_result["success"]:
70
+ return sender_result
71
+
72
+ account = sender_result["account"]
73
+ return {
74
+ "success": True,
75
+ "telegram_id": telegram_id,
76
+ "account_id": account["account_id"],
77
+ "serial_id": account["serial_id"],
78
+ "name": account["name"],
79
+ "balance": account.get("balance", 0.0),
80
+ "currency": account.get("currency", "YER"),
81
+ }
82
+
83
+
84
+ def get_receiver_account_name(receiver_serial_id: str) -> Dict[str, Any]:
85
+ with _LOCK:
86
+ data = _load_bank_data()
87
+ receiver = _find_account_by_serial_id(data["accounts"], receiver_serial_id)
88
+
89
+ if not receiver:
90
+ return {
91
+ "success": False,
92
+ "message": f"No account was found for ID {receiver_serial_id}.",
93
+ }
94
+
95
+ return {
96
+ "success": True,
97
+ "serial_id": receiver["serial_id"],
98
+ "name": receiver["name"],
99
+ "account_id": receiver["account_id"],
100
+ "currency": receiver.get("currency", "YER"),
101
+ }
102
+
103
+
104
+ def prepare_transfer(telegram_id: int, receiver_serial_id: str, amount: Optional[float] = None) -> Dict[str, Any]:
105
+ with _LOCK:
106
+ data = _load_bank_data()
107
+ sender = _find_account_by_telegram_id(data["accounts"], telegram_id)
108
+ if not sender:
109
+ return {
110
+ "success": False,
111
+ "message": "You do not have an account in the bank system. Please visit the bank to create an account first.",
112
+ }
113
+
114
+ receiver = _find_account_by_serial_id(data["accounts"], receiver_serial_id)
115
+ if not receiver:
116
+ return {
117
+ "success": False,
118
+ "message": f"No account was found for ID {receiver_serial_id}.",
119
+ }
120
+
121
+ if receiver["serial_id"] == sender["serial_id"]:
122
+ return {
123
+ "success": False,
124
+ "message": "You cannot transfer money to the same account.",
125
+ }
126
+
127
+ pending_transfers = _load_pending_transfers()
128
+ pending_payload = {
129
+ "telegram_id": telegram_id,
130
+ "sender_name": sender["name"],
131
+ "sender_serial_id": sender["serial_id"],
132
+ "receiver_name": receiver["name"],
133
+ "receiver_serial_id": receiver["serial_id"],
134
+ "receiver_account_id": receiver["account_id"],
135
+ "amount": amount,
136
+ "currency": sender.get("currency", "YER"),
137
+ "created_at": datetime.utcnow().isoformat(),
138
+ }
139
+ pending_transfers[str(telegram_id)] = pending_payload
140
+ _save_pending_transfers(pending_transfers)
141
+
142
+ return {
143
+ "success": True,
144
+ "pending_transfer": pending_payload,
145
+ "message": f"Receiver found: {receiver['name']}. Waiting for user confirmation.",
146
+ }
147
+
148
+
149
+ def get_pending_transfer(telegram_id: int) -> Dict[str, Any]:
150
+ with _LOCK:
151
+ pending_transfers = _load_pending_transfers()
152
+ pending_transfer = pending_transfers.get(str(telegram_id))
153
+
154
+ if not pending_transfer:
155
+ return {
156
+ "success": False,
157
+ "message": "No pending transfer was found for this Telegram user.",
158
+ }
159
+
160
+ return {
161
+ "success": True,
162
+ "pending_transfer": pending_transfer,
163
+ }
164
+
165
+
166
+ def confirm_transfer(telegram_id: int) -> Dict[str, Any]:
167
+ with _LOCK:
168
+ data = _load_bank_data()
169
+ pending_transfers = _load_pending_transfers()
170
+ pending_transfer = pending_transfers.get(str(telegram_id))
171
+
172
+ if not pending_transfer:
173
+ return {
174
+ "success": False,
175
+ "message": "There is no pending transfer to confirm.",
176
+ }
177
+
178
+ sender = _find_account_by_telegram_id(data["accounts"], telegram_id)
179
+ receiver = _find_account_by_serial_id(data["accounts"], pending_transfer["receiver_serial_id"])
180
+
181
+ if not sender or not receiver:
182
+ return {
183
+ "success": False,
184
+ "message": "The sender or receiver account could not be found.",
185
+ }
186
+
187
+ amount = pending_transfer.get("amount")
188
+ if amount is None:
189
+ return {
190
+ "success": False,
191
+ "message": "The transfer amount is missing. Ask the user for the amount before confirming.",
192
+ }
193
+
194
+ try:
195
+ amount_value = float(amount)
196
+ except (TypeError, ValueError):
197
+ return {
198
+ "success": False,
199
+ "message": "The transfer amount is invalid.",
200
+ }
201
+
202
+ if amount_value <= 0:
203
+ return {
204
+ "success": False,
205
+ "message": "The transfer amount must be greater than zero.",
206
+ }
207
+
208
+ if float(sender.get("balance", 0.0)) < amount_value:
209
+ return {
210
+ "success": False,
211
+ "message": f"Insufficient balance. Available balance is {sender.get('balance', 0.0):.2f} {sender.get('currency', 'YER')}.",
212
+ }
213
+
214
+ sender["balance"] = round(float(sender["balance"]) - amount_value, 2)
215
+ receiver["balance"] = round(float(receiver.get("balance", 0.0)) + amount_value, 2)
216
+
217
+ transaction = {
218
+ "transaction_id": f"TX-{datetime.utcnow().strftime('%Y%m%d%H%M%S%f')}",
219
+ "telegram_id": telegram_id,
220
+ "sender_serial_id": sender["serial_id"],
221
+ "receiver_serial_id": receiver["serial_id"],
222
+ "receiver_name": receiver["name"],
223
+ "amount": amount_value,
224
+ "currency": sender.get("currency", "YER"),
225
+ "created_at": datetime.utcnow().isoformat(),
226
+ }
227
+ data.setdefault("transactions", []).append(transaction)
228
+ _save_bank_data(data)
229
+
230
+ pending_transfers.pop(str(telegram_id), None)
231
+ _save_pending_transfers(pending_transfers)
232
+
233
+ return {
234
+ "success": True,
235
+ "transaction": transaction,
236
+ "sender_balance": sender["balance"],
237
+ "message": f"Transfer completed successfully to {receiver['name']}.",
238
+ }
239
+
240
+
241
+ def cancel_transfer(telegram_id: int) -> Dict[str, Any]:
242
+ with _LOCK:
243
+ pending_transfers = _load_pending_transfers()
244
+ removed = pending_transfers.pop(str(telegram_id), None)
245
+ _save_pending_transfers(pending_transfers)
246
+
247
+ if not removed:
248
+ return {
249
+ "success": False,
250
+ "message": "There is no pending transfer to cancel.",
251
+ }
252
+
253
+ return {
254
+ "success": True,
255
+ "message": "The pending transfer has been canceled.",
256
+ }
257
+
utils.py CHANGED
@@ -7,10 +7,14 @@ async def dns_test():
7
  except Exception as e:
8
  return {"error": str(e)}
9
 
10
- async def test_ai_response():
11
  try:
12
  from ai_service import get_ai_response
13
- response = await get_ai_response("عن ماذا كنت اسال", 12)
14
- return {"response": response}
 
 
 
 
15
  except Exception as e:
16
  return {"error": str(e)}
 
7
  except Exception as e:
8
  return {"error": str(e)}
9
 
10
+ async def test_ai_response(message: str, telegram_id: int):
11
  try:
12
  from ai_service import get_ai_response
13
+ response = await get_ai_response(message, telegram_id)
14
+ return {
15
+ "message": message,
16
+ "telegram_id": telegram_id,
17
+ "response": response,
18
+ }
19
  except Exception as e:
20
  return {"error": str(e)}