Добавлены новые поля для агентской комиссии в модели Company и CompanyProfileResponse. Реализованы функции для обработки продаж через интеграционный API, включая создание и регистрацию продаж с учетом агентской комиссии. Обновлены соответствующие эндпоинты и модели для работы с токенами и продажами. Улучшена логика обработки транзакций и обновления балансов компаний и агентов.

This commit is contained in:
Redsandyg
2025-06-10 14:15:46 +03:00
parent 076cdd1828
commit 1cc18e0364
9 changed files with 312 additions and 160 deletions

View File

@@ -1,75 +1,169 @@
from fastapi import FastAPI, HTTPException, status, Depends, Request
from fastapi import FastAPI, Depends, HTTPException, status, Header
from fastapi.security import OAuth2PasswordBearer
from sqlmodel import Session, select
from typing import Optional
from datetime import datetime, timedelta
import jwt
from jwt.exceptions import InvalidTokenError
from pydantic import BaseModel
from datetime import datetime
import hashlib
import uuid
from sql_models import Sale
from helpers_bff import get_db, AUTH_DB_ENGINE # Assuming these are the correct imports
from sql_models import Company, IntegrationToken, Ref, Sale, AgentTransaction, PartnerTransaction, AgentBalance, TgAgent, CompanyBalance
from integration_models import Token, SaleCreateRequest, SaleCreateResponse, TransactionStatus
from helpers_bff import AUTH_DB_ENGINE, get_integration_db, create_integration_jwt_token, get_current_company_from_jwt
app = FastAPI()
# Конфигурация для интеграционного API токена
INTEGRATION_SECRET_KEY = "your-integration-super-secret-key" # Смените это на безопасный ключ!
INTEGRATION_ALGORITHM = "HS256"
INTEGRATION_TOKEN_EXPIRE_MINUTES = 60 * 24 # 24 часа
#7c06945b-f4b8-4929-8350-b9841405a609
class IntegrationTokenData(BaseModel):
client_id: str
class SaleCreate(BaseModel):
cost: float
crediting: float
ref_id: int
sale_id: str
company_id: int
sale_date: datetime = datetime.utcnow()
@app.post("/token", tags=["integration"], response_model=Token)
async def get_token_for_api_key(
x_api_key: str = Header(..., alias="X-API-Key"),
db: Session = Depends(get_integration_db)
):
"""
Обменивает API-ключ на JWT токен.
"""
def create_integration_access_token(data: dict, expires_delta: timedelta = None):
to_encode = data.copy()
if expires_delta:
expire = datetime.utcnow() + expires_delta
else:
expire = datetime.utcnow() + timedelta(minutes=INTEGRATION_TOKEN_EXPIRE_MINUTES)
to_encode.update({"exp": expire})
encoded_jwt = jwt.encode(to_encode, INTEGRATION_SECRET_KEY, algorithm=INTEGRATION_ALGORITHM)
return encoded_jwt
if not x_api_key:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="API-ключ не предоставлен",
headers={"WWW-Authenticate": "Bearer"},
)
async def verify_integration_token(request: Request):
credentials_exception = HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Could not validate integration credentials",
headers={"WWW-Authenticate": "Bearer"},
)
auth_header = request.headers.get("Authorization")
if not auth_header or not auth_header.startswith("Bearer "):
raise credentials_exception
token = auth_header.replace("Bearer ", "").strip()
try:
payload = jwt.decode(token, INTEGRATION_SECRET_KEY, algorithms=[INTEGRATION_ALGORITHM])
client_id: str = payload.get("client_id")
if client_id is None:
raise credentials_exception
# Здесь вы можете добавить логику для проверки client_id, например, из базы данных
except InvalidTokenError:
raise credentials_exception
return True # Токен действителен
api_key_hash = hashlib.sha256(x_api_key.encode()).hexdigest()
integration_token_db = db.exec(
select(IntegrationToken).where(IntegrationToken.token_hash == api_key_hash)
).first()
@app.post("/sale", status_code=status.HTTP_201_CREATED)
async def upload_sale(sale_data: SaleCreate, db: Session = Depends(get_db), verified: bool = Depends(verify_integration_token)):
if not verified:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Not authorized")
if not integration_token_db:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Неверный API-ключ",
headers={"WWW-Authenticate": "Bearer"},
)
db_sale = Sale(cost=sale_data.cost, crediting=sale_data.crediting, ref=sale_data.ref_id, sale_id=sale_data.sale_id, company_id=sale_data.company_id, sale_date=sale_data.sale_date)
db.add(db_sale)
# Обновляем use_dttm токена
integration_token_db.use_dttm = datetime.utcnow()
db.add(integration_token_db)
db.commit()
db.refresh(db_sale)
return {"message": "Sale uploaded successfully", "sale_id": db_sale.id}
db.refresh(integration_token_db)
@app.get("/generate-integration-token")
async def generate_token_endpoint(client_id: str):
token_data = {"client_id": client_id}
token = create_integration_access_token(token_data)
return {"access_token": token, "token_type": "bearer"}
jwt_token = create_integration_jwt_token(integration_token_db.company_id)
return {"access_token": jwt_token, "token_type": "bearer"}
@app.post("/sale", tags=["integration"], response_model=SaleCreateResponse)
async def create_sale(
req: SaleCreateRequest,
company: Company = Depends(get_current_company_from_jwt), # Используем новую зависимость
db: Session = Depends(get_integration_db)
):
"""
Регистрирует новую продажу в системе.
"""
# 1. Найти Ref по `ref` и `company.id`
# Сначала находим TgAgent, связанный с компанией, затем Ref
tg_agent = db.exec(
select(TgAgent)
.join(Ref)
.where(TgAgent.company_id == company.id)
.where(Ref.ref == req.ref)
).first()
if not tg_agent:
raise HTTPException(status_code=404, detail="Реферальная ссылка не найдена или не принадлежит данной компании")
referral = db.exec(select(Ref).where(Ref.ref == req.ref).where(Ref.tg_agent_id == tg_agent.id)).first()
if not referral:
raise HTTPException(status_code=404, detail="Реферальная ссылка не найдена")
# 2. Проверить, что sale_id уникален для данной компании
existing_sale = db.exec(
select(Sale)
.where(Sale.company_id == company.id)
.where(Sale.sale_id == req.sale_id)
).first()
if existing_sale:
raise HTTPException(status_code=400, detail="Продажа с таким sale_id уже существует для данной компании")
# 3. Рассчитать crediting
crediting_amount = req.cost * (company.agent_commission / 100.0)
# 4. Создать Sale
new_sale = Sale(
cost=req.cost,
crediting=crediting_amount,
ref=referral.id,
sale_id=req.sale_id,
company_id=company.id,
sale_dttm=datetime.utcnow()
)
db.add(new_sale)
db.commit()
db.refresh(new_sale)
# 5. Обновить AgentBalance и CompanyBalance
# AgentBalance
agent_balance = db.exec(select(AgentBalance).where(AgentBalance.tg_agent_id == tg_agent.id)).first()
if not agent_balance:
agent_balance = AgentBalance(tg_agent_id=tg_agent.id, available_balance=0.0, frozen_balance=0.0)
db.add(agent_balance)
db.commit()
db.refresh(agent_balance)
# CompanyBalance
company_balance = db.exec(select(CompanyBalance).where(CompanyBalance.company_id == company.id)).first()
if not company_balance:
company_balance = CompanyBalance(company_id=company.id, available_balance=0.0, pending_balance=0.0)
db.add(company_balance)
db.commit()
db.refresh(company_balance)
# Создать AgentTransaction
agent_transaction_status = TransactionStatus.NEW if company.auto_approve_transactions else TransactionStatus.WAITING
agent_transaction = AgentTransaction(
tg_agent_id=tg_agent.id,
amount=crediting_amount,
status=agent_transaction_status.value,
transaction_group=uuid.uuid4()
)
db.add(agent_transaction)
db.commit()
db.refresh(agent_transaction)
# Создать PartnerTransaction
partner_transaction_status = TransactionStatus.NEW if company.auto_approve_transactions else TransactionStatus.PROCESS
partner_transaction = PartnerTransaction(
company_id=company.id,
type="sale_crediting",
amount=crediting_amount,
status=partner_transaction_status.value,
transaction_group=agent_transaction.transaction_group,
agent_transaction_id=agent_transaction.id
)
db.add(partner_transaction)
db.commit()
db.refresh(partner_transaction)
# Обновление балансов в зависимости от auto_approve_transactions
if company.auto_approve_transactions:
agent_balance.available_balance += crediting_amount
company_balance.available_balance -= crediting_amount
company_balance.updated_dttm = datetime.utcnow()
else:
agent_balance.frozen_balance += crediting_amount
company_balance.pending_balance -= crediting_amount
company_balance.updated_dttm = datetime.utcnow()
db.add(agent_balance)
db.add(company_balance)
db.commit()
db.refresh(agent_balance)
db.refresh(company_balance)
return {
"msg": "Продажа успешно зарегистрирована",
"sale_id": new_sale.sale_id,
"crediting": new_sale.crediting
}