feat(referral): add referral system with withdrawals

This commit is contained in:
2026-07-18 10:58:14 +05:00
parent 5e5bab91f2
commit a04734b8a7
17 changed files with 1750 additions and 60 deletions
+27 -1
View File
@@ -6,7 +6,29 @@ from .db_purchases import PurchaseModel, Purchasesx
from .db_refill import RefillModel, Refillx from .db_refill import RefillModel, Refillx
from .db_settings import SettingsModel, Settingsx from .db_settings import SettingsModel, Settingsx
from .db_users import UserModel, UsersRepository, Userx from .db_users import UserModel, UsersRepository, Userx
from .entities import Category, Item, Payments, Position, Purchase, Refill, Settings, User
from .db_referral import (
ReferralModel,
ReferralTransactionModel,
ReferralWithdrawalModel,
Referralx,
ReferralTransactionx,
ReferralWithdrawalx,
)
from .entities import (
Category,
Item,
Payments,
Position,
Purchase,
Refill,
Settings,
User,
Referral,
ReferralTransaction,
ReferralWithdrawal,
)
ModelCategory = Category ModelCategory = Category
ModelItem = Item ModelItem = Item
@@ -18,3 +40,7 @@ ModelSettings = Settings
ModelUser = User ModelUser = User
ModelUsers = User ModelUsers = User
SettingsRepository = Settingsx SettingsRepository = Settingsx
ModelReferral = Referral
ModelReferralTransaction = ReferralTransaction
ModelReferralWithdrawal = ReferralWithdrawal
+728
View File
@@ -0,0 +1,728 @@
# - *- coding: utf- 8 - *-
from math import isfinite
import re
from urllib.parse import urlparse
from uuid import uuid4
from sqlalchemy import BigInteger, Float, Integer, String, UniqueConstraint, text
from sqlalchemy.orm import Mapped, mapped_column
from sqlalchemy.ext.asyncio import AsyncConnection
from typing import Any, Dict, Optional, Tuple, Union
from tgbot.database.core import Base, session_factory
from tgbot.database.entities import Referral, ReferralTransaction, ReferralWithdrawal
from tgbot.database.repository import BaseRepository
from tgbot.utils.const_functions import get_unix
def is_valid_referral_withdrawal_recipient(
withdrawal_method: str, recipient: str
) -> bool:
if withdrawal_method == "Cryptobot":
return bool(re.fullmatch(r"@[A-Za-z][A-Za-z0-9_]{4,31}", recipient))
if withdrawal_method == "Lolzteam":
parsed_url = urlparse(recipient)
return parsed_url.scheme in ("http", "https") and parsed_url.hostname in (
"zelenka.guru",
"lolz.team",
"lolz.live",
"lolz.guru",
)
return False
async def ensure_referrals_schema(conn: AsyncConnection) -> None:
user_columns_result = await conn.exec_driver_sql("PRAGMA table_info(storage_users)")
user_columns = {column[1] for column in user_columns_result.fetchall()}
# Обновление полей пользователя
if user_columns:
if "user_referrer_id" not in user_columns:
await conn.exec_driver_sql(
"ALTER TABLE storage_users ADD COLUMN user_referrer_id BIGINT"
)
if "user_referral_balance" not in user_columns:
await conn.exec_driver_sql(
"ALTER TABLE storage_users "
"ADD COLUMN user_referral_balance FLOAT NOT NULL DEFAULT 0"
)
if "user_referral_hold" not in user_columns:
await conn.exec_driver_sql(
"ALTER TABLE storage_users "
"ADD COLUMN user_referral_hold FLOAT NOT NULL DEFAULT 0"
)
await conn.exec_driver_sql(
"CREATE INDEX IF NOT EXISTS ix_storage_users_user_referrer_id "
"ON storage_users (user_referrer_id)"
)
settings_columns_result = await conn.exec_driver_sql(
"PRAGMA table_info(storage_settings)"
)
settings_columns = {column[1] for column in settings_columns_result.fetchall()}
# Обновление полей настроек
if settings_columns:
if "status_referral" not in settings_columns:
await conn.exec_driver_sql(
"ALTER TABLE storage_settings "
"ADD COLUMN status_referral VARCHAR(16) NOT NULL DEFAULT 'False'"
)
if "referral_bonus_rub" not in settings_columns:
await conn.exec_driver_sql(
"ALTER TABLE storage_settings "
"ADD COLUMN referral_bonus_rub FLOAT NOT NULL DEFAULT 0"
)
if "referral_refill_percent" not in settings_columns:
await conn.exec_driver_sql(
"ALTER TABLE storage_settings "
"ADD COLUMN referral_refill_percent FLOAT NOT NULL DEFAULT 0"
)
class ReferralModel(Base):
__tablename__ = "storage_referrals"
increment: Mapped[int] = mapped_column(
Integer, primary_key=True, autoincrement=True
)
referrer_id: Mapped[int] = mapped_column(BigInteger, nullable=False, index=True)
referral_id: Mapped[int] = mapped_column(
BigInteger, nullable=False, unique=True, index=True
)
referral_unix: Mapped[int] = mapped_column(
Integer, nullable=False, default=get_unix
)
class ReferralTransactionModel(Base):
__tablename__ = "storage_referral_transactions"
__table_args__ = (
UniqueConstraint(
"user_id",
"transaction_type",
"source_id",
name="uq_referral_transaction_source",
),
)
increment: Mapped[int] = mapped_column(
Integer, primary_key=True, autoincrement=True
)
user_id: Mapped[int] = mapped_column(BigInteger, nullable=False, index=True)
related_user_id: Mapped[Optional[int]] = mapped_column(BigInteger, nullable=True)
transaction_type: Mapped[str] = mapped_column(String(64), nullable=False)
source_id: Mapped[str] = mapped_column(String(128), nullable=False)
amount: Mapped[float] = mapped_column(Float, nullable=False, default=0)
transaction_unix: Mapped[int] = mapped_column(
Integer, nullable=False, default=get_unix
)
class ReferralWithdrawalModel(Base):
__tablename__ = "storage_referral_withdrawals"
increment: Mapped[int] = mapped_column(
Integer, primary_key=True, autoincrement=True
)
user_id: Mapped[int] = mapped_column(BigInteger, nullable=False, index=True)
withdrawal_amount: Mapped[float] = mapped_column(Float, nullable=False, default=0)
withdrawal_method: Mapped[str] = mapped_column(String(32), nullable=False)
withdrawal_recipient: Mapped[str] = mapped_column(String(255), nullable=False)
withdrawal_status: Mapped[str] = mapped_column(
String(32), nullable=False, default="pending"
)
withdrawal_unix: Mapped[int] = mapped_column(
Integer, nullable=False, default=get_unix
)
processed_unix: Mapped[Optional[int]] = mapped_column(Integer, nullable=True)
processed_admin_id: Mapped[Optional[int]] = mapped_column(BigInteger, nullable=True)
class Referralx(BaseRepository[ReferralModel, Referral]):
def __init__(self):
super().__init__()
self.table_model = ReferralModel
self.entity_model = Referral
self.storage_name = ReferralModel.__tablename__
async def add(self, referrer_id: int, referral_id: int) -> Referral:
return await self._insert(
referrer_id=referrer_id,
referral_id=referral_id,
referral_unix=get_unix(),
)
async def register_referral(self, referral_id: int, referrer_id: int) -> str:
if referral_id == referrer_id:
return "SELF_REFERRAL"
async with session_factory() as session:
try:
await session.execute(text("BEGIN IMMEDIATE"))
settings_result = await session.execute(
text(
"SELECT status_referral, referral_bonus_rub "
"FROM storage_settings WHERE id = 1"
)
)
settings = settings_result.mappings().first()
if settings is None or settings["status_referral"] != "True":
await session.rollback()
return "DISABLED"
referrer_result = await session.execute(
text(
"SELECT user_referral_balance FROM storage_users "
"WHERE user_id = :user_id"
),
{"user_id": referrer_id},
)
referrer = referrer_result.mappings().first()
if referrer is None:
await session.rollback()
return "REFERRER_NOT_FOUND"
referral_result = await session.execute(
text(
"SELECT user_balance, user_referrer_id "
"FROM storage_users WHERE user_id = :user_id"
),
{"user_id": referral_id},
)
referral = referral_result.mappings().first()
if referral is None:
await session.rollback()
return "REFERRAL_NOT_FOUND"
existing_referral_result = await session.execute(
text(
"SELECT increment FROM storage_referrals "
"WHERE referral_id = :user_id LIMIT 1"
),
{"user_id": referral_id},
)
if (
referral["user_referrer_id"] is not None
or existing_referral_result.mappings().first() is not None
):
await session.rollback()
return "ALREADY"
bonus = round(float(settings["referral_bonus_rub"]), 2)
source_id = f"registration:{referral_id}"
now_unix = get_unix()
# Приглашённому — бонус на основной баланс.
await session.execute(
text(
"INSERT INTO storage_referrals "
"(referrer_id, referral_id, referral_unix) "
"VALUES (:referrer_id, :referral_id, :referral_unix)"
),
{
"referrer_id": referrer_id,
"referral_id": referral_id,
"referral_unix": now_unix,
},
)
await session.execute(
text(
"UPDATE storage_users "
"SET user_referrer_id = :referrer_id, "
"user_balance = :balance "
"WHERE user_id = :user_id"
),
{
"referrer_id": referrer_id,
"balance": round(float(referral["user_balance"]) + bonus, 2),
"user_id": referral_id,
},
)
# Рефереру — бонус на реферальный баланс.
await session.execute(
text(
"UPDATE storage_users "
"SET user_referral_balance = :balance "
"WHERE user_id = :user_id"
),
{
"balance": round(
float(referrer["user_referral_balance"]) + bonus, 2
),
"user_id": referrer_id,
},
)
await session.execute(
text(
"INSERT INTO storage_referral_transactions "
"(user_id, related_user_id, transaction_type, source_id, amount, transaction_unix) "
"VALUES (:user_id, :related_user_id, :transaction_type, :source_id, :amount, :transaction_unix)"
),
{
"user_id": referral_id,
"related_user_id": referrer_id,
"transaction_type": "registration_bonus_main",
"source_id": source_id,
"amount": bonus,
"transaction_unix": now_unix,
},
)
await session.execute(
text(
"INSERT INTO storage_referral_transactions "
"(user_id, related_user_id, transaction_type, source_id, amount, transaction_unix) "
"VALUES (:user_id, :related_user_id, :transaction_type, :source_id, :amount, :transaction_unix)"
),
{
"user_id": referrer_id,
"related_user_id": referral_id,
"transaction_type": "registration_bonus_referral",
"source_id": source_id,
"amount": bonus,
"transaction_unix": now_unix,
},
)
await session.commit()
except Exception:
await session.rollback()
raise
return "ok"
async def transfer_to_main_balance(self, user_id: int, amount: float) -> str:
try:
transfer_amount = round(float(amount), 2)
except (TypeError, ValueError):
return "INVALID_AMOUNT"
if not isfinite(transfer_amount) or transfer_amount <= 0:
return "INVALID_AMOUNT"
async with session_factory() as session:
try:
await session.execute(text("BEGIN IMMEDIATE"))
user_result = await session.execute(
text(
"SELECT user_balance, user_referral_balance, user_referral_hold "
"FROM storage_users WHERE user_id = :user_id"
),
{"user_id": user_id},
)
get_user = user_result.mappings().first()
if get_user is None:
await session.rollback()
return "USER_NOT_FOUND"
available_balance = round(
float(get_user["user_referral_balance"])
- float(get_user["user_referral_hold"]),
2,
)
if transfer_amount > available_balance:
await session.rollback()
return "INSUFFICIENT_FUNDS"
await session.execute(
text(
"UPDATE storage_users "
"SET user_balance = :user_balance, "
"user_referral_balance = :referral_balance "
"WHERE user_id = :user_id"
),
{
"user_balance": round(
float(get_user["user_balance"]) + transfer_amount,
2,
),
"referral_balance": round(
float(get_user["user_referral_balance"]) - transfer_amount,
2,
),
"user_id": user_id,
},
)
await session.execute(
text(
"INSERT INTO storage_referral_transactions "
"(user_id, related_user_id, transaction_type, source_id, amount, transaction_unix) "
"VALUES (:user_id, NULL, :transaction_type, :source_id, :amount, :transaction_unix)"
),
{
"user_id": user_id,
"transaction_type": "transfer_to_main",
"source_id": f"transfer:{user_id}:{uuid4().hex}",
"amount": transfer_amount,
"transaction_unix": get_unix(),
},
)
await session.commit()
except Exception:
await session.rollback()
raise
return "ok"
class ReferralTransactionx(
BaseRepository[ReferralTransactionModel, ReferralTransaction]
):
def __init__(self):
super().__init__()
self.table_model = ReferralTransactionModel
self.entity_model = ReferralTransaction
self.storage_name = ReferralTransactionModel.__tablename__
async def get_refill_reward(self, source_id: str) -> Optional[ReferralTransaction]:
return await self.get(
transaction_type="refill_percent",
source_id=source_id,
)
async def add(
self,
user_id: int,
transaction_type: str,
source_id: str,
amount: float,
related_user_id: Optional[int] = None,
) -> ReferralTransaction:
return await self._insert(
user_id=user_id,
related_user_id=related_user_id,
transaction_type=transaction_type,
source_id=source_id,
amount=round(amount, 2),
transaction_unix=get_unix(),
)
class ReferralWithdrawalx(BaseRepository[ReferralWithdrawalModel, ReferralWithdrawal]):
def __init__(self):
super().__init__()
self.table_model = ReferralWithdrawalModel
self.entity_model = ReferralWithdrawal
self.storage_name = ReferralWithdrawalModel.__tablename__
async def create_pending(
self,
user_id: int,
withdrawal_amount: float,
withdrawal_method: str,
withdrawal_recipient: str,
) -> Tuple[str, Optional[ReferralWithdrawal]]:
try:
amount = round(float(withdrawal_amount), 2)
except (TypeError, ValueError):
return "INVALID_AMOUNT", None
recipient = str(withdrawal_recipient).strip()
if not isfinite(amount) or amount <= 0:
return "INVALID_AMOUNT", None
if withdrawal_method not in ("Cryptobot", "Lolzteam"):
return "INVALID_METHOD", None
if not is_valid_referral_withdrawal_recipient(withdrawal_method, recipient):
return "INVALID_RECIPIENT", None
withdrawal_id: Optional[int] = None
async with session_factory() as session:
try:
await session.execute(text("BEGIN IMMEDIATE"))
user_result = await session.execute(
text(
"SELECT user_referral_balance, user_referral_hold "
"FROM storage_users WHERE user_id = :user_id"
),
{"user_id": user_id},
)
get_user = user_result.mappings().first()
if get_user is None:
await session.rollback()
return "USER_NOT_FOUND", None
available_balance = round(
float(get_user["user_referral_balance"])
- float(get_user["user_referral_hold"]),
2,
)
if amount > available_balance:
await session.rollback()
return "INSUFFICIENT_FUNDS", None
now_unix = get_unix()
withdrawal_result = await session.execute(
text(
"INSERT INTO storage_referral_withdrawals "
"(user_id, withdrawal_amount, withdrawal_method, withdrawal_recipient, withdrawal_status, withdrawal_unix) "
"VALUES (:user_id, :amount, :method, :recipient, 'pending', :unix)"
),
{
"user_id": user_id,
"amount": amount,
"method": withdrawal_method,
"recipient": recipient,
"unix": now_unix,
},
)
withdrawal_id = int(withdrawal_result.lastrowid)
await session.execute(
text(
"UPDATE storage_users "
"SET user_referral_hold = :hold "
"WHERE user_id = :user_id"
),
{
"hold": round(
float(get_user["user_referral_hold"]) + amount,
2,
),
"user_id": user_id,
},
)
await session.execute(
text(
"INSERT INTO storage_referral_transactions "
"(user_id, related_user_id, transaction_type, source_id, amount, transaction_unix) "
"VALUES (:user_id, NULL, :transaction_type, :source_id, :amount, :unix)"
),
{
"user_id": user_id,
"transaction_type": "withdrawal_hold",
"source_id": f"withdrawal:{withdrawal_id}",
"amount": amount,
"unix": now_unix,
},
)
await session.commit()
except Exception:
await session.rollback()
raise
return "ok", await self.get(increment=withdrawal_id)
async def complete(
self, withdrawal_id: int, admin_id: int
) -> Tuple[str, Optional[ReferralWithdrawal]]:
async with session_factory() as session:
try:
await session.execute(text("BEGIN IMMEDIATE"))
withdrawal_result = await session.execute(
text(
"SELECT user_id, withdrawal_amount, withdrawal_status "
"FROM storage_referral_withdrawals WHERE increment = :withdrawal_id"
),
{"withdrawal_id": withdrawal_id},
)
withdrawal = withdrawal_result.mappings().first()
if withdrawal is None:
await session.rollback()
return "NOT_FOUND", None
if withdrawal["withdrawal_status"] != "pending":
await session.rollback()
return "ALREADY_PROCESSED", None
user_result = await session.execute(
text(
"SELECT user_referral_balance, user_referral_hold "
"FROM storage_users WHERE user_id = :user_id"
),
{"user_id": withdrawal["user_id"]},
)
get_user = user_result.mappings().first()
amount = float(withdrawal["withdrawal_amount"])
if (
get_user is None
or float(get_user["user_referral_balance"]) < amount
or float(get_user["user_referral_hold"]) < amount
):
await session.rollback()
return "BALANCE_ERROR", None
now_unix = get_unix()
await session.execute(
text(
"UPDATE storage_users "
"SET user_referral_balance = :balance, user_referral_hold = :hold "
"WHERE user_id = :user_id"
),
{
"balance": round(
float(get_user["user_referral_balance"]) - amount,
2,
),
"hold": round(
float(get_user["user_referral_hold"]) - amount,
2,
),
"user_id": withdrawal["user_id"],
},
)
await session.execute(
text(
"UPDATE storage_referral_withdrawals "
"SET withdrawal_status = 'completed', processed_unix = :unix, processed_admin_id = :admin_id "
"WHERE increment = :withdrawal_id"
),
{
"unix": now_unix,
"admin_id": admin_id,
"withdrawal_id": withdrawal_id,
},
)
await session.execute(
text(
"INSERT INTO storage_referral_transactions "
"(user_id, related_user_id, transaction_type, source_id, amount, transaction_unix) "
"VALUES (:user_id, NULL, :transaction_type, :source_id, :amount, :unix)"
),
{
"user_id": withdrawal["user_id"],
"transaction_type": "withdrawal_completed",
"source_id": f"withdrawal:{withdrawal_id}",
"amount": amount,
"unix": now_unix,
},
)
await session.commit()
except Exception:
await session.rollback()
raise
return "ok", await self.get(increment=withdrawal_id)
async def reject(
self, withdrawal_id: int, admin_id: int
) -> Tuple[str, Optional[ReferralWithdrawal]]:
async with session_factory() as session:
try:
await session.execute(text("BEGIN IMMEDIATE"))
withdrawal_result = await session.execute(
text(
"SELECT user_id, withdrawal_amount, withdrawal_status "
"FROM storage_referral_withdrawals WHERE increment = :withdrawal_id"
),
{"withdrawal_id": withdrawal_id},
)
withdrawal = withdrawal_result.mappings().first()
if withdrawal is None:
await session.rollback()
return "NOT_FOUND", None
if withdrawal["withdrawal_status"] != "pending":
await session.rollback()
return "ALREADY_PROCESSED", None
user_result = await session.execute(
text(
"SELECT user_referral_hold FROM storage_users "
"WHERE user_id = :user_id"
),
{"user_id": withdrawal["user_id"]},
)
get_user = user_result.mappings().first()
amount = float(withdrawal["withdrawal_amount"])
if get_user is None or float(get_user["user_referral_hold"]) < amount:
await session.rollback()
return "BALANCE_ERROR", None
now_unix = get_unix()
await session.execute(
text(
"UPDATE storage_users "
"SET user_referral_hold = :hold WHERE user_id = :user_id"
),
{
"hold": round(
float(get_user["user_referral_hold"]) - amount,
2,
),
"user_id": withdrawal["user_id"],
},
)
await session.execute(
text(
"UPDATE storage_referral_withdrawals "
"SET withdrawal_status = 'rejected', processed_unix = :unix, processed_admin_id = :admin_id "
"WHERE increment = :withdrawal_id"
),
{
"unix": now_unix,
"admin_id": admin_id,
"withdrawal_id": withdrawal_id,
},
)
await session.execute(
text(
"INSERT INTO storage_referral_transactions "
"(user_id, related_user_id, transaction_type, source_id, amount, transaction_unix) "
"VALUES (:user_id, NULL, :transaction_type, :source_id, :amount, :unix)"
),
{
"user_id": withdrawal["user_id"],
"transaction_type": "withdrawal_rejected",
"source_id": f"withdrawal:{withdrawal_id}",
"amount": amount,
"unix": now_unix,
},
)
await session.commit()
except Exception:
await session.rollback()
raise
return "ok", await self.get(increment=withdrawal_id)
async def add(
self,
user_id: int,
withdrawal_amount: float,
withdrawal_method: str,
withdrawal_recipient: str,
) -> ReferralWithdrawal:
return await self._insert(
user_id=user_id,
withdrawal_amount=round(withdrawal_amount, 2),
withdrawal_method=withdrawal_method,
withdrawal_recipient=withdrawal_recipient,
withdrawal_status="pending",
withdrawal_unix=get_unix(),
)
async def update(
self,
where: Optional[Union[Dict[str, Any], int]] = None,
**kwargs,
) -> int:
if isinstance(where, int):
where = {"increment": where}
return await self._update(where=where, **kwargs)
+69 -1
View File
@@ -90,7 +90,7 @@ class Refillx(BaseRepository[RefillModel, Refill]):
user_result = await session.execute( user_result = await session.execute(
text( text(
""" """
SELECT user_balance, user_refill SELECT user_balance, user_refill, user_referrer_id
FROM storage_users FROM storage_users
WHERE user_id = :user_id WHERE user_id = :user_id
""" """
@@ -103,6 +103,14 @@ class Refillx(BaseRepository[RefillModel, Refill]):
await session.rollback() await session.rollback()
return "USER_NOT_FOUND" return "USER_NOT_FOUND"
settings_result = await session.execute(
text(
"SELECT status_referral, referral_refill_percent "
"FROM storage_settings WHERE id = 1"
)
)
get_settings = settings_result.mappings().first()
new_balance = round(float(get_user["user_balance"]) + float(pay_amount), 2) new_balance = round(float(get_user["user_balance"]) + float(pay_amount), 2)
new_refill = round(float(get_user["user_refill"]) + float(pay_amount), 2) new_refill = round(float(get_user["user_refill"]) + float(pay_amount), 2)
@@ -138,6 +146,66 @@ class Refillx(BaseRepository[RefillModel, Refill]):
), ),
{"balance": new_balance, "refill": new_refill, "user_id": user_id}, {"balance": new_balance, "refill": new_refill, "user_id": user_id},
) )
referrer_id = get_user["user_referrer_id"]
referral_reward = 0.0
if (
get_settings is not None
and get_settings["status_referral"] == "True"
and referrer_id is not None
and referrer_id != user_id
):
referral_reward = round(
float(pay_amount)
* float(get_settings["referral_refill_percent"])
/ 100,
2,
)
if referral_reward > 0:
referrer_result = await session.execute(
text(
"SELECT user_referral_balance FROM storage_users "
"WHERE user_id = :user_id"
),
{"user_id": referrer_id},
)
get_referrer = referrer_result.mappings().first()
if get_referrer is not None:
source_id = f"refill:{pay_comment or pay_receipt}"
await session.execute(
text(
"UPDATE storage_users "
"SET user_referral_balance = :balance "
"WHERE user_id = :user_id"
),
{
"balance": round(
float(get_referrer["user_referral_balance"])
+ referral_reward,
2,
),
"user_id": referrer_id,
},
)
await session.execute(
text(
"INSERT INTO storage_referral_transactions "
"(user_id, related_user_id, transaction_type, source_id, amount, transaction_unix) "
"VALUES (:user_id, :related_user_id, :transaction_type, :source_id, :amount, :transaction_unix)"
),
{
"user_id": referrer_id,
"related_user_id": user_id,
"transaction_type": "refill_percent",
"source_id": source_id,
"amount": referral_reward,
"transaction_unix": get_unix(),
},
)
await session.commit() await session.commit()
except Exception: except Exception:
await session.rollback() await session.rollback()
+10 -1
View File
@@ -1,7 +1,7 @@
# - *- coding: utf- 8 - *- # - *- coding: utf- 8 - *-
from typing import Any, Dict, Optional from typing import Any, Dict, Optional
from sqlalchemy import Integer, String from sqlalchemy import Integer, String, Float
from sqlalchemy.orm import Mapped, mapped_column from sqlalchemy.orm import Mapped, mapped_column
from tgbot.database.core import Base from tgbot.database.core import Base
@@ -46,6 +46,15 @@ class SettingsModel(Base):
misc_profit_week: Mapped[int] = mapped_column(Integer, nullable=False, default=0) misc_profit_week: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
misc_profit_month: Mapped[int] = mapped_column(Integer, nullable=False, default=0) misc_profit_month: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
# Реферальная система
referral_refill_percent: Mapped[float] = mapped_column(
Float, nullable=False, default=0
)
status_referral: Mapped[str] = mapped_column(
String(16), nullable=False, default="False"
)
referral_bonus_rub: Mapped[float] = mapped_column(Float, nullable=False, default=0)
ModelBase = Settings ModelBase = Settings
BaseModel = Settings BaseModel = Settings
+33 -17
View File
@@ -15,8 +15,12 @@ from tgbot.utils.const_functions import get_unix
class UserModel(Base): class UserModel(Base):
__tablename__ = "storage_users" __tablename__ = "storage_users"
increment: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) increment: Mapped[int] = mapped_column(
user_id: Mapped[int] = mapped_column(BigInteger, nullable=False, unique=True, index=True) Integer, primary_key=True, autoincrement=True
)
user_id: Mapped[int] = mapped_column(
BigInteger, nullable=False, unique=True, index=True
)
user_login: Mapped[str] = mapped_column(String(255), nullable=False, default="") user_login: Mapped[str] = mapped_column(String(255), nullable=False, default="")
user_name: Mapped[str] = mapped_column(String(255), nullable=False, default="") user_name: Mapped[str] = mapped_column(String(255), nullable=False, default="")
user_surname: Mapped[str] = mapped_column(String(255), nullable=False, default="") user_surname: Mapped[str] = mapped_column(String(255), nullable=False, default="")
@@ -26,6 +30,13 @@ class UserModel(Base):
user_give: Mapped[float] = mapped_column(Float, nullable=False, default=0) user_give: Mapped[float] = mapped_column(Float, nullable=False, default=0)
user_unix: Mapped[int] = mapped_column(Integer, nullable=False, default=get_unix) user_unix: Mapped[int] = mapped_column(Integer, nullable=False, default=get_unix)
# Поля для реферальной системы
user_referrer_id: Mapped[int] = mapped_column(BigInteger, nullable=True, index=True)
user_referral_balance: Mapped[float] = mapped_column(
Float, nullable=False, default=0
)
user_referral_hold: Mapped[float] = mapped_column(Float, nullable=False, default=0)
ModelBase = User ModelBase = User
BaseModel = User BaseModel = User
@@ -41,31 +52,34 @@ class UsersRepository(BaseRepository[UserModel, User]):
# Добавление или обновление пользователя # Добавление или обновление пользователя
async def add( async def add(
self, self,
user_id: int, user_id: int,
user_login: str, user_login: str,
user_name: str, user_name: str,
user_surname: str = "", user_surname: str = "",
user_fullname: str = "", user_fullname: str = "",
) -> User: ) -> User:
return await self.upsert( return await self.upsert(
user_id=user_id, user_id=user_id,
user_login=user_login, user_login=user_login,
user_name=user_name, user_name=user_name,
user_surname=user_surname, user_surname=user_surname,
user_fullname=user_fullname or " ".join(filter(None, [user_name, user_surname])), user_fullname=user_fullname
or " ".join(filter(None, [user_name, user_surname])),
) )
# Выполнение upsert пользователя по телеграм ID # Выполнение upsert пользователя по телеграм ID
async def upsert( async def upsert(
self, self,
user_id: int, user_id: int,
user_login: str, user_login: str,
user_name: str, user_name: str,
user_surname: str = "", user_surname: str = "",
user_fullname: str = "", user_fullname: str = "",
) -> User: ) -> User:
user_fullname = user_fullname or " ".join(filter(None, [user_name, user_surname])) user_fullname = user_fullname or " ".join(
filter(None, [user_name, user_surname])
)
user_table = UserModel.__table__ user_table = UserModel.__table__
statement = insert(UserModel).values( statement = insert(UserModel).values(
user_id=user_id, user_id=user_id,
@@ -105,7 +119,9 @@ class UsersRepository(BaseRepository[UserModel, User]):
return user return user
# Обновление пользователя по ID или фильтру # Обновление пользователя по ID или фильтру
async def update(self, where: Optional[Union[Dict[str, Any], int]] = None, **kwargs) -> int: async def update(
self, where: Optional[Union[Dict[str, Any], int]] = None, **kwargs
) -> int:
if isinstance(where, int): if isinstance(where, int):
where = {"user_id": where} where = {"user_id": where}
+42
View File
@@ -97,6 +97,11 @@ class Settings:
misc_profit_week: int misc_profit_week: int
misc_profit_month: int misc_profit_month: int
# Реферальная система
referral_refill_percent: float
status_referral: str
referral_bonus_rub: float
@dataclass @dataclass
class User: class User:
@@ -110,3 +115,40 @@ class User:
user_refill: float user_refill: float
user_give: float user_give: float
user_unix: int user_unix: int
# Поля для реферальной системы
user_referrer_id: Optional[int]
user_referral_balance: float
user_referral_hold: float
@dataclass
class Referral:
increment: int
referrer_id: int
referral_id: int
referral_unix: int
@dataclass
class ReferralTransaction:
increment: int
user_id: int
related_user_id: Optional[int]
transaction_type: str
source_id: str
amount: float
transaction_unix: int
@dataclass
class ReferralWithdrawal:
increment: int
user_id: int
withdrawal_amount: float
withdrawal_method: str
withdrawal_recipient: str
withdrawal_status: str
withdrawal_unix: int
processed_unix: Optional[int]
processed_admin_id: Optional[int]
+3
View File
@@ -160,10 +160,13 @@ async def prepare_database() -> None:
import tgbot.database.db_refill # noqa: F401 import tgbot.database.db_refill # noqa: F401
import tgbot.database.db_settings # noqa: F401 import tgbot.database.db_settings # noqa: F401
import tgbot.database.db_users # noqa: F401 import tgbot.database.db_users # noqa: F401
import tgbot.database.db_referral # noqa: F401
async with engine.begin() as conn: async with engine.begin() as conn:
from tgbot.database.db_payments import ensure_payments_schema from tgbot.database.db_payments import ensure_payments_schema
from tgbot.database.db_referral import ensure_referrals_schema
await ensure_referrals_schema(conn)
await ensure_payments_schema(conn) await ensure_payments_schema(conn)
await conn.run_sync(Base.metadata.create_all) await conn.run_sync(Base.metadata.create_all)
+34
View File
@@ -20,6 +20,17 @@ def close_finl() -> InlineKeyboardMarkup:
# Рассылка # Рассылка
def referral_withdrawal_actions_finl(withdrawal_id: int) -> InlineKeyboardMarkup:
keyboard = InlineKeyboardBuilder()
keyboard.row(
ikb("✅ Выполнено", data=f"referral_withdrawal_complete:{withdrawal_id}"),
ikb("❌ Отклонить", data=f"referral_withdrawal_reject:{withdrawal_id}"),
)
return keyboard.as_markup()
def mail_confirm_finl() -> InlineKeyboardMarkup: def mail_confirm_finl() -> InlineKeyboardMarkup:
keyboard = InlineKeyboardBuilder() keyboard = InlineKeyboardBuilder()
@@ -207,6 +218,17 @@ async def settings_finl() -> InlineKeyboardMarkup:
else: else:
method_prod_kb = ikb("На каждой строке", data="settings_edit_method_prod:skip") method_prod_kb = ikb("На каждой строке", data="settings_edit_method_prod:skip")
# Бонус за приглашение
referral_bonus_kb = ikb(
f"{get_settings.referral_bonus_rub}", data="settings_edit_referral_bonus"
)
# Доход с пополнений
deposit_percent_kb = ikb(
f"{get_settings.referral_refill_percent}%",
data="settings_edit_referral_percent",
)
keyboard.row( keyboard.row(
ikb("❔ FAQ", data="..."), ikb("❔ FAQ", data="..."),
faq_kb, faq_kb,
@@ -222,6 +244,12 @@ async def settings_finl() -> InlineKeyboardMarkup:
).row( ).row(
ikb("🎁 Метод добавления", data="..."), ikb("🎁 Метод добавления", data="..."),
method_prod_kb, method_prod_kb,
).row(
ikb("🤝 Бонус за приглашение", data="..."),
referral_bonus_kb,
).row(
ikb("📈 Доход с пополнений", data="..."),
deposit_percent_kb,
) )
return keyboard.as_markup() return keyboard.as_markup()
@@ -236,6 +264,7 @@ async def settings_status_finl() -> InlineKeyboardMarkup:
status_work_kb = ikb("Включены ✅", data="settings_status_work:False") status_work_kb = ikb("Включены ✅", data="settings_status_work:False")
status_buy_kb = ikb("Включены ✅", data="settings_status_buy:False") status_buy_kb = ikb("Включены ✅", data="settings_status_buy:False")
status_refill_kb = ikb("Включены ✅", data="settings_status_refill:False") status_refill_kb = ikb("Включены ✅", data="settings_status_refill:False")
status_referral_kb = ikb("Включена ✅", data="settings_status_referral:False")
notification_buy_kb = ikb("Включены 🔔", data="settings_notification_buy:False") notification_buy_kb = ikb("Включены 🔔", data="settings_notification_buy:False")
notification_refill_kb = ikb( notification_refill_kb = ikb(
"Включены 🔔", data="settings_notification_refill:False" "Включены 🔔", data="settings_notification_refill:False"
@@ -247,6 +276,8 @@ async def settings_status_finl() -> InlineKeyboardMarkup:
status_work_kb = ikb("Выключены ❌", data="settings_status_work:True") status_work_kb = ikb("Выключены ❌", data="settings_status_work:True")
if get_settings.status_refill == "False": if get_settings.status_refill == "False":
status_refill_kb = ikb("Выключены ❌", data="settings_status_refill:True") status_refill_kb = ikb("Выключены ❌", data="settings_status_refill:True")
if get_settings.status_referral == "False":
status_referral_kb = ikb("Выключена ❌", data="settings_status_referral:True")
if get_settings.notification_buy == "False": if get_settings.notification_buy == "False":
notification_buy_kb = ikb("Выключены 🔕", data="settings_notification_buy:True") notification_buy_kb = ikb("Выключены 🔕", data="settings_notification_buy:True")
if get_settings.notification_refill == "False": if get_settings.notification_refill == "False":
@@ -263,6 +294,9 @@ async def settings_status_finl() -> InlineKeyboardMarkup:
).row( ).row(
ikb("🎁 Покупки", data="..."), ikb("🎁 Покупки", data="..."),
status_buy_kb, status_buy_kb,
).row(
ikb("🤝 Реферальная система", data="..."),
status_referral_kb,
).row( ).row(
ikb("📢 Увед. о покупках", data="..."), ikb("📢 Увед. о покупках", data="..."),
notification_buy_kb, notification_buy_kb,
+35
View File
@@ -33,6 +33,41 @@ def user_support_finl(support_login: str) -> InlineKeyboardMarkup:
return keyboard.as_markup() return keyboard.as_markup()
def referral_menu_finl(referral_link: str) -> InlineKeyboardMarkup:
keyboard = InlineKeyboardBuilder()
keyboard.row(
ikb("💸 Перевести на основной баланс", data="referral_transfer"),
).row(
ikb("📥 Вывести средства", data="referral_withdrawal"),
)
keyboard.row(ikb("▪️ Скопировать ссылку", copy=referral_link))
return keyboard.as_markup()
def referral_transfer_method_finl() -> InlineKeyboardMarkup:
keyboard = InlineKeyboardBuilder()
keyboard.row(ikb("🔙 Вернуться", data="referral_menu"))
return keyboard.as_markup()
def referral_withdrawal_method_finl() -> InlineKeyboardMarkup:
keyboard = InlineKeyboardBuilder()
keyboard.row(
ikb("🔷 CryptoBot", data="referral_withdrawal_method:Cryptobot"),
).row(
ikb("🟢 Lolzteam", data="referral_withdrawal_method:Lolzteam"),
).row(
ikb("🔙 Вернуться", data="referral_menu"),
)
return keyboard.as_markup()
################################################################################ ################################################################################
################################### ПЛАТЕЖИ #################################### ################################### ПЛАТЕЖИ ####################################
# Выбор способа пополнения # Выбор способа пополнения
+2
View File
@@ -14,6 +14,8 @@ def menu_frep(user_id: int) -> ReplyKeyboardMarkup:
rkb("🎁 Купить"), rkb("🎁 Купить"),
rkb("👤 Профиль"), rkb("👤 Профиль"),
rkb("🧮 Наличие товаров"), rkb("🧮 Наличие товаров"),
).row(
rkb("🤝 Реферальная система"),
).row( ).row(
rkb("☎️ Поддержка"), rkb("☎️ Поддержка"),
rkb("❔ FAQ"), rkb("❔ FAQ"),
+67 -2
View File
@@ -3,16 +3,37 @@ from aiogram import BaseMiddleware
from cachetools import TTLCache from cachetools import TTLCache
from tgbot.data.config import BOT_USER_CACHE_TTL from tgbot.data.config import BOT_USER_CACHE_TTL
from tgbot.database import Userx from tgbot.database import Referralx, Settingsx, Userx
from tgbot.utils.const_functions import clear_html from tgbot.utils.const_functions import clear_html
from tgbot.utils.misc.bot_logging import bot_logger
class ExistsUserMiddleware(BaseMiddleware): class ExistsUserMiddleware(BaseMiddleware):
# Создание кеша пользователей # Создание кеша пользователей
def __init__(self, cache_ttl: int = BOT_USER_CACHE_TTL) -> None: def __init__(self, cache_ttl: int = BOT_USER_CACHE_TTL) -> None:
self.users = Userx() self.users = Userx()
self.referrals = Referralx()
self.cache = TTLCache(maxsize=10_000, ttl=cache_ttl) self.cache = TTLCache(maxsize=10_000, ttl=cache_ttl)
@staticmethod
def _get_referrer_id(event) -> int | None:
event_text = getattr(event, "text", None)
if not isinstance(event_text, str):
return None
start_data = event_text.split(maxsplit=1)
if len(start_data) != 2 or start_data[0] != "/start":
return None
referral_data = start_data[1]
if not referral_data.startswith("r_") or not referral_data[2:].isdigit():
return None
return int(referral_data[2:])
# Добавление или обновление пользователя перед handler-ом # Добавление или обновление пользователя перед handler-ом
async def __call__(self, handler, event, data): async def __call__(self, handler, event, data):
this_user = data.get("event_from_user") this_user = data.get("event_from_user")
@@ -32,6 +53,12 @@ class ExistsUserMiddleware(BaseMiddleware):
) )
cached_user = self.cache.get(user_id) cached_user = self.cache.get(user_id)
existing_user = None
if cached_user is None:
existing_user = await self.users.get(user_id=user_id)
is_new_user = cached_user is None and existing_user is None
if cached_user is None or cached_user["data"] != user_data: if cached_user is None or cached_user["data"] != user_data:
user = await self.users.upsert( user = await self.users.upsert(
@@ -41,10 +68,48 @@ class ExistsUserMiddleware(BaseMiddleware):
user_surname=user_data[2], user_surname=user_data[2],
user_fullname=user_data[3], user_fullname=user_data[3],
) )
self.cache[user_id] = {"data": user_data, "user": user}
else: else:
user = cached_user["user"] user = cached_user["user"]
referrer_id = self._get_referrer_id(event)
if is_new_user and referrer_id is not None:
registration_status = await self.referrals.register_referral(
referral_id=user_id,
referrer_id=referrer_id,
)
if registration_status == "ok":
user = await self.users.get_required(user_id=user_id)
get_settings = await Settingsx().get()
bot = data.get("bot") or getattr(event, "bot", None)
if bot is not None:
notifications = (
(
referrer_id,
"<b>🤝 Новый реферал</b>\n\n"
f"▪️ Пользователь: <a href='tg://user?id={user_id}'>{user.user_name}</a>\n"
f"▪️ Начислено: <code>{get_settings.referral_bonus_rub}₽</code>",
),
(
user_id,
"<b>🤝 Реферальный бонус</b>\n\n"
f"▪️ Вам начислено: <code>{get_settings.referral_bonus_rub}₽</code>",
),
)
for recipient_id, notification_text in notifications:
try:
await bot.send_message(recipient_id, notification_text)
except Exception:
bot_logger.warning(
"Не удалось отправить уведомление о реферальной регистрации",
exc_info=True,
)
self.cache[user_id] = {"data": user_data, "user": user}
data["User"] = user data["User"] = user
return await handler(event, data) return await handler(event, data)
+46 -12
View File
@@ -2,8 +2,19 @@
from aiogram import Dispatcher from aiogram import Dispatcher
from tgbot.routers import main_errors, main_start, main_missed from tgbot.routers import main_errors, main_start, main_missed
from tgbot.routers.admin import admin_menu, admin_functions, admin_payments, admin_products, admin_settings from tgbot.routers.admin import (
from tgbot.routers.user import user_menu, user_transactions, user_products admin_menu,
admin_functions,
admin_payments,
admin_products,
admin_settings,
)
from tgbot.routers.user import (
user_menu,
user_referral,
user_transactions,
user_products,
)
from tgbot.utils.misc.bot_filters import IsAdmin, IsPrivate from tgbot.utils.misc.bot_filters import IsAdmin, IsPrivate
@@ -23,17 +34,39 @@ def register_all_routers(dp: Dispatcher):
user_products.router.callback_query.filter(IsPrivate()) user_products.router.callback_query.filter(IsPrivate())
user_transactions.router.message.filter(IsPrivate()) user_transactions.router.message.filter(IsPrivate())
user_transactions.router.callback_query.filter(IsPrivate()) user_transactions.router.callback_query.filter(IsPrivate())
user_referral.router.message.filter(IsPrivate())
user_referral.router.callback_query.filter(IsPrivate())
admin_menu.router.message.filter(IsPrivate(), IsAdmin()) # Работа message роутера только для админов admin_menu.router.message.filter(
admin_menu.router.callback_query.filter(IsPrivate(), IsAdmin()) # Работа callback роутера только для админов IsPrivate(), IsAdmin()
admin_functions.router.message.filter(IsPrivate(), IsAdmin()) # Работа message роутера только для админов ) # Работа message роутера только для админов
admin_functions.router.callback_query.filter(IsPrivate(), IsAdmin()) # Работа callback роутера только для админов admin_menu.router.callback_query.filter(
admin_payments.router.message.filter(IsPrivate(), IsAdmin()) # Работа message роутера только для админов IsPrivate(), IsAdmin()
admin_payments.router.callback_query.filter(IsPrivate(), IsAdmin()) # Работа callback роутера только для админов ) # Работа callback роутера только для админов
admin_settings.router.message.filter(IsPrivate(), IsAdmin()) # Работа message роутера только для админов admin_functions.router.message.filter(
admin_settings.router.callback_query.filter(IsPrivate(), IsAdmin()) # Работа callback роутера только для админов IsPrivate(), IsAdmin()
admin_products.router.message.filter(IsPrivate(), IsAdmin()) # Работа message роутера только для админов ) # Работа message роутера только для админов
admin_products.router.callback_query.filter(IsPrivate(), IsAdmin()) # Работа callback роутера только для админов admin_functions.router.callback_query.filter(
IsPrivate(), IsAdmin()
) # Работа callback роутера только для админов
admin_payments.router.message.filter(
IsPrivate(), IsAdmin()
) # Работа message роутера только для админов
admin_payments.router.callback_query.filter(
IsPrivate(), IsAdmin()
) # Работа callback роутера только для админов
admin_settings.router.message.filter(
IsPrivate(), IsAdmin()
) # Работа message роутера только для админов
admin_settings.router.callback_query.filter(
IsPrivate(), IsAdmin()
) # Работа callback роутера только для админов
admin_products.router.message.filter(
IsPrivate(), IsAdmin()
) # Работа message роутера только для админов
admin_products.router.callback_query.filter(
IsPrivate(), IsAdmin()
) # Работа callback роутера только для админов
# Подключение обязательных роутеров # Подключение обязательных роутеров
dp.include_router(main_errors.router) # Роутер ошибки dp.include_router(main_errors.router) # Роутер ошибки
@@ -44,6 +77,7 @@ def register_all_routers(dp: Dispatcher):
dp.include_router(admin_menu.router) # Админ роутер dp.include_router(admin_menu.router) # Админ роутер
dp.include_router(user_products.router) # Юзер роутер dp.include_router(user_products.router) # Юзер роутер
dp.include_router(user_transactions.router) # Юзер роутер dp.include_router(user_transactions.router) # Юзер роутер
dp.include_router(user_referral.router) # Юзер роутер
dp.include_router(admin_functions.router) # Админ роутер dp.include_router(admin_functions.router) # Админ роутер
dp.include_router(admin_payments.router) # Админ роутер dp.include_router(admin_payments.router) # Админ роутер
dp.include_router(admin_settings.router) # Админ роутер dp.include_router(admin_settings.router) # Админ роутер
+175 -23
View File
@@ -5,18 +5,137 @@ from aiogram import Router, Bot, F
from aiogram.filters import StateFilter from aiogram.filters import StateFilter
from aiogram.types import CallbackQuery, Message from aiogram.types import CallbackQuery, Message
from tgbot.database import Purchasesx, Refillx, Userx from tgbot.database import Purchasesx, Refillx, ReferralWithdrawalx, Userx
from tgbot.keyboards.inline_admin import profile_edit_return_finl, mail_confirm_finl from tgbot.keyboards.inline_admin import profile_edit_return_finl, mail_confirm_finl
from tgbot.services.api_hosting_text import HostingAPI from tgbot.services.api_hosting_text import HostingAPI
from tgbot.utils.const_functions import is_number, to_number, del_message, ded, clear_html, convert_date from tgbot.utils.const_functions import (
is_number,
to_number,
del_message,
ded,
clear_html,
convert_date,
)
from tgbot.utils.misc.bot_logging import bot_logger from tgbot.utils.misc.bot_logging import bot_logger
from tgbot.utils.misc.bot_models import FSM, ARS from tgbot.utils.misc.bot_models import FSM, ARS
from tgbot.utils.misc_functions import functions_mail_make from tgbot.utils.misc_functions import functions_mail_make
from tgbot.utils.text_functions import open_profile_admin, refill_open_admin, purchase_open_admin from tgbot.utils.text_functions import (
open_profile_admin,
refill_open_admin,
purchase_open_admin,
)
router = Router(name=__name__) router = Router(name=__name__)
def withdrawal_method_title(withdrawal_method: str) -> str:
if withdrawal_method == "Cryptobot":
return "CryptoBot"
return withdrawal_method
@router.callback_query(F.data.startswith("referral_withdrawal_complete:"))
async def referral_withdrawal_complete(
call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS
):
withdrawal_id_raw = call.data.split(":", 1)[1]
if not withdrawal_id_raw.isdigit():
return await call.answer("❌ Заявка не найдена", True)
status, withdrawal = await ReferralWithdrawalx().complete(
withdrawal_id=int(withdrawal_id_raw),
admin_id=call.from_user.id,
)
if status == "ALREADY_PROCESSED":
return await call.answer("❌ Заявка уже обработана", True)
if status != "ok" or withdrawal is None:
return await call.answer("❌ Не удалось обработать заявку", True)
method_title = withdrawal_method_title(withdrawal.withdrawal_method)
get_user = await Userx().get_required(user_id=withdrawal.user_id)
await call.message.edit_text(
ded(f"""
<b>✅ Заявка на вывод выполнена</b>
▪️ Номер заявки: <code>#{withdrawal.increment}</code>
▪️ Пользователь: <b>@{get_user.user_login}</b> | <a href='tg://user?id={get_user.user_id}'>{get_user.user_name}</a> | <code>{get_user.user_id}</code>
▪️ Сумма: <code>{withdrawal.withdrawal_amount}₽</code>
▪️ Способ: <code>{method_title}</code>
▪️ Реквизиты: <code>{withdrawal.withdrawal_recipient}</code>
"""),
reply_markup=None,
)
try:
await bot.send_message(
withdrawal.user_id,
ded(f"""
<b>✅ Заявка на вывод выполнена</b>
▪️ Номер заявки: <code>#{withdrawal.increment}</code>
▪️ Сумма: <code>{withdrawal.withdrawal_amount}₽</code>
▪️ Способ: <code>{method_title}</code>
"""),
)
except Exception:
bot_logger.warning(
"Не удалось отправить пользователю уведомление о выполненном выводе",
exc_info=True,
)
await call.answer("✅ Заявка выполнена", True)
@router.callback_query(F.data.startswith("referral_withdrawal_reject:"))
async def referral_withdrawal_reject(
call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS
):
withdrawal_id_raw = call.data.split(":", 1)[1]
if not withdrawal_id_raw.isdigit():
return await call.answer("❌ Заявка не найдена", True)
status, withdrawal = await ReferralWithdrawalx().reject(
withdrawal_id=int(withdrawal_id_raw),
admin_id=call.from_user.id,
)
if status == "ALREADY_PROCESSED":
return await call.answer("❌ Заявка уже обработана", True)
if status != "ok" or withdrawal is None:
return await call.answer("❌ Не удалось обработать заявку", True)
get_user = await Userx().get_required(user_id=withdrawal.user_id)
method_title = withdrawal_method_title(withdrawal.withdrawal_method)
await call.message.edit_text(
ded(f"""
<b>❌ Заявка на вывод отклонена</b>
▪️ Номер заявки: <code>#{withdrawal.increment}</code>
▪️ Пользователь: <b>@{get_user.user_login}</b> | <a href='tg://user?id={get_user.user_id}'>{get_user.user_name}</a> | <code>{get_user.user_id}</code>
▪️ Сумма возвращена на реферальный баланс: <code>{withdrawal.withdrawal_amount}₽</code>
▪️ Способ: <code>{method_title}</code>
▪️ Реквизиты: <code>{withdrawal.withdrawal_recipient}</code>
"""),
reply_markup=None,
)
try:
await bot.send_message(
withdrawal.user_id,
ded(f"""
<b>❌ Заявка на вывод отклонена</b>
▪️ Номер заявки: <code>#{withdrawal.increment}</code>
▪️ Сумма возвращена на реферальный баланс: <code>{withdrawal.withdrawal_amount}₽</code>
"""),
)
except Exception:
bot_logger.warning(
"Не удалось отправить пользователю уведомление об отклонённом выводе",
exc_info=True,
)
await call.answer("❌ Заявка отклонена", True)
# Поиск чеков и профилей # Поиск чеков и профилей
@router.message(F.text == "🔍 Поиск") @router.message(F.text == "🔍 Поиск")
async def functions_find(message: Message, bot: Bot, state: FSM, arSession: ARS): async def functions_find(message: Message, bot: Bot, state: FSM, arSession: ARS):
@@ -55,17 +174,23 @@ async def functions_mail_get(message: Message, bot: Bot, state: FSM, arSession:
# Подтверждение отправки рассылки # Подтверждение отправки рассылки
@router.callback_query(F.data.startswith("mail_confirm:"), StateFilter("here_mail_confirm")) @router.callback_query(
async def functions_mail_confirm(call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS): F.data.startswith("mail_confirm:"), StateFilter("here_mail_confirm")
)
async def functions_mail_confirm(
call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS
):
get_status = call.data.split(":")[1] get_status = call.data.split(":")[1]
send_message = (await state.get_data())['here_mail_message'] send_message = (await state.get_data())["here_mail_message"]
await state.clear() await state.clear()
if get_status == "Yes": if get_status == "Yes":
get_users = await Userx().get_all() get_users = await Userx().get_all()
await call.message.edit_text(f"<b>📢 Рассылка началась... (0/{len(get_users)})</b>") await call.message.edit_text(
f"<b>📢 Рассылка началась... (0/{len(get_users)})</b>"
)
await asyncio.create_task(functions_mail_make(bot, send_message, call)) await asyncio.create_task(functions_mail_make(bot, send_message, call))
else: else:
@@ -76,7 +201,7 @@ async def functions_mail_confirm(call: CallbackQuery, bot: Bot, state: FSM, arSe
##################################### ПОИСК #################################### ##################################### ПОИСК ####################################
# Принятие айди/логина пользователя или чека для поиска # Принятие айди/логина пользователя или чека для поиска
@router.message(F.text, StateFilter("here_find")) @router.message(F.text, StateFilter("here_find"))
@router.message(F.text.lower().startswith(('.find', 'find'))) @router.message(F.text.lower().startswith((".find", "find")))
async def functions_find_get(message: Message, bot: Bot, state: FSM, arSession: ARS): async def functions_find_get(message: Message, bot: Bot, state: FSM, arSession: ARS):
find_data = message.text.lower() find_data = message.text.lower()
@@ -116,14 +241,18 @@ async def functions_find_get(message: Message, bot: Bot, state: FSM, arSession:
return await refill_open_admin(bot, message.from_user.id, get_refill) return await refill_open_admin(bot, message.from_user.id, get_refill)
if get_purchase is not None: if get_purchase is not None:
return await purchase_open_admin(bot, arSession, message.from_user.id, get_purchase) return await purchase_open_admin(
bot, arSession, message.from_user.id, get_purchase
)
################################################################################ ################################################################################
############################## УПРАВЛЕНИЕ ПРОФИЛЕМ ############################# ############################## УПРАВЛЕНИЕ ПРОФИЛЕМ #############################
# Обновление профиля пользователя # Обновление профиля пользователя
@router.callback_query(F.data.startswith("admin_user_refresh:")) @router.callback_query(F.data.startswith("admin_user_refresh:"))
async def functions_user_refresh(call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS): async def functions_user_refresh(
call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS
):
user_id = int(call.data.split(":")[1]) user_id = int(call.data.split(":")[1])
get_user = await Userx().get_required(user_id=user_id) get_user = await Userx().get_required(user_id=user_id)
@@ -136,7 +265,9 @@ async def functions_user_refresh(call: CallbackQuery, bot: Bot, state: FSM, arSe
# Покупки пользователя # Покупки пользователя
@router.callback_query(F.data.startswith("admin_user_purchases:")) @router.callback_query(F.data.startswith("admin_user_purchases:"))
async def functions_user_purchases(call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS): async def functions_user_purchases(
call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS
):
user_id = int(call.data.split(":")[1]) user_id = int(call.data.split(":")[1])
get_user = await Userx().get_required(user_id=user_id) get_user = await Userx().get_required(user_id=user_id)
@@ -174,7 +305,9 @@ async def functions_user_purchases(call: CallbackQuery, bot: Bot, state: FSM, ar
# Выдача баланса пользователю # Выдача баланса пользователю
@router.callback_query(F.data.startswith("admin_user_balance_add:")) @router.callback_query(F.data.startswith("admin_user_balance_add:"))
async def functions_user_balance_add(call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS): async def functions_user_balance_add(
call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS
):
user_id = int(call.data.split(":")[1]) user_id = int(call.data.split(":")[1])
await state.update_data(here_user=user_id) await state.update_data(here_user=user_id)
@@ -188,8 +321,10 @@ async def functions_user_balance_add(call: CallbackQuery, bot: Bot, state: FSM,
# Принятие суммы для выдачи баланса пользователю # Принятие суммы для выдачи баланса пользователю
@router.message(F.text, StateFilter("here_user_add")) @router.message(F.text, StateFilter("here_user_add"))
async def functions_user_balance_add_get(message: Message, bot: Bot, state: FSM, arSession: ARS): async def functions_user_balance_add_get(
user_id = (await state.get_data())['here_user'] message: Message, bot: Bot, state: FSM, arSession: ARS
):
user_id = (await state.get_data())["here_user"]
if not is_number(message.text): if not is_number(message.text):
return await message.answer( return await message.answer(
@@ -222,7 +357,11 @@ async def functions_user_balance_add_get(message: Message, bot: Bot, state: FSM,
f"<b>💰 Вам было выдано <code>{message.text}₽</code></b>", f"<b>💰 Вам было выдано <code>{message.text}₽</code></b>",
) )
except Exception: except Exception:
bot_logger.debug("Не удалось уведомить пользователя %s о выдаче баланса", user_id, exc_info=True) bot_logger.debug(
"Не удалось уведомить пользователя %s о выдаче баланса",
user_id,
exc_info=True,
)
await message.answer( await message.answer(
f"👤 Пользователь: <a href='tg://user?id={get_user.user_id}'>{get_user.user_name}</a>\n" f"👤 Пользователь: <a href='tg://user?id={get_user.user_id}'>{get_user.user_name}</a>\n"
@@ -235,7 +374,9 @@ async def functions_user_balance_add_get(message: Message, bot: Bot, state: FSM,
# Изменение баланса пользователю # Изменение баланса пользователю
@router.callback_query(F.data.startswith("admin_user_balance_set:")) @router.callback_query(F.data.startswith("admin_user_balance_set:"))
async def functions_user_balance_set(call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS): async def functions_user_balance_set(
call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS
):
user_id = int(call.data.split(":")[1]) user_id = int(call.data.split(":")[1])
await state.update_data(here_user=user_id) await state.update_data(here_user=user_id)
@@ -249,8 +390,10 @@ async def functions_user_balance_set(call: CallbackQuery, bot: Bot, state: FSM,
# Принятие суммы для изменения баланса пользователя # Принятие суммы для изменения баланса пользователя
@router.message(F.text, StateFilter("here_user_set")) @router.message(F.text, StateFilter("here_user_set"))
async def functions_user_balance_set_get(message: Message, bot: Bot, state: FSM, arSession: ARS): async def functions_user_balance_set_get(
user_id = (await state.get_data())['here_user'] message: Message, bot: Bot, state: FSM, arSession: ARS
):
user_id = (await state.get_data())["here_user"]
if not is_number(message.text): if not is_number(message.text):
return await message.answer( return await message.answer(
@@ -294,7 +437,9 @@ async def functions_user_balance_set_get(message: Message, bot: Bot, state: FSM,
# Отправка сообщения пользователю # Отправка сообщения пользователю
@router.callback_query(F.data.startswith("admin_user_message:")) @router.callback_query(F.data.startswith("admin_user_message:"))
async def functions_user_user_message(call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS): async def functions_user_user_message(
call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS
):
user_id = int(call.data.split(":")[1]) user_id = int(call.data.split(":")[1])
await state.update_data(here_user_id=user_id) await state.update_data(here_user_id=user_id)
@@ -309,17 +454,24 @@ async def functions_user_user_message(call: CallbackQuery, bot: Bot, state: FSM,
# Принятие сообщения для отправки пользователю # Принятие сообщения для отправки пользователю
@router.message(F.text, StateFilter("here_user_message")) @router.message(F.text, StateFilter("here_user_message"))
async def functions_user_user_message_get(message: Message, bot: Bot, state: FSM, arSession: ARS): async def functions_user_user_message_get(
user_id = (await state.get_data())['here_user_id'] message: Message, bot: Bot, state: FSM, arSession: ARS
):
user_id = (await state.get_data())["here_user_id"]
await state.clear() await state.clear()
get_message = "<b>💌 Сообщение от администратора:</b>\n" + f"<code>{clear_html(message.text)}</code>" get_message = (
"<b>💌 Сообщение от администратора:</b>\n"
+ f"<code>{clear_html(message.text)}</code>"
)
get_user = await Userx().get_required(user_id=user_id) get_user = await Userx().get_required(user_id=user_id)
try: try:
await bot.send_message(user_id, get_message) await bot.send_message(user_id, get_message)
except Exception: except Exception:
bot_logger.debug("Не удалось отправить сообщение пользователю %s", user_id, exc_info=True) bot_logger.debug(
"Не удалось отправить сообщение пользователю %s", user_id, exc_info=True
)
await message.reply("<b>❌ Не удалось отправить сообщение</b>") await message.reply("<b>❌ Не удалось отправить сообщение</b>")
else: else:
await message.reply("<b>✅ Сообщение было успешно доставлено</b>") await message.reply("<b>✅ Сообщение было успешно доставлено</b>")
+94 -1
View File
@@ -1,11 +1,13 @@
# - *- coding: utf- 8 - *- # - *- coding: utf- 8 - *-
from math import isfinite
from aiogram import Router, Bot, F from aiogram import Router, Bot, F
from aiogram.filters import StateFilter from aiogram.filters import StateFilter
from aiogram.types import CallbackQuery, Message from aiogram.types import CallbackQuery, Message
from tgbot.database import Settingsx, Userx from tgbot.database import Settingsx, Userx
from tgbot.keyboards.inline_admin import settings_status_finl, settings_finl from tgbot.keyboards.inline_admin import settings_status_finl, settings_finl
from tgbot.utils.const_functions import ded from tgbot.utils.const_functions import ded, is_number, to_number
from tgbot.utils.misc.bot_logging import bot_logger from tgbot.utils.misc.bot_logging import bot_logger
from tgbot.utils.misc.bot_models import FSM, ARS from tgbot.utils.misc.bot_models import FSM, ARS
from tgbot.utils.misc_functions import send_admins, insert_tags from tgbot.utils.misc_functions import send_admins, insert_tags
@@ -116,6 +118,31 @@ async def settings_status_refill(
await call.message.edit_reply_markup(reply_markup=await settings_status_finl()) await call.message.edit_reply_markup(reply_markup=await settings_status_finl())
# Включение/выключение реферальной системы
@router.callback_query(F.data.startswith("settings_status_referral:"))
async def settings_status_referral(
call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS
):
get_status = call.data.split(":")[1]
get_user = await Userx().get_required(user_id=call.from_user.id)
await Settingsx().update(status_referral=get_status)
if get_status == "True":
send_text = "🟢 Включил реферальную систему в боте"
else:
send_text = "🔴 Выключил реферальную систему в боте"
await send_admins(
bot,
f"👤 Администратор <a href='tg://user?id={get_user.user_id}'>{get_user.user_name}</a>\n"
f"{send_text}",
not_me=get_user.user_id,
)
await call.message.edit_reply_markup(reply_markup=await settings_status_finl())
# Включение/выключение уведомлений о покупках # Включение/выключение уведомлений о покупках
@router.callback_query(F.data.startswith("settings_notification_buy:")) @router.callback_query(F.data.startswith("settings_notification_buy:"))
async def settings_notification_buy( async def settings_notification_buy(
@@ -267,6 +294,72 @@ async def settings_faq_get(message: Message, bot: Bot, state: FSM, arSession: AR
# Принятие поддержки # Принятие поддержки
@router.callback_query(F.data == "settings_edit_referral_bonus")
async def settings_edit_referral_bonus(
call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS
):
await state.clear()
await state.set_state("here_settings_referral_bonus")
await call.message.edit_text(
"<b>🤝 Введите фиксированный бонус за приглашение в рублях</b>"
)
@router.callback_query(F.data == "settings_edit_referral_percent")
async def settings_edit_referral_percent(
call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS
):
await state.clear()
await state.set_state("here_settings_referral_percent")
await call.message.edit_text(
"<b>📈 Введите процент дохода с пополнений от 0 до 100</b>"
)
@router.message(F.text, StateFilter("here_settings_referral_bonus"))
async def settings_referral_bonus_get(
message: Message, bot: Bot, state: FSM, arSession: ARS
):
if not is_number(message.text):
return await message.answer(
"<b>❌ Введите неотрицательное число в рублях</b>"
)
referral_bonus = float(to_number(message.text))
if not isfinite(referral_bonus) or referral_bonus < 0:
return await message.answer(
"<b>❌ Введите неотрицательное число в рублях</b>"
)
await Settingsx().update(referral_bonus_rub=round(referral_bonus, 2))
await state.clear()
await message.answer(
"<b>🖍 Изменение данных бота</b>",
reply_markup=await settings_finl(),
)
@router.message(F.text, StateFilter("here_settings_referral_percent"))
async def settings_referral_percent_get(
message: Message, bot: Bot, state: FSM, arSession: ARS
):
if not is_number(message.text):
return await message.answer("<b>❌ Введите число от 0 до 100</b>")
referral_percent = float(to_number(message.text))
if not isfinite(referral_percent) or not 0 <= referral_percent <= 100:
return await message.answer("<b>❌ Введите число от 0 до 100</b>")
await Settingsx().update(referral_refill_percent=round(referral_percent, 2))
await state.clear()
await message.answer(
"<b>🖍 Изменение данных бота</b>",
reply_markup=await settings_finl(),
)
@router.message(F.text, StateFilter("here_settings_support")) @router.message(F.text, StateFilter("here_settings_support"))
async def settings_support_get(message: Message, bot: Bot, state: FSM, arSession: ARS): async def settings_support_get(message: Message, bot: Bot, state: FSM, arSession: ARS):
get_support = message.text get_support = message.text
+3 -1
View File
@@ -124,7 +124,9 @@ async def main_start(message: Message, bot: Bot, state: FSM, arSession: ARS):
async def main_start_deeplink(message: Message, bot: Bot, state: FSM, arSession: ARS): async def main_start_deeplink(message: Message, bot: Bot, state: FSM, arSession: ARS):
deepling_args = message.text[7:] deepling_args = message.text[7:]
if deepling_args.startswith("p_"): if deepling_args.startswith("r_"):
await main_start(message, bot, state, arSession)
elif deepling_args.startswith("p_"):
position_id_raw = deepling_args[2:] position_id_raw = deepling_args[2:]
if not position_id_raw.isdigit(): if not position_id_raw.isdigit():
+359
View File
@@ -0,0 +1,359 @@
# - *- coding: utf- 8 - *-
from math import isfinite
from urllib.parse import urlparse
import re
from aiogram import Bot, F, Router
from aiogram.filters import StateFilter
from aiogram.types import CallbackQuery, Message
from tgbot.database import ReferralWithdrawalx, Referralx, Settingsx, Userx
from tgbot.keyboards.inline_admin import referral_withdrawal_actions_finl
from tgbot.keyboards.inline_user import (
referral_menu_finl,
referral_transfer_method_finl,
referral_withdrawal_method_finl,
)
from tgbot.utils.const_functions import clear_html, ded, is_number, to_number
from tgbot.utils.misc.bot_models import ARS, FSM
from tgbot.utils.misc_functions import send_admins
router = Router(name=__name__)
def is_valid_withdrawal_recipient(withdrawal_method: str, recipient: str) -> bool:
if withdrawal_method == "Cryptobot":
return bool(re.fullmatch(r"@[A-Za-z][A-Za-z0-9_]{4,31}", recipient))
if withdrawal_method == "Lolzteam":
parsed_url = urlparse(recipient)
return parsed_url.scheme in ("http", "https") and parsed_url.hostname in (
"zelenka.guru",
"lolz.team",
"lolz.live",
"lolz.guru",
)
return False
def withdrawal_recipient_hint(withdrawal_method: str) -> str:
if withdrawal_method == "Cryptobot":
return (
"<b>📥 Введите @username получателя в CryptoBot</b>\n"
"➖➖➖➖➖➖➖➖➖➖\n"
"<i>Пример: @username</i>"
)
return (
"<b>📥 Введите ссылку на профиль получателя Lolzteam</b>\n"
"➖➖➖➖➖➖➖➖➖➖\n"
"<i>Подойдут ссылки с zelenka.guru, lolz.team, lolz.live или lolz.guru</i>"
)
def withdrawal_method_title(withdrawal_method: str) -> str:
if withdrawal_method == "Cryptobot":
return "CryptoBot"
return withdrawal_method
async def get_referral_menu_data(user_id: int, bot: Bot):
get_settings = await Settingsx().get()
if get_settings.status_referral != "True":
return None
get_user = await Userx().get_required(user_id=user_id)
get_referrals = await Referralx().gets(referrer_id=user_id)
bot_username = get_settings.misc_bot
if bot_username == "None":
bot_username = (await bot.get_me()).username or ""
referral_link = f"https://t.me/{bot_username}?start=r_{user_id}"
available_balance = round(
get_user.user_referral_balance - get_user.user_referral_hold,
2,
)
referral_text = ded(f"""
<b>🤝 Реферальная система</b>
➖➖➖➖➖➖➖➖➖➖
▪️ Приглашено пользователей: <code>{len(get_referrals)}</code>
▪️ Реферальный баланс: <code>{get_user.user_referral_balance}₽</code>
▪️ Доступно: <code>{available_balance}₽</code>
➖➖➖➖➖➖➖➖➖➖
▪️ Бонус за приглашение: <code>{get_settings.referral_bonus_rub}₽</code>
▪️ Доход с пополнений: <code>{get_settings.referral_refill_percent}%</code>
""")
return referral_text, referral_menu_finl(referral_link)
@router.message(F.text == "🤝 Реферальная система")
async def show_referral_menu(
message: Message,
bot: Bot,
state: FSM,
arSession: ARS,
user_id: int | None = None,
):
await state.clear()
user_id = user_id or message.from_user.id
referral_menu_data = await get_referral_menu_data(user_id, bot)
if referral_menu_data is None:
return await message.answer("<b>⛔ Реферальная система временно отключена</b>")
referral_text, referral_keyboard = referral_menu_data
await message.answer(
referral_text,
reply_markup=referral_keyboard,
disable_web_page_preview=True,
)
@router.callback_query(F.data == "referral_menu")
async def user_referral_menu_return(
call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS
):
await state.clear()
referral_menu_data = await get_referral_menu_data(call.from_user.id, bot)
if referral_menu_data is None:
return await call.answer("⛔ Реферальная система временно отключена", True)
referral_text, referral_keyboard = referral_menu_data
await call.message.edit_text(
referral_text,
reply_markup=referral_keyboard,
disable_web_page_preview=True,
)
@router.callback_query(F.data == "referral_transfer")
async def user_referral_transfer_start(
call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS
):
get_settings = await Settingsx().get()
if get_settings.status_referral != "True":
await state.clear()
return await call.answer("⛔ Реферальная система временно отключена", True)
await state.clear()
await state.set_state("here_referral_transfer_amount")
await call.message.edit_text(
"<b>💸 Введите сумму для перевода на основной баланс</b>",
reply_markup=referral_transfer_method_finl(),
)
@router.callback_query(F.data == "referral_withdrawal")
async def user_referral_withdrawal_start(
call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS
):
get_settings = await Settingsx().get()
if get_settings.status_referral != "True":
await state.clear()
return await call.answer("⛔ Реферальная система временно отключена", True)
await state.clear()
await call.message.edit_text(
"<b>📥 Выберите способ вывода средств</b>",
reply_markup=referral_withdrawal_method_finl(),
)
@router.callback_query(F.data.startswith("referral_withdrawal_method:"))
async def user_referral_withdrawal_method(
call: CallbackQuery, bot: Bot, state: FSM, arSession: ARS
):
withdrawal_method = call.data.split(":", 1)[1]
if withdrawal_method not in ("Cryptobot", "Lolzteam"):
return await call.answer("❌ Способ вывода не найден", True)
get_settings = await Settingsx().get()
if get_settings.status_referral != "True":
await state.clear()
return await call.answer("⛔ Реферальная система временно отключена", True)
await state.clear()
await state.update_data(here_referral_withdrawal_method=withdrawal_method)
await state.set_state("here_referral_withdrawal_amount")
await call.message.edit_text("<b>📥 Введите сумму для вывода</b>")
@router.message(F.text, StateFilter("here_referral_withdrawal_amount"))
async def user_referral_withdrawal_amount(
message: Message, bot: Bot, state: FSM, arSession: ARS
):
get_settings = await Settingsx().get()
if get_settings.status_referral != "True":
await state.clear()
return await message.answer("<b>⛔ Реферальная система временно отключена</b>")
if not is_number(message.text):
return await message.answer("<b>❌ Введите положительную сумму в рублях</b>")
withdrawal_amount = float(to_number(message.text))
if not isfinite(withdrawal_amount) or withdrawal_amount <= 0:
return await message.answer("<b>❌ Введите положительную сумму в рублях</b>")
get_user = await Userx().get_required(user_id=message.from_user.id)
available_balance = round(
get_user.user_referral_balance - get_user.user_referral_hold, 2
)
if withdrawal_amount > available_balance:
return await message.answer(
"<b>❌ Недостаточно доступных средств на реферальном балансе</b>"
)
withdrawal_data = await state.get_data()
withdrawal_method = withdrawal_data.get("here_referral_withdrawal_method")
if withdrawal_method not in ("Cryptobot", "Lolzteam"):
await state.clear()
return await message.answer("<b>❌ Данные вывода устарели. Начните заново</b>")
await state.update_data(here_referral_withdrawal_amount=withdrawal_amount)
await state.set_state("here_referral_withdrawal_recipient")
await message.answer(withdrawal_recipient_hint(withdrawal_method))
@router.message(F.text, StateFilter("here_referral_withdrawal_recipient"))
async def user_referral_withdrawal_recipient(
message: Message, bot: Bot, state: FSM, arSession: ARS
):
get_settings = await Settingsx().get()
if get_settings.status_referral != "True":
await state.clear()
return await message.answer("<b>⛔ Реферальная система временно отключена</b>")
withdrawal_data = await state.get_data()
withdrawal_method = withdrawal_data.get("here_referral_withdrawal_method")
withdrawal_amount = withdrawal_data.get("here_referral_withdrawal_amount")
if withdrawal_method is None or withdrawal_amount is None:
await state.clear()
return await message.answer("<b>❌ Данные вывода устарели. Начните заново</b>")
withdrawal_recipient = clear_html(message.text).strip()
if not withdrawal_recipient:
return await message.answer(
"<b>❌ Введите реквизиты</b>\n"
f"{withdrawal_recipient_hint(withdrawal_method)}"
)
if not is_valid_withdrawal_recipient(withdrawal_method, withdrawal_recipient):
return await message.answer(
"<b>❌ Некорректные реквизиты</b>\n"
f"{withdrawal_recipient_hint(withdrawal_method)}"
)
withdrawal_status, withdrawal = await ReferralWithdrawalx().create_pending(
user_id=message.from_user.id,
withdrawal_amount=withdrawal_amount,
withdrawal_method=withdrawal_method,
withdrawal_recipient=withdrawal_recipient,
)
if withdrawal_status == "INSUFFICIENT_FUNDS":
return await message.answer(
"<b>❌ Недостаточно доступных средств на реферальном балансе</b>"
)
if withdrawal_status in ("INVALID_AMOUNT", "INVALID_RECIPIENT", "INVALID_METHOD"):
return await message.answer("<b>❌ Некорректные данные для вывода</b>")
if withdrawal_status == "USER_NOT_FOUND":
await state.clear()
return await message.answer(
"<b>❌ Пользователь не найден. Обратитесь в поддержку</b>"
)
if withdrawal_status != "ok" or withdrawal is None:
return await message.answer(
"<b>❌ Не удалось создать заявку. Попробуйте позже</b>"
)
await state.clear()
get_user = await Userx().get_required(user_id=message.from_user.id)
method_title = withdrawal_method_title(withdrawal.withdrawal_method)
await message.answer(
ded(f"""
<b>📥 Заявка на вывод создана</b>
➖➖➖➖➖➖➖➖➖➖
▪️ Номер заявки: <code>#{withdrawal.increment}</code>
▪️ Сумма: <code>{withdrawal.withdrawal_amount}₽</code>
▪️ Способ: <code>{method_title}</code>
"""),
)
await send_admins(
bot,
ded(f"""
<b>📥 Новая заявка на вывод</b>
▪️ Номер заявки: <code>#{withdrawal.increment}</code>
▪️ Пользователь: <b>@{get_user.user_login}</b> | <a href='tg://user?id={get_user.user_id}'>{get_user.user_name}</a> | <code>{get_user.user_id}</code>
▪️ Сумма: <code>{withdrawal.withdrawal_amount}₽</code>
▪️ Способ: <code>{method_title}</code>
▪️ Реквизиты: <code>{withdrawal.withdrawal_recipient}</code>
"""),
keyboard=referral_withdrawal_actions_finl(withdrawal.increment),
)
@router.message(F.text, StateFilter("here_referral_transfer_amount"))
async def user_referral_transfer_amount(
message: Message, bot: Bot, state: FSM, arSession: ARS
):
get_settings = await Settingsx().get()
if get_settings.status_referral != "True":
await state.clear()
return await message.answer("<b>⛔ Реферальная система временно отключена</b>")
if not is_number(message.text):
return await message.answer("<b>❌ Введите положительную сумму в рублях</b>")
transfer_amount = float(to_number(message.text))
if not isfinite(transfer_amount) or transfer_amount <= 0:
return await message.answer("<b>❌ Введите положительную сумму в рублях</b>")
transfer_status = await Referralx().transfer_to_main_balance(
user_id=message.from_user.id,
amount=transfer_amount,
)
if transfer_status == "INSUFFICIENT_FUNDS":
return await message.answer(
"<b>❌ Недостаточно доступных средств на реферальном балансе</b>"
)
if transfer_status == "INVALID_AMOUNT":
return await message.answer("<b>❌ Введите положительную сумму в рублях</b>")
if transfer_status == "USER_NOT_FOUND":
await state.clear()
return await message.answer(
"<b>❌ Пользователь не найден. Обратитесь в поддержку</b>"
)
if transfer_status != "ok":
return await message.answer(
"<b>❌ Не удалось выполнить перевод. Попробуйте позже</b>"
)
await state.clear()
get_user = await Userx().get_required(user_id=message.from_user.id)
await message.answer(
ded(f"""
<b>💸 Средства переведены на основной баланс</b>
➖➖➖➖➖➖➖➖➖➖
▪️ Переведено: <code>{round(transfer_amount, 2)}₽</code>
▪️ Основной баланс: <code>{get_user.user_balance}₽</code>
▪️ Реферальный баланс: <code>{get_user.user_referral_balance}₽</code>
"""),
)
+23 -1
View File
@@ -5,7 +5,7 @@ from aiogram import Router, Bot, F
from aiogram.filters import StateFilter from aiogram.filters import StateFilter
from aiogram.types import CallbackQuery, Message, PreCheckoutQuery from aiogram.types import CallbackQuery, Message, PreCheckoutQuery
from tgbot.database import Paymentsx, Refillx, Userx, Settingsx from tgbot.database import Paymentsx, ReferralTransactionx, Refillx, Userx, Settingsx
from tgbot.keyboards.inline_user import refill_bill_finl, refill_method_finl from tgbot.keyboards.inline_user import refill_bill_finl, refill_method_finl
from tgbot.services.api_cryptobot import CryptobotAPI from tgbot.services.api_cryptobot import CryptobotAPI
from tgbot.services.api_lolzteam import LolzteamAPI from tgbot.services.api_lolzteam import LolzteamAPI
@@ -459,4 +459,26 @@ async def save_refill_success(
"""), """),
) )
referral_reward = await ReferralTransactionx().get_refill_reward(
f"refill:{pay_comment or pay_receipt}"
)
if referral_reward is not None:
try:
await bot.send_message(
referral_reward.user_id,
ded(f"""
<b>🤝 Реферальное начисление</b>
▪️ Ваш реферал: <a href='tg://user?id={get_user.user_id}'>{get_user.user_name}</a>
▪️ Пополнение: <code>{pay_amount}₽</code>
▪️ Начислено: <code>{referral_reward.amount}₽</code>
"""),
)
except Exception:
bot_logger.warning(
"Не удалось отправить уведомление о реферальном начислении",
exc_info=True,
)
return response_success, pay_receipt return response_success, pay_receipt