robokassa nomenclature/ minor fixes for API

This commit is contained in:
Vladless
2025-08-10 23:06:26 +03:00
parent 214c9f0c65
commit b999e4aced
5 changed files with 68 additions and 23 deletions
+19 -2
View File
@@ -1,5 +1,5 @@
from fastapi import APIRouter, Depends, HTTPException, Path
from sqlalchemy import select
from sqlalchemy import delete, select
from sqlalchemy.ext.asyncio import AsyncSession
from api.depends import get_session, verify_admin_token
@@ -18,7 +18,7 @@ gift_router = generate_crud_router(
schema_update=GiftUpdate,
identifier_field="gift_id",
parameter_name="gift_id",
enabled_methods=["get_all", "get_one", "create", "update", "delete"],
enabled_methods=["get_all", "get_one", "create", "update"],
)
router.include_router(gift_router, prefix="", tags=["Gifts"])
@@ -46,3 +46,20 @@ gift_usage_router = generate_crud_router(
)
router.include_router(gift_usage_router, prefix="/usages", tags=["GiftUsages"])
router.include_router(gift_usage_router, prefix="/usages", tags=["Gifts"])
@router.delete("/{gift_id}", response_model=dict, tags=["Gifts"])
async def delete_gift_with_usages(
gift_id: str = Path(..., description="ID подарка"),
admin: Admin = Depends(verify_admin_token),
session: AsyncSession = Depends(get_session),
):
result = await session.execute(select(Gift).where(Gift.gift_id == gift_id))
gift = result.scalar_one_or_none()
if not gift:
raise HTTPException(status_code=404, detail="Gift not found")
await session.execute(delete(GiftUsage).where(GiftUsage.gift_id == gift_id))
await session.delete(gift)
await session.commit()
return {"message": "Подарок и связанные использования удалены"}
+1 -1
View File
@@ -1,5 +1,5 @@
from datetime import datetime
from typing import Literal, Optional, Union
from typing import Literal
from pydantic import BaseModel
+4 -2
View File
@@ -18,11 +18,13 @@ async def import_keys_from_3xui_db(db_path: str, session: AsyncSession) -> tuple
skipped = 0
if USE_COUNTRY_SELECTION:
result = await session.execute(select(Server.name).where(Server.enabled is True, Server.panel_type == "3x-ui"))
result = await session.execute(
select(Server.server_name).where(Server.enabled.is_(True), Server.panel_type == "3x-ui")
)
else:
result = await session.execute(
select(Server.cluster_name)
.where(Server.enabled is True, Server.panel_type == "3x-ui", Server.cluster_name.isnot(None))
.where(Server.enabled.is_(True), Server.panel_type == "3x-ui", Server.cluster_name.isnot(None))
.distinct()
)
+5 -1
View File
@@ -123,7 +123,11 @@ class Coupon(DictLikeMixin, Base):
class CouponUsage(DictLikeMixin, Base):
__tablename__ = "coupon_usages"
coupon_id = Column(Integer, ForeignKey("coupons.id"), primary_key=True)
coupon_id = Column(
Integer,
ForeignKey("coupons.id", ondelete="CASCADE"), # Каскадное удаление
primary_key=True
)
user_id = Column(BigInteger, primary_key=True)
used_at = Column(DateTime, default=datetime.utcnow)
+39 -17
View File
@@ -12,6 +12,7 @@ from sqlalchemy.ext.asyncio import AsyncSession
from datetime import datetime, timedelta
from sqlalchemy import select, and_
from pytz import timezone
from typing import Optional
from config import (
ROBOKASSA_ENABLE,
@@ -58,16 +59,35 @@ if ROBOKASSA_ENABLE:
logger.info("Robokassa initialized with login: {}", ROBOKASSA_LOGIN)
def _build_receipt(amount: float, *, sno: Optional[str] = None) -> dict:
receipt = {
"items": [{
"name": "Пополнение баланса",
"quantity": 1,
"sum": float(amount),
"payment_method": "full_payment",
"payment_object": "payment",
"tax": "none",
}]
}
if sno:
receipt["sno"] = sno
return receipt
def generate_payment_link(amount, inv_id, description, tg_id):
"""Генерация ссылки на оплату."""
"""Генерация ссылки на оплату с номенклатурой (Receipt)."""
logger.debug(
f"Generating payment link for amount: {amount}, inv_id: {inv_id}, description: {description}"
)
receipt = _build_receipt(amount)
payment_link = robokassa._payment.link.generate_by_script(
out_sum=amount,
out_sum=float(amount),
inv_id=inv_id,
description=f"Пополнение баланса (tg_id: {tg_id})",
id=f"{tg_id}",
id=str(tg_id),
receipt=receipt
)
logger.info(f"Generated payment link: {payment_link}")
return payment_link
@@ -247,24 +267,26 @@ async def robokassa_webhook(request: web.Request):
def check_payment_signature(params):
"""Проверка подписи запроса от Robokassa с учетом shp_id."""
out_sum = params.get("OutSum")
inv_id = params.get("InvId")
signature_value = params.get("SignatureValue")
shp_id = params.get("shp_id")
"""Проверка подписи ResultURL от Robokassa с учётом всех Shp_*."""
out_sum = params.get("OutSum") or params.get("out_summ") or params.get("outsumm")
inv_id = params.get("InvId") or params.get("inv_id") or params.get("invid")
received_sig = (params.get("SignatureValue") or params.get("signaturevalue") or "").upper()
signature_string = f"{out_sum}:{inv_id}:{ROBOKASSA_PASSWORD2}:shp_id={shp_id}"
if not out_sum or not inv_id or not received_sig:
logger.error("Missing required params for signature check.")
return False
logger.info(f"Signature string before hashing: {signature_string}")
shp_items = [(k, params[k]) for k in params.keys() if k.lower().startswith("shp_")]
shp_items.sort(key=lambda kv: kv[0].lower())
shp_suffix = "".join(f":{k}={v}" for k, v in shp_items)
base = f"{out_sum}:{inv_id}:{ROBOKASSA_PASSWORD2}{shp_suffix}"
expected_sig = hashlib.md5(base.encode("utf-8")).hexdigest().upper()
expected_signature = (
hashlib.md5(signature_string.encode("utf-8")).hexdigest().upper()
)
logger.info(f"Signature base (RESULT): {base}")
logger.info(f"Expected signature: {expected_sig}")
logger.info(f"Received signature: {received_sig}")
logger.info(f"Expected signature: {expected_signature}")
logger.info(f"Received signature: {signature_value}")
return signature_value.upper() == expected_signature.upper()
return received_sig == expected_sig
@router.callback_query(F.data == "enter_custom_amount_robokassa")