robokassa nomenclature/ minor fixes for API
This commit is contained in:
+19
-2
@@ -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
@@ -1,5 +1,5 @@
|
||||
from datetime import datetime
|
||||
from typing import Literal, Optional, Union
|
||||
from typing import Literal
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
@@ -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
@@ -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)
|
||||
|
||||
|
||||
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user