Добавлены новые поля для агентской комиссии в модели Company и CompanyProfileResponse. Реализованы функции для обработки продаж через интеграционный API, включая создание и регистрацию продаж с учетом агентской комиссии. Обновлены соответствующие эндпоинты и модели для работы с токенами и продажами. Улучшена логика обработки транзакций и обновления балансов компаний и агентов.
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user