bug fixes: silent_mode/ cash register indempotence/ antispam for key_country/ ban improvements
This commit is contained in:
+5
-3
@@ -163,7 +163,9 @@ async def get_tracking_source_with_stats(
|
||||
type=source.type,
|
||||
created_by=source.created_by,
|
||||
created_at=source.created_at,
|
||||
registrations=stats.get("registrations", 0),
|
||||
trials=stats.get("trials", 0),
|
||||
payments=stats.get("payments", 0),
|
||||
registrations=(stats["registrations"] if stats else 0),
|
||||
trials=(stats["trials"] if stats else 0),
|
||||
payments=(stats["payments"] if stats else 0),
|
||||
total_amount=(float(stats["total_amount"]) if stats else 0.0),
|
||||
monthly=(stats["monthly"] if stats and "monthly" in stats else []),
|
||||
)
|
||||
|
||||
@@ -94,6 +94,16 @@ class BlockedUserResponse(BaseModel):
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class MonthlyStats(BaseModel):
|
||||
month: str
|
||||
registrations: int
|
||||
trials: int
|
||||
new_purchases_count: int
|
||||
new_purchases_amount: float
|
||||
repeat_purchases_count: int
|
||||
repeat_purchases_amount: float
|
||||
|
||||
|
||||
class TrackingSourceResponse(BaseModel):
|
||||
id: int
|
||||
name: str
|
||||
@@ -105,6 +115,9 @@ class TrackingSourceResponse(BaseModel):
|
||||
registrations: int = 0
|
||||
trials: int = 0
|
||||
payments: int = 0
|
||||
total_amount: float = 0.0
|
||||
|
||||
monthly: list[MonthlyStats] = []
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
from datetime import datetime
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
@@ -12,6 +11,7 @@ class TariffBase(BaseModel):
|
||||
device_limit: int | None = None
|
||||
is_active: bool = True
|
||||
subgroup_title: str | None = None
|
||||
sort_order: int | None = None
|
||||
|
||||
|
||||
class TariffResponse(TariffBase):
|
||||
@@ -32,6 +32,7 @@ class TariffUpdate(BaseModel):
|
||||
device_limit: int | None = None
|
||||
is_active: bool | None = None
|
||||
subgroup_title: str | None = None
|
||||
sort_order: int | None = None
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
@@ -13,6 +13,7 @@ from config import ADMIN_ID, API_TOKEN
|
||||
from filters.private import IsPrivateFilter
|
||||
from logger import logger
|
||||
from utils.modules_loader import load_modules_from_folder
|
||||
from database import async_session_maker
|
||||
|
||||
|
||||
bot = Bot(token=API_TOKEN, default=DefaultBotProperties(parse_mode=ParseMode.HTML))
|
||||
@@ -52,26 +53,28 @@ async def errors_handler(event: ErrorEvent, bot: Bot) -> bool:
|
||||
chat_id=event.update.message.chat.id,
|
||||
user_id=event.update.message.from_user.id,
|
||||
)
|
||||
await start_entry(
|
||||
event=event.update.message,
|
||||
state=fsm_context,
|
||||
session=None,
|
||||
admin=False,
|
||||
captcha=False,
|
||||
)
|
||||
async with async_session_maker() as session:
|
||||
await start_entry(
|
||||
event=event.update.message,
|
||||
state=fsm_context,
|
||||
session=session,
|
||||
admin=False,
|
||||
captcha=False,
|
||||
)
|
||||
elif event.update.callback_query:
|
||||
fsm_context = dp.fsm.get_context(
|
||||
bot=bot,
|
||||
chat_id=event.update.callback_query.message.chat.id,
|
||||
user_id=event.update.callback_query.from_user.id,
|
||||
)
|
||||
await start_entry(
|
||||
event=event.update.callback_query,
|
||||
state=fsm_context,
|
||||
session=None,
|
||||
admin=False,
|
||||
captcha=False,
|
||||
)
|
||||
async with async_session_maker() as session:
|
||||
await start_entry(
|
||||
event=event.update.callback_query,
|
||||
state=fsm_context,
|
||||
session=session,
|
||||
admin=False,
|
||||
captcha=False,
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при показе стартового меню после ошибки: {e}", exc_info=True)
|
||||
|
||||
@@ -93,38 +96,37 @@ async def errors_handler(event: ErrorEvent, bot: Bot) -> bool:
|
||||
caption=f"{hbold(type(event.exception).__name__)}: {str(event.exception)[:1021]}...",
|
||||
)
|
||||
|
||||
from handlers.start import start_entry
|
||||
|
||||
if event.update.message:
|
||||
fsm_context = dp.fsm.get_context(
|
||||
bot=bot,
|
||||
chat_id=event.update.message.chat.id,
|
||||
user_id=event.update.message.from_user.id,
|
||||
)
|
||||
await start_entry(
|
||||
event=event.update.message,
|
||||
state=fsm_context,
|
||||
session=None,
|
||||
admin=False,
|
||||
captcha=False,
|
||||
)
|
||||
async with async_session_maker() as session:
|
||||
await start_entry(
|
||||
event=event.update.message,
|
||||
state=fsm_context,
|
||||
session=session,
|
||||
admin=False,
|
||||
captcha=False,
|
||||
)
|
||||
elif event.update.callback_query:
|
||||
fsm_context = dp.fsm.get_context(
|
||||
bot=bot,
|
||||
chat_id=event.update.callback_query.message.chat.id,
|
||||
user_id=event.update.callback_query.from_user.id,
|
||||
)
|
||||
await start_entry(
|
||||
event=event.update.callback_query,
|
||||
state=fsm_context,
|
||||
session=None,
|
||||
admin=False,
|
||||
captcha=False,
|
||||
)
|
||||
async with async_session_maker() as session:
|
||||
await start_entry(
|
||||
event=event.update.callback_query,
|
||||
state=fsm_context,
|
||||
session=session,
|
||||
admin=False,
|
||||
captcha=False,
|
||||
)
|
||||
|
||||
except TelegramBadRequest as exception:
|
||||
logger.warning(f"Не удалось отправить детали ошибки: {exception}")
|
||||
except Exception as exception:
|
||||
logger.error(f"Неожиданная ошибка в error handler: {exception}")
|
||||
|
||||
return True
|
||||
|
||||
+128
-11
@@ -1,4 +1,4 @@
|
||||
from sqlalchemy import func, insert, not_, select
|
||||
from sqlalchemy import func, insert, not_, select, and_
|
||||
from sqlalchemy.exc import SQLAlchemyError
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
@@ -6,6 +6,8 @@ from database.models import Payment, TrackingSource, User
|
||||
from logger import logger
|
||||
|
||||
|
||||
EXCLUDED_PAYMENT_MARKERS = ["coupon", "referral", "cashback"]
|
||||
|
||||
async def create_tracking_source(session: AsyncSession, name: str, code: str, type_: str, created_by: int):
|
||||
try:
|
||||
stmt = insert(TrackingSource).values(
|
||||
@@ -70,12 +72,18 @@ async def get_all_tracking_sources(session: AsyncSession) -> list[dict]:
|
||||
|
||||
|
||||
async def get_tracking_source_stats(session: AsyncSession, code: str) -> dict | None:
|
||||
source_result = await session.execute(select(TrackingSource.created_at).where(TrackingSource.code == code))
|
||||
created_at_row = source_result.first()
|
||||
if not created_at_row:
|
||||
def _month_key(dt) -> str:
|
||||
return dt.strftime("%Y-%m")
|
||||
|
||||
src_row = await session.execute(
|
||||
select(TrackingSource.name, TrackingSource.code, TrackingSource.created_at)
|
||||
.where(TrackingSource.code == code)
|
||||
)
|
||||
src = src_row.first()
|
||||
if not src:
|
||||
return None
|
||||
|
||||
created_at = created_at_row[0]
|
||||
src_name, src_code, created_at = src
|
||||
|
||||
reg_subq = (
|
||||
select(func.count(func.distinct(User.tg_id)))
|
||||
@@ -95,25 +103,25 @@ async def get_tracking_source_stats(session: AsyncSession, code: str) -> dict |
|
||||
.where(
|
||||
(User.source_code == code)
|
||||
& (Payment.status == "success")
|
||||
& not_(Payment.payment_system.in_(["coupon", "referral", "cashback"]))
|
||||
& not_(Payment.payment_system.in_(EXCLUDED_PAYMENT_MARKERS))
|
||||
& (Payment.created_at >= created_at)
|
||||
)
|
||||
.scalar_subquery()
|
||||
)
|
||||
|
||||
amount_subq = (
|
||||
select(func.coalesce(func.sum(Payment.amount), 0))
|
||||
select(func.coalesce(func.sum(Payment.amount), 0.0))
|
||||
.join(User, Payment.tg_id == User.tg_id)
|
||||
.where(
|
||||
(User.source_code == code)
|
||||
& (Payment.status == "success")
|
||||
& not_(Payment.payment_system.in_(["coupon", "referral", "cashback"]))
|
||||
& not_(Payment.payment_system.in_(EXCLUDED_PAYMENT_MARKERS))
|
||||
& (Payment.created_at >= created_at)
|
||||
)
|
||||
.scalar_subquery()
|
||||
)
|
||||
|
||||
query = select(
|
||||
header_q = select(
|
||||
TrackingSource.name,
|
||||
TrackingSource.code,
|
||||
TrackingSource.created_at,
|
||||
@@ -123,11 +131,119 @@ async def get_tracking_source_stats(session: AsyncSession, code: str) -> dict |
|
||||
amount_subq.label("total_amount"),
|
||||
).where(TrackingSource.code == code)
|
||||
|
||||
result = await session.execute(query)
|
||||
row = result.first()
|
||||
header_res = await session.execute(header_q)
|
||||
row = header_res.first()
|
||||
if not row:
|
||||
return None
|
||||
|
||||
payments_base = (
|
||||
select(
|
||||
Payment.tg_id.label("tg_id"),
|
||||
Payment.amount.label("amount"),
|
||||
Payment.created_at.label("dt"),
|
||||
)
|
||||
.join(User, Payment.tg_id == User.tg_id)
|
||||
.where(
|
||||
(User.source_code == code)
|
||||
& (Payment.status == "success")
|
||||
& not_(Payment.payment_system.in_(EXCLUDED_PAYMENT_MARKERS))
|
||||
& (Payment.created_at >= created_at)
|
||||
)
|
||||
.subquery()
|
||||
)
|
||||
|
||||
first_pay = (
|
||||
select(
|
||||
payments_base.c.tg_id.label("tg_id"),
|
||||
func.min(payments_base.c.dt).label("first_dt"),
|
||||
)
|
||||
.group_by(payments_base.c.tg_id)
|
||||
.subquery()
|
||||
)
|
||||
|
||||
month_expr_new = func.date_trunc("month", payments_base.c.dt).label("month")
|
||||
new_rows = await session.execute(
|
||||
select(
|
||||
month_expr_new,
|
||||
func.count().label("cnt"),
|
||||
func.coalesce(func.sum(payments_base.c.amount), 0.0).label("amt"),
|
||||
)
|
||||
.join(
|
||||
first_pay,
|
||||
and_(
|
||||
payments_base.c.tg_id == first_pay.c.tg_id,
|
||||
payments_base.c.dt == first_pay.c.first_dt,
|
||||
),
|
||||
)
|
||||
.group_by(month_expr_new)
|
||||
.order_by(month_expr_new)
|
||||
)
|
||||
new_by_month = {r.month: (int(r.cnt), float(r.amt)) for r in new_rows.all()}
|
||||
|
||||
month_expr_rep = func.date_trunc("month", payments_base.c.dt).label("month")
|
||||
repeat_rows = await session.execute(
|
||||
select(
|
||||
month_expr_rep,
|
||||
func.count().label("cnt"),
|
||||
func.coalesce(func.sum(payments_base.c.amount), 0.0).label("amt"),
|
||||
)
|
||||
.join(first_pay, payments_base.c.tg_id == first_pay.c.tg_id)
|
||||
.where(payments_base.c.dt > first_pay.c.first_dt)
|
||||
.group_by(month_expr_rep)
|
||||
.order_by(month_expr_rep)
|
||||
)
|
||||
repeat_by_month = {r.month: (int(r.cnt), float(r.amt)) for r in repeat_rows.all()}
|
||||
|
||||
month_expr_regs = func.date_trunc("month", User.created_at).label("month")
|
||||
regs_rows = await session.execute(
|
||||
select(
|
||||
month_expr_regs,
|
||||
func.count(func.distinct(User.tg_id)).label("cnt"),
|
||||
)
|
||||
.where((User.source_code == code) & (User.created_at >= created_at))
|
||||
.group_by(month_expr_regs)
|
||||
.order_by(month_expr_regs)
|
||||
)
|
||||
regs_by_month = {r.month: int(r.cnt) for r in regs_rows.all()}
|
||||
|
||||
month_expr_trials = func.date_trunc("month", User.created_at).label("month")
|
||||
trials_rows = await session.execute(
|
||||
select(
|
||||
month_expr_trials,
|
||||
func.count(func.distinct(User.tg_id)).label("cnt"),
|
||||
)
|
||||
.where(
|
||||
(User.source_code == code)
|
||||
& (User.trial == 1)
|
||||
& (User.created_at >= created_at)
|
||||
)
|
||||
.group_by(month_expr_trials)
|
||||
.order_by(month_expr_trials)
|
||||
)
|
||||
trials_by_month = {r.month: int(r.cnt) for r in trials_rows.all()}
|
||||
|
||||
months = set()
|
||||
months.update(regs_by_month.keys())
|
||||
months.update(trials_by_month.keys())
|
||||
months.update(new_by_month.keys())
|
||||
months.update(repeat_by_month.keys())
|
||||
|
||||
monthly = []
|
||||
for m in sorted(months):
|
||||
regs = regs_by_month.get(m, 0)
|
||||
trls = trials_by_month.get(m, 0)
|
||||
new_cnt, new_amt = new_by_month.get(m, (0, 0.0))
|
||||
rep_cnt, rep_amt = repeat_by_month.get(m, (0, 0.0))
|
||||
monthly.append({
|
||||
"month": _month_key(m),
|
||||
"registrations": regs,
|
||||
"trials": trls,
|
||||
"new_purchases_count": new_cnt,
|
||||
"new_purchases_amount": new_amt,
|
||||
"repeat_purchases_count": rep_cnt,
|
||||
"repeat_purchases_amount": rep_amt,
|
||||
})
|
||||
|
||||
return {
|
||||
"name": row.name,
|
||||
"code": row.code,
|
||||
@@ -136,4 +252,5 @@ async def get_tracking_source_stats(session: AsyncSession, code: str) -> dict |
|
||||
"trials": row.trials or 0,
|
||||
"payments": row.payments or 0,
|
||||
"total_amount": float(row.total_amount or 0),
|
||||
"monthly": monthly,
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ from aiogram.types import CallbackQuery, InlineKeyboardButton, InlineKeyboardMar
|
||||
from sqlalchemy import distinct, func, select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from database.models import Key, Payment, Server, Tariff, User
|
||||
from database.models import Key, Payment, Server, Tariff, User, BlockedUser, ManualBan
|
||||
from filters.admin import IsAdminFilter
|
||||
from logger import logger
|
||||
|
||||
@@ -234,9 +234,17 @@ async def handle_send_confirm(callback_query: CallbackQuery, state: FSMContext,
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка восстановления клавиатуры: {e}")
|
||||
|
||||
banned_tg_ids = (
|
||||
select(BlockedUser.tg_id).union_all(
|
||||
select(ManualBan.tg_id).where(
|
||||
(ManualBan.until.is_(None)) | (ManualBan.until > datetime.utcnow())
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
query = None
|
||||
if send_to == "subscribed":
|
||||
query = select(distinct(User.tg_id)).join(Key).where(Key.expiry_time > now_ms)
|
||||
query = select(distinct(User.tg_id)).join(Key).where(Key.expiry_time > now_ms).where(~User.tg_id.in_(banned_tg_ids))
|
||||
elif send_to == "unsubscribed":
|
||||
subquery = (
|
||||
select(User.tg_id)
|
||||
@@ -250,16 +258,17 @@ async def handle_send_confirm(callback_query: CallbackQuery, state: FSMContext,
|
||||
.having(func.max(Key.expiry_time) <= now_ms)
|
||||
)
|
||||
)
|
||||
query = select(distinct(subquery.c.tg_id))
|
||||
query = select(distinct(subquery.c.tg_id)).where(~subquery.c.tg_id.in_(banned_tg_ids))
|
||||
elif send_to == "untrial":
|
||||
subquery = select(Key.tg_id)
|
||||
query = select(distinct(User.tg_id)).where(~User.tg_id.in_(subquery) & User.trial.in_([0, -1]))
|
||||
query = select(distinct(User.tg_id)).where(~User.tg_id.in_(subquery) & User.trial.in_([0, -1])).where(~User.tg_id.in_(banned_tg_ids))
|
||||
elif send_to == "cluster":
|
||||
query = (
|
||||
select(distinct(User.tg_id))
|
||||
.join(Key, User.tg_id == Key.tg_id)
|
||||
.join(Server, Key.server_id == Server.cluster_name)
|
||||
.where(Server.cluster_name == cluster_name)
|
||||
.where(~User.tg_id.in_(banned_tg_ids))
|
||||
)
|
||||
elif send_to == "hotleads":
|
||||
subquery = select(Key.tg_id)
|
||||
@@ -268,6 +277,7 @@ async def handle_send_confirm(callback_query: CallbackQuery, state: FSMContext,
|
||||
.join(Payment, User.tg_id == Payment.tg_id)
|
||||
.where(Payment.status == "success")
|
||||
.where(~User.tg_id.in_(subquery))
|
||||
.where(~User.tg_id.in_(banned_tg_ids))
|
||||
)
|
||||
elif send_to == "trial":
|
||||
trial_tariff_subquery = select(Tariff.id).where(Tariff.group_code == "trial")
|
||||
@@ -275,9 +285,10 @@ async def handle_send_confirm(callback_query: CallbackQuery, state: FSMContext,
|
||||
query = (
|
||||
select(distinct(Key.tg_id))
|
||||
.where(Key.tariff_id.in_(trial_tariff_subquery))
|
||||
.where(~Key.tg_id.in_(banned_tg_ids))
|
||||
)
|
||||
else:
|
||||
query = select(distinct(User.tg_id))
|
||||
query = select(distinct(User.tg_id)).where(~User.tg_id.in_(banned_tg_ids))
|
||||
|
||||
result = await session.execute(query)
|
||||
tg_ids = [row[0] for row in result.all()]
|
||||
|
||||
@@ -203,7 +203,19 @@ async def handle_stats(callback_query: CallbackQuery, session: AsyncSession):
|
||||
if extra_blocks:
|
||||
stats_message += "\n\n" + "\n\n".join([str(b) for b in extra_blocks if b])
|
||||
|
||||
await callback_query.message.edit_text(text=stats_message, reply_markup=build_stats_kb())
|
||||
new_kb = build_stats_kb()
|
||||
current_text = callback_query.message.html_text or callback_query.message.text or ""
|
||||
cur_kb = callback_query.message.reply_markup
|
||||
cur_kb_json = cur_kb.model_dump_json() if cur_kb else None
|
||||
new_kb_json = new_kb.model_dump_json() if new_kb else None
|
||||
|
||||
if current_text == stats_message and cur_kb_json == new_kb_json:
|
||||
try:
|
||||
await callback_query.answer()
|
||||
except Exception:
|
||||
pass
|
||||
else:
|
||||
await callback_query.message.edit_text(text=stats_message, reply_markup=new_kb)
|
||||
|
||||
except TelegramBadRequest as e:
|
||||
if "message is not modified" not in str(e):
|
||||
|
||||
@@ -245,12 +245,6 @@ async def change_location_callback(callback_query: CallbackQuery, session: Any):
|
||||
|
||||
@router.callback_query(F.data.startswith("select_country|"))
|
||||
async def handle_country_selection(callback_query: CallbackQuery, session: Any, state: FSMContext):
|
||||
"""
|
||||
Обрабатывает выбор страны.
|
||||
Формат callback data:
|
||||
select_country|{selected_country}|{ts} [|{old_key_name} (опционально)]
|
||||
Если передан old_key_name – значит, происходит смена локации.
|
||||
"""
|
||||
data = callback_query.data.split("|")
|
||||
if len(data) < 3:
|
||||
await callback_query.message.answer("❌ Некорректные данные. Попробуйте снова.")
|
||||
@@ -263,23 +257,41 @@ async def handle_country_selection(callback_query: CallbackQuery, session: Any,
|
||||
await callback_query.message.answer("❌ Некорректное время истечения. Попробуйте снова.")
|
||||
return
|
||||
|
||||
expiry_time = datetime.fromtimestamp(ts, tz=moscow_tz)
|
||||
|
||||
old_key_name = data[3] if len(data) > 3 else None
|
||||
|
||||
tg_id = callback_query.from_user.id
|
||||
logger.info(f"Пользователь {tg_id} выбрал страну: {selected_country}")
|
||||
logger.info(f"Получено время истечения (timestamp): {ts}")
|
||||
|
||||
await finalize_key_creation(
|
||||
tg_id,
|
||||
expiry_time,
|
||||
selected_country,
|
||||
state,
|
||||
session,
|
||||
callback_query,
|
||||
old_key_name,
|
||||
)
|
||||
fsm_data = await state.get_data()
|
||||
if fsm_data.get("creating_key"):
|
||||
try:
|
||||
await callback_query.answer("⏳ Уже обрабатываю…")
|
||||
except Exception:
|
||||
pass
|
||||
return
|
||||
|
||||
await state.update_data(creating_key=True)
|
||||
|
||||
try:
|
||||
await callback_query.answer("Обрабатываю…")
|
||||
if callback_query.message:
|
||||
await callback_query.message.edit_reply_markup(reply_markup=None)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
try:
|
||||
expiry_time = datetime.fromtimestamp(ts, tz=moscow_tz)
|
||||
await finalize_key_creation(
|
||||
tg_id,
|
||||
expiry_time,
|
||||
selected_country,
|
||||
state,
|
||||
session,
|
||||
callback_query,
|
||||
old_key_name,
|
||||
)
|
||||
finally:
|
||||
fsm_data = await state.get_data()
|
||||
if fsm_data.get("creating_key"):
|
||||
await state.update_data(creating_key=False)
|
||||
|
||||
|
||||
async def finalize_key_creation(
|
||||
|
||||
@@ -338,7 +338,11 @@ async def select_tariff_plan(callback_query: CallbackQuery, session: Any, state:
|
||||
|
||||
tariff = await get_tariff_by_id(session, tariff_id)
|
||||
if not tariff:
|
||||
await callback_query.message.edit_text("❌ Указанный тариф не найден.")
|
||||
await edit_or_send_message(
|
||||
target_message=callback_query.message,
|
||||
text="❌ Указанный тариф не найден.",
|
||||
)
|
||||
await callback_query.answer()
|
||||
return
|
||||
|
||||
discount_info = await check_hot_lead_discount(session, tg_id)
|
||||
@@ -346,18 +350,18 @@ async def select_tariff_plan(callback_query: CallbackQuery, session: Any, state:
|
||||
if not discount_info.get("available") or datetime.utcnow() >= discount_info["expires_at"]:
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(InlineKeyboardButton(text=MAIN_MENU, callback_data="profile"))
|
||||
|
||||
await callback_query.message.edit_text(
|
||||
"❌ Скидка недоступна или истекла. Пожалуйста, выберите тариф заново.",
|
||||
reply_markup=builder.as_markup()
|
||||
await edit_or_send_message(
|
||||
target_message=callback_query.message,
|
||||
text="❌ Скидка недоступна или истекла. Пожалуйста, выберите тариф заново.",
|
||||
reply_markup=builder.as_markup(),
|
||||
)
|
||||
await callback_query.answer()
|
||||
return
|
||||
|
||||
duration_days = tariff["duration_days"]
|
||||
price_rub = tariff["price_rub"]
|
||||
|
||||
balance = await get_balance(session, tg_id)
|
||||
price_rub = tariff["price_rub"]
|
||||
|
||||
if balance < price_rub:
|
||||
required_amount = ceil(price_rub - balance)
|
||||
@@ -374,7 +378,7 @@ async def select_tariff_plan(callback_query: CallbackQuery, session: Any, state:
|
||||
|
||||
module_fast_flow_handlers = load_module_fast_flow_handlers()
|
||||
flow_handled = False
|
||||
|
||||
|
||||
if USE_NEW_PAYMENT_FLOW in module_fast_flow_handlers:
|
||||
try:
|
||||
handler = module_fast_flow_handlers[USE_NEW_PAYMENT_FLOW]
|
||||
@@ -413,6 +417,7 @@ async def select_tariff_plan(callback_query: CallbackQuery, session: Any, state:
|
||||
text=CREATING_CONNECTION_MSG,
|
||||
reply_markup=builder.as_markup(),
|
||||
)
|
||||
await callback_query.answer()
|
||||
|
||||
expiry_time = datetime.now(moscow_tz) + timedelta(days=duration_days)
|
||||
await state.update_data(tariff_id=tariff_id)
|
||||
|
||||
Binary file not shown.
@@ -49,7 +49,10 @@ async def process_callback_pay_kassai(callback_query: types.CallbackQuery, state
|
||||
if method_name:
|
||||
method = next((m for m in KASSAI_PAYMENT_METHODS if m["name"] == method_name and m["enable"]), None)
|
||||
if not method:
|
||||
await callback_query.message.delete()
|
||||
try:
|
||||
await callback_query.message.delete()
|
||||
except Exception:
|
||||
pass
|
||||
await callback_query.message.answer(
|
||||
target_message=callback_query.message,
|
||||
text="Ошибка: выбранный способ оплаты недоступен.",
|
||||
@@ -80,7 +83,10 @@ async def process_callback_pay_kassai(callback_query: types.CallbackQuery, state
|
||||
builder.row(InlineKeyboardButton(text="Ввести сумму", callback_data=f"kassai_custom_amount|{method_name}"))
|
||||
builder.row(InlineKeyboardButton(text=BACK, callback_data="balance"))
|
||||
|
||||
await callback_query.message.delete()
|
||||
try:
|
||||
await callback_query.message.delete()
|
||||
except Exception:
|
||||
pass
|
||||
new_msg = await callback_query.message.answer(
|
||||
target_message=callback_query.message,
|
||||
text=method["desc"],
|
||||
@@ -101,7 +107,10 @@ async def process_callback_pay_kassai(callback_query: types.CallbackQuery, state
|
||||
builder.row(InlineKeyboardButton(text=method["button"], callback_data=f'kassai_method|{method["name"]}'))
|
||||
builder.row(InlineKeyboardButton(text=BACK, callback_data="balance"))
|
||||
|
||||
await callback_query.message.delete()
|
||||
try:
|
||||
await callback_query.message.delete()
|
||||
except Exception:
|
||||
pass
|
||||
new_msg = await callback_query.message.answer(
|
||||
target_message=callback_query.message,
|
||||
text="Выберите способ оплаты через KassaAI:",
|
||||
|
||||
@@ -255,7 +255,7 @@ async def robokassa_webhook(request: web.Request):
|
||||
logger.info(f"Processing payment for user {tg_id} with amount {amount}.")
|
||||
|
||||
async with async_session_maker() as session:
|
||||
recent_time = datetime.now(MOSCOW_TZ).replace(tzinfo=None) - timedelta(minutes=1)
|
||||
recent_time = datetime.now(MOSCOW_TZ).replace(tzinfo=None) - timedelta(seconds=10)
|
||||
|
||||
result = await session.execute(
|
||||
select(Payment).where(
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -4,6 +4,7 @@ from typing import Any
|
||||
from aiogram import BaseMiddleware
|
||||
from aiogram.types import Message, Update
|
||||
from sqlalchemy import select
|
||||
from datetime import datetime
|
||||
|
||||
from config import DISABLE_DIRECT_START
|
||||
from database import async_session_maker, check_user_exists
|
||||
@@ -60,9 +61,15 @@ class DirectStartBlockerMiddleware(BaseMiddleware):
|
||||
|
||||
elif start_param.startswith("gift_"):
|
||||
gift_id = start_param.removeprefix("gift_")
|
||||
result = await session.execute(select(Gift).where(Gift.id == gift_id))
|
||||
result = await session.execute(
|
||||
select(Gift).where(
|
||||
Gift.gift_id == gift_id,
|
||||
Gift.is_used.is_(False),
|
||||
(Gift.expiry_time.is_(None)) | (Gift.expiry_time > datetime.utcnow()),
|
||||
)
|
||||
)
|
||||
if not result.scalar_one_or_none():
|
||||
logger.info(f"[DirectStartBlocker] Подарок не найден: {gift_id!r}")
|
||||
logger.info(f"[DirectStartBlocker] Подарок неактивен или не найден: {gift_id!r}")
|
||||
return
|
||||
|
||||
elif start_param.startswith("referral_"):
|
||||
@@ -92,4 +99,4 @@ class DirectStartBlockerMiddleware(BaseMiddleware):
|
||||
)
|
||||
return
|
||||
|
||||
return await handler(event, data)
|
||||
return
|
||||
|
||||
Reference in New Issue
Block a user