add logic

This commit is contained in:
o.vodianov 2025-08-08 23:32:05 +04:00
parent c783275c6f
commit 3c87cc575c
9 changed files with 200 additions and 35 deletions

View File

@ -3,6 +3,7 @@ import hashlib
import base64 import base64
import re import re
from datetime import datetime from datetime import datetime
from urllib.parse import urlparse
def UUID_code_generator() -> str: def UUID_code_generator() -> str:
@ -37,3 +38,11 @@ def is_phone_correct(phone: str) -> bool:
regex = r"^(?:\+7|8|7)\d{10}$" regex = r"^(?:\+7|8|7)\d{10}$"
return re.match(regex, normalized) return re.match(regex, normalized)
def is_valid_url(text: str) -> bool:
try:
result = urlparse(text)
return all([result.scheme in ["http", "https"], result.netloc])
except Exception:
return False

View File

@ -168,7 +168,9 @@ class PaymentMethod(Base):
"doctors.id", ondelete="CASCADE"), nullable=False) "doctors.id", ondelete="CASCADE"), nullable=False)
method: Mapped[str] = mapped_column(nullable=False) method: Mapped[str] = mapped_column(nullable=False)
details: Mapped[Optional[str]] = mapped_column(nullable=True) details: Mapped[Optional[str]] = mapped_column(nullable=True)
payment_api_key: Mapped[str] = mapped_column(nullable=False)
is_active: Mapped[bool] = mapped_column(default=True, nullable=False) is_active: Mapped[bool] = mapped_column(default=True, nullable=False)
is_primary: Mapped[bool] = mapped_column(default=True, nullable=False)
created_at: Mapped[datetime] = mapped_column(nullable=False) created_at: Mapped[datetime] = mapped_column(nullable=False)
doctor: Mapped["Doctors"] = relationship(back_populates="payment_methods") doctor: Mapped["Doctors"] = relationship(back_populates="payment_methods")

View File

@ -0,0 +1,124 @@
from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup
from telegram.ext import (
ContextTypes,
ConversationHandler,
CommandHandler,
MessageHandler,
filters,
)
from docbot.handlers.utils.cancel_handler import get_cancel_handler
from docbot.services.doctors_service import add_payment_link
from docbot.handlers.utils.utils import cleanup_session
from core.enums.dialog_helpers import ConfirmationMessage
from core.logging import logger
from core.utils import is_valid_url
ASK_PAYMENT_LINK = 1
ASK_PAYMENT_SYSTEM_API = 2
SAVE_PAYMENT_METHOD = 3
async def send_info(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
user_id = update.effective_user.id
context.user_data['user_id'] = user_id
keyboard_accept = [
[
InlineKeyboardButton(
ConfirmationMessage.PROCEED.value,
callback_data="accepted"
),
InlineKeyboardButton(
ConfirmationMessage.DECLINE.value,
callback_data="declined"
),
]
]
await update.message.reply_text(
text="Вам предстоит последовательно ввести вашу платёжную ссылку и API ключ.\n"
"Эти данные нам необходимы для того, чтобы отслеживать состояние платежей по вашей платёжной ссылке.",
parse_mode="Markdown",
reply_markup=InlineKeyboardMarkup(keyboard_accept),
)
logger.info(f"Show user {user_id} payment configuration information.")
return ASK_PAYMENT_LINK
async def ask_payment_link(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
user_id = context.user_data['user_id']
await update.message.reply_text(
text="Введите адрес вашей платёжной ссылки, пожалуйста."
)
logger.info(f"Ask user {user_id} to enter their payment link.")
return ASK_PAYMENT_SYSTEM_API
async def ask_payment_system_api(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
payment_link = update.message.text.strip()
user_id = context.user_data['user_id']
context.user_data['payment_link'] = payment_link
if is_valid_url(payment_link):
logger.info(f"Payment link {payment_link} provided by user {user_id} is valid.")
await update.message.reply_text(
text="Введите API ключ, который указан у вас личном кабинете плтёжной системы, пожалуйста."
)
return ASK_PAYMENT_SYSTEM_API
else:
logger.info(f"Payment link {payment_link} provided by user {user_id} is invalid.")
await update.message.reply_text(
text="Ссылка на вашу платёжную система не прошла проверку формата."
)
return ASK_PAYMENT_LINK
async def save_payment_method(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
payment_system_api = update.message.text.strip()
user_id = context.user_data['user_id']
await add_payment_link(
telegram_id=user_id,
payment_link=context.user_data['payment_link'],
payment_api_key=payment_system_api
)
await update.message.reply_text(
text="Ваши платёжные данные успешно сохранены!"
)
context.user_data.clear()
return ConversationHandler.END
def add_payment_method() -> CommandHandler:
"""Фабрика для регистрации в Application."""
return CommandHandler("payment_methods", send_info)
def get_add_payment_method_handler() -> ConversationHandler:
return ConversationHandler(
entry_points=[add_payment_method()],
states={
ASK_PAYMENT_LINK: [
MessageHandler(filters.TEXT & ~filters.COMMAND,
ask_payment_link)
],
ASK_PAYMENT_SYSTEM_API: [
MessageHandler(filters.TEXT & ~filters.COMMAND,
ask_payment_system_api)
],
SAVE_PAYMENT_METHOD: [
MessageHandler(filters.TEXT & ~filters.COMMAND,
save_payment_method)
],
},
fallbacks=[get_cancel_handler()],
name="add_payment_method",
persistent=True,
)

View File

@ -163,7 +163,8 @@ async def receive_doctor_consultation_packages_acknowledgement_status(update: Up
await update.message.reply_text( await update.message.reply_text(
ALL_INFORMATION_RECEIVED_TEXT, ALL_INFORMATION_RECEIVED_TEXT,
parse_mode="Markdown" parse_mode="Markdown",
reply_markup=ReplyKeyboardRemove
) )
await create_doctor( await create_doctor(

View File

@ -54,7 +54,6 @@ keyboard = [
async def accept_personal_data_agreement(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int: async def accept_personal_data_agreement(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
keyboard_accept = [ keyboard_accept = [
[ [
InlineKeyboardButton( InlineKeyboardButton(
@ -92,8 +91,6 @@ async def accept_personal_data_agreement(update: Update, context: ContextTypes.D
async def receive_patient_aceptance(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int: async def receive_patient_aceptance(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
# Здесь можно добавить логику обработки согласия пациента
user_data = context.user_data user_data = context.user_data
await update.callback_query.answer() await update.callback_query.answer()

View File

@ -1,8 +1,8 @@
from telegram import Update, ReplyKeyboardMarkup from telegram import Update, ReplyKeyboardMarkup, ReplyKeyboardRemove
from telegram.ext import ContextTypes, CommandHandler from telegram.ext import ContextTypes, CommandHandler
from docbot.services.admins_service import get_admin_info from docbot.services.admins_service import get_admin_info
from docbot.services.doctors_service import get_doctor_info from docbot.services.doctors_service import get_doctor
async def help_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: async def help_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
@ -28,21 +28,15 @@ async def help_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> No
return return
# 2) Проверим, зарегистрирован ли врач # 2) Проверим, зарегистрирован ли врач
if await get_doctor_info(user_id): if await get_doctor(user_id):
text = ( text = (
"👨‍⚕️ *Меню доктора*:\n\n" "👨‍⚕️ *Меню доктора*:\n\n"
"/start Запустить бота\n" "/start Запустить бота\n"
"/sessions Посмотреть список пациентов (или текущих сессий)\n" "/sessions Посмотреть список пациентов (или текущих сессий)\n"
"/markconsulted <CODE> Отметить, что пациент с кодом <CODE> получил консультацию\n"
"/help Показать это меню\n" "/help Показать это меню\n"
) )
# Можно предлагать докторам также специальные кнопки:
keyboard = ReplyKeyboardMarkup( await update.message.reply_text(text, parse_mode="Markdown")
[["/sessions"], ["/markconsulted <CODE>"], ["/help"]],
one_time_keyboard=True,
resize_keyboard=True
)
await update.message.reply_text(text, parse_mode="Markdown", reply_markup=keyboard)
return return
# 3) Иначе — это пациент (незарегистрированный/обычный пользователь) # 3) Иначе — это пациент (незарегистрированный/обычный пользователь)
@ -53,12 +47,8 @@ async def help_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> No
"/send_form Отправить ссылку на заполненную анкету\n" "/send_form Отправить ссылку на заполненную анкету\n"
"/help Показать это меню\n" "/help Показать это меню\n"
) )
keyboard = ReplyKeyboardMarkup(
[["/form", "/consult"], ["/help"]], await update.message.reply_text(text, parse_mode="Markdown")
one_time_keyboard=True,
resize_keyboard=True
)
await update.message.reply_text(text, parse_mode="Markdown", reply_markup=keyboard)
def get_help_handler() -> CommandHandler: def get_help_handler() -> CommandHandler:

View File

@ -0,0 +1,10 @@
from telegram import Update, ReplyKeyboardRemove
from telegram.ext import ContextTypes
async def cleanup_session(update: Update, context: ContextTypes.DEFAULT_TYPE, message: str):
context.user_data.clear()
await update.message.reply_text(
message,
reply_markup=ReplyKeyboardRemove()
)

View File

@ -6,11 +6,13 @@ from docbot.handlers.start_handler import get_start_handler
from docbot.handlers.patients.send_form_handler import get_send_form_handler from docbot.handlers.patients.send_form_handler import get_send_form_handler
from docbot.handlers.patients.consultation_handler import get_consultation_handler from docbot.handlers.patients.consultation_handler import get_consultation_handler
from docbot.handlers.admins.doctors_handler import get_doctors_handler from docbot.handlers.admins.doctors_handler import get_doctors_handler
from docbot.handlers.doctors.register_handler import get_register_doctor_first_stage_handler
from docbot.handlers.admins.generate_ref import get_referral_handlers from docbot.handlers.admins.generate_ref import get_referral_handlers
from docbot.handlers.admins.verify_handler import get_verify_handler
from docbot.handlers.doctors.register_handler import get_register_doctor_first_stage_handler
from docbot.handlers.doctors.add_payments_method import get_add_payment_method_handler
from docbot.handlers.utils.help import get_help_handler from docbot.handlers.utils.help import get_help_handler
from docbot.handlers.utils.unknown import get_unknown_handler from docbot.handlers.utils.unknown import get_unknown_handler
from docbot.handlers.admins.verify_handler import get_verify_handler from docbot.handlers.utils.cancel_handler import get_cancel_handler
def main(): def main():
@ -24,16 +26,19 @@ def main():
.concurrent_updates(concurrent_updates=False) .concurrent_updates(concurrent_updates=False)
.build() .build()
) )
app.add_handler(get_consultation_handler()) app.add_handler(get_start_handler(), group=0)
app.add_handler(get_start_handler()) app.add_handler(get_cancel_handler(), group=0)
app.add_handler(get_send_form_handler()) app.add_handler(get_help_handler(), group=0)
app.add_handler(get_doctors_handler()) app.add_handler(get_consultation_handler(), group=1)
app.add_handler(get_register_doctor_first_stage_handler()) app.add_handler(get_send_form_handler(), group=2)
app.add_handler(get_referral_handlers()) app.add_handler(get_doctors_handler(), group=3)
app.add_handler(get_verify_handler()) app.add_handler(get_register_doctor_first_stage_handler(), group=1)
app.add_handler(get_help_handler()) app.add_handler(get_referral_handlers(), group=3)
app.add_handler(get_unknown_handler()) app.add_handler(get_verify_handler(), group=3)
app.add_handler(get_unknown_handler(), group=0)
app.add_handler(get_add_payment_method_handler(), group=1)
app.add_error_handler(get_start_handler()) app.add_error_handler(get_start_handler())
app.add_handlers
logger.debug("Все хэндлеры зарегистрированы, запускаем polling") logger.debug("Все хэндлеры зарегистрированы, запускаем polling")
app.run_polling() app.run_polling()

View File

@ -5,12 +5,14 @@ from datetime import datetime
from sqlalchemy import select from sqlalchemy import select
from db.session import AsyncSessionLocal from db.session import AsyncSessionLocal
from db.models import Doctors, VerificationRequests, ReferralCode from db.models import (
Doctors, VerificationRequests, ReferralCode, PaymentMethod
)
from core.enums.consultation_types import Consultation from core.enums.consultation_types import Consultation
from core.enums.statuses_helpers import ObjectStatuses from core.enums.statuses_helpers import ObjectStatuses
async def get_doctor_info(telegram_id: int) -> Doctors | None: async def get_doctor(telegram_id: int) -> Doctors | None:
async with AsyncSessionLocal() as session: async with AsyncSessionLocal() as session:
result = await session.execute( result = await session.execute(
select(Doctors) select(Doctors)
@ -28,6 +30,31 @@ async def get_doctors_names() -> Doctors | None:
return result.all() return result.all()
async def is_there_primary_payment_method() -> PaymentMethod | None:
async with AsyncSessionLocal() as session:
result = await session.execute(
select(PaymentMethod.method)
.where(PaymentMethod.is_primary and PaymentMethod.is_active)
)
return result.all()
async def add_payment_link(telegram_id: int, payment_link: str, payment_api_key: str):
async with AsyncSessionLocal() as session:
async with session.begin():
doctor = get_doctor(telegram_id=telegram_id)
session.add(PaymentMethod(
doctor_id=doctor.id,
method=payment_link,
payment_api_key=payment_api_key,
details="",
is_active=True,
is_primary=False if await is_there_primary_payment_method() else True,
created_at=datetime.utcnow()
))
async def add_doctor(telegram_id: int, name: str, available_formats: Consultation, is_active: bool): async def add_doctor(telegram_id: int, name: str, available_formats: Consultation, is_active: bool):
async with AsyncSessionLocal() as session: async with AsyncSessionLocal() as session:
async with session.begin(): async with session.begin():