From 3c87cc575c4b49c9d7c1347525cf69634989d47e Mon Sep 17 00:00:00 2001 From: "o.vodianov" Date: Fri, 8 Aug 2025 23:32:05 +0400 Subject: [PATCH] add logic --- src/core/utils.py | 9 ++ src/db/models.py | 2 + .../handlers/doctors/add_payments_method.py | 124 ++++++++++++++++++ .../handlers/doctors/register_handler.py | 3 +- .../handlers/patients/consultation_handler.py | 3 - src/docbot/handlers/utils/help.py | 26 ++-- src/docbot/handlers/utils/utils.py | 10 ++ src/docbot/main.py | 27 ++-- src/docbot/services/doctors_service.py | 31 ++++- 9 files changed, 200 insertions(+), 35 deletions(-) create mode 100644 src/docbot/handlers/doctors/add_payments_method.py create mode 100644 src/docbot/handlers/utils/utils.py diff --git a/src/core/utils.py b/src/core/utils.py index ec861a0..61cbd1a 100644 --- a/src/core/utils.py +++ b/src/core/utils.py @@ -3,6 +3,7 @@ import hashlib import base64 import re from datetime import datetime +from urllib.parse import urlparse def UUID_code_generator() -> str: @@ -37,3 +38,11 @@ def is_phone_correct(phone: str) -> bool: regex = r"^(?:\+7|8|7)\d{10}$" 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 diff --git a/src/db/models.py b/src/db/models.py index c4ddb80..b8ec6c0 100644 --- a/src/db/models.py +++ b/src/db/models.py @@ -168,7 +168,9 @@ class PaymentMethod(Base): "doctors.id", ondelete="CASCADE"), nullable=False) method: Mapped[str] = mapped_column(nullable=False) 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_primary: Mapped[bool] = mapped_column(default=True, nullable=False) created_at: Mapped[datetime] = mapped_column(nullable=False) doctor: Mapped["Doctors"] = relationship(back_populates="payment_methods") diff --git a/src/docbot/handlers/doctors/add_payments_method.py b/src/docbot/handlers/doctors/add_payments_method.py new file mode 100644 index 0000000..d422caf --- /dev/null +++ b/src/docbot/handlers/doctors/add_payments_method.py @@ -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, + ) diff --git a/src/docbot/handlers/doctors/register_handler.py b/src/docbot/handlers/doctors/register_handler.py index b0db516..906a5f6 100644 --- a/src/docbot/handlers/doctors/register_handler.py +++ b/src/docbot/handlers/doctors/register_handler.py @@ -163,7 +163,8 @@ async def receive_doctor_consultation_packages_acknowledgement_status(update: Up await update.message.reply_text( ALL_INFORMATION_RECEIVED_TEXT, - parse_mode="Markdown" + parse_mode="Markdown", + reply_markup=ReplyKeyboardRemove ) await create_doctor( diff --git a/src/docbot/handlers/patients/consultation_handler.py b/src/docbot/handlers/patients/consultation_handler.py index 9ddf54e..df6c2a3 100644 --- a/src/docbot/handlers/patients/consultation_handler.py +++ b/src/docbot/handlers/patients/consultation_handler.py @@ -54,7 +54,6 @@ keyboard = [ async def accept_personal_data_agreement(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int: - keyboard_accept = [ [ 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: - # Здесь можно добавить логику обработки согласия пациента - user_data = context.user_data await update.callback_query.answer() diff --git a/src/docbot/handlers/utils/help.py b/src/docbot/handlers/utils/help.py index 322e186..bd51843 100644 --- a/src/docbot/handlers/utils/help.py +++ b/src/docbot/handlers/utils/help.py @@ -1,8 +1,8 @@ -from telegram import Update, ReplyKeyboardMarkup +from telegram import Update, ReplyKeyboardMarkup, ReplyKeyboardRemove from telegram.ext import ContextTypes, CommandHandler 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: @@ -28,21 +28,15 @@ async def help_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> No return # 2) Проверим, зарегистрирован ли врач - if await get_doctor_info(user_id): + if await get_doctor(user_id): text = ( "👨‍⚕️ *Меню доктора*:\n\n" "/start ‒ Запустить бота\n" "/sessions ‒ Посмотреть список пациентов (или текущих сессий)\n" - "/markconsulted ‒ Отметить, что пациент с кодом получил консультацию\n" "/help ‒ Показать это меню\n" ) - # Можно предлагать докторам также специальные кнопки: - keyboard = ReplyKeyboardMarkup( - [["/sessions"], ["/markconsulted "], ["/help"]], - one_time_keyboard=True, - resize_keyboard=True - ) - await update.message.reply_text(text, parse_mode="Markdown", reply_markup=keyboard) + + await update.message.reply_text(text, parse_mode="Markdown") return # 3) Иначе — это пациент (незарегистрированный/обычный пользователь) @@ -53,13 +47,9 @@ async def help_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> No "/send_form ‒ Отправить ссылку на заполненную анкету\n" "/help ‒ Показать это меню\n" ) - keyboard = ReplyKeyboardMarkup( - [["/form", "/consult"], ["/help"]], - one_time_keyboard=True, - resize_keyboard=True - ) - await update.message.reply_text(text, parse_mode="Markdown", reply_markup=keyboard) - + + await update.message.reply_text(text, parse_mode="Markdown") + def get_help_handler() -> CommandHandler: """ diff --git a/src/docbot/handlers/utils/utils.py b/src/docbot/handlers/utils/utils.py new file mode 100644 index 0000000..8d996f8 --- /dev/null +++ b/src/docbot/handlers/utils/utils.py @@ -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() + ) diff --git a/src/docbot/main.py b/src/docbot/main.py index 04dae5b..d544fc1 100644 --- a/src/docbot/main.py +++ b/src/docbot/main.py @@ -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.consultation_handler import get_consultation_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.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.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(): @@ -24,16 +26,19 @@ def main(): .concurrent_updates(concurrent_updates=False) .build() ) - app.add_handler(get_consultation_handler()) - app.add_handler(get_start_handler()) - app.add_handler(get_send_form_handler()) - app.add_handler(get_doctors_handler()) - app.add_handler(get_register_doctor_first_stage_handler()) - app.add_handler(get_referral_handlers()) - app.add_handler(get_verify_handler()) - app.add_handler(get_help_handler()) - app.add_handler(get_unknown_handler()) + app.add_handler(get_start_handler(), group=0) + app.add_handler(get_cancel_handler(), group=0) + app.add_handler(get_help_handler(), group=0) + app.add_handler(get_consultation_handler(), group=1) + app.add_handler(get_send_form_handler(), group=2) + app.add_handler(get_doctors_handler(), group=3) + app.add_handler(get_register_doctor_first_stage_handler(), group=1) + app.add_handler(get_referral_handlers(), group=3) + 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_handlers logger.debug("Все хэндлеры зарегистрированы, запускаем polling") app.run_polling() diff --git a/src/docbot/services/doctors_service.py b/src/docbot/services/doctors_service.py index 6943557..2e2afab 100644 --- a/src/docbot/services/doctors_service.py +++ b/src/docbot/services/doctors_service.py @@ -5,12 +5,14 @@ from datetime import datetime from sqlalchemy import select 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.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: result = await session.execute( select(Doctors) @@ -28,6 +30,31 @@ async def get_doctors_names() -> Doctors | None: 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 with AsyncSessionLocal() as session: async with session.begin():