partner-core/integration_api.py

318 lines
14 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

from fastapi import FastAPI, Depends, HTTPException, status, Header, Body, Request
from sqlmodel import Session, select, Field
from typing import Optional, List, Dict
from datetime import datetime, timedelta
import hashlib
import uuid
from random import choices
import string
from sql_models import Company, IntegrationToken, Ref, Sale, AgentTransaction, PartnerTransaction, AgentBalance, TgAgent, CompanyBalance
from integration_models import Token, SaleCreateRequest, SaleCreateResponse, TransactionStatus, WithdrawRequest, WithdrawResponse
from bff_models import RegisterResponse, TgAuthResponse
from tg_models import RefAddRequest, RefResponse, RefAddResponse, RefStatResponse, RegisterRequest, StatResponse
from helpers_bff import AUTH_DB_ENGINE, get_integration_db, create_integration_jwt_token, get_current_company_from_jwt, get_tg_agent_by_tg_id, get_current_tg_agent
app = FastAPI()
#7c06945b-f4b8-4929-8350-b9841405a609
@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 токен.
"""
if not x_api_key:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="API-ключ не предоставлен",
headers={"WWW-Authenticate": "Bearer"},
)
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()
if not integration_token_db:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Неверный API-ключ",
headers={"WWW-Authenticate": "Bearer"},
)
# Обновляем use_dttm токена
integration_token_db.use_dttm = datetime.utcnow()
db.add(integration_token_db)
db.commit()
db.refresh(integration_token_db)
jwt_token = create_integration_jwt_token(integration_token_db.company_id)
return {"access_token": jwt_token, "token_type": "bearer"}
@app.get("/ref", response_model=List[RefResponse], tags=["agent-tg"])
def get_refs(current_tg_agent: TgAgent = Depends(get_current_tg_agent), db: Session = Depends(get_integration_db)):
"""
Возвращает список реферальных ссылок текущего Telegram-агента.
"""
refs = db.exec(select(Ref).where(Ref.tg_agent_id == current_tg_agent.id)).all()
return [RefResponse(ref=r.ref, description=r.description or "", promocode=r.promocode) for r in refs]
@app.post("/ref/add", tags=["agent-tg"], response_model=RefAddResponse)
def add_ref(req: RefAddRequest, current_tg_agent: TgAgent = Depends(get_current_tg_agent), db: Session = Depends(get_integration_db)):
"""
Добавляет новую реферальную ссылку для текущего Telegram-агента.
"""
# Генерация промокода (логика как была для промокодов)
alphabet = string.ascii_letters + string.digits + "!@#$%^&*"
promocode = ''.join(choices(alphabet, k=8))
while db.exec(select(Ref).where(Ref.promocode == promocode)).first():
promocode = ''.join(choices(alphabet, k=8))
new_ref = Ref(
tg_agent_id=current_tg_agent.id,
ref=str(uuid.uuid4()),
description=req.description,
promocode=promocode
)
db.add(new_ref)
db.commit()
db.refresh(new_ref)
return {"ref": new_ref.ref, "promocode": new_ref.promocode, "description": new_ref.description}
@app.get("/ref/stat", tags=["agent-tg"], response_model=RefStatResponse)
def get_ref_stat(current_tg_agent: TgAgent = Depends(get_current_tg_agent), db: Session = Depends(get_integration_db)):
"""
Возвращает статистику по реферальным ссылкам текущего Telegram-агента.
"""
# 1. Получаем все реферальные ссылки пользователя
refs = db.exec(select(Ref).where(Ref.tg_agent_id == current_tg_agent.id)).all()
result = []
for ref in refs:
# 2. Для каждой ссылки считаем продажи и сумму
sales = db.exec(select(Sale).where(Sale.ref == ref.id)).all()
sales_count = len(sales)
income = sum(sale.crediting for sale in sales)
result.append({
"description": ref.description or "",
"sales": sales_count,
"income": income
})
return {"refData": result}
@app.get("/stat", tags=["agent-tg"], response_model=StatResponse)
def get_stat(current_tg_agent: TgAgent = Depends(get_current_tg_agent), db: Session = Depends(get_integration_db)):
"""
Возвращает общую статистику для текущего Telegram-агента.
"""
# 1. Получаем все реферальные ссылки пользователя
refs = db.exec(select(Ref).where(Ref.tg_agent_id == current_tg_agent.id)).all()
ref_ids = [r.id for r in refs]
# 2. Считаем totalSales (продажи по всем рефам пользователя)
total_sales = db.exec(select(Sale).where(Sale.ref.in_(ref_ids))).all()
totalSales = len(total_sales)
totalIncome = sum(sale.crediting for sale in total_sales)
# Заменено получение доступного остатка из AgentBalance
agent_balance = db.exec(select(AgentBalance).where(AgentBalance.tg_agent_id == current_tg_agent.id)).first()
availableWithdrawal = agent_balance.available_balance if agent_balance else 0.0
return {
"totalSales": totalSales,
"totalIncome": totalIncome,
"availableWithdrawal": availableWithdrawal
}
@app.post("/tg_auth", tags=["agent-tg"], response_model=TgAuthResponse)
def tg_auth(hash: str = Body(..., embed=True), db: Session = Depends(get_integration_db)):
"""
Авторизует Telegram-агента по хешу.
"""
tg_agent = db.exec(select(TgAgent).where(TgAgent.hash == hash)).first()
if not tg_agent:
raise HTTPException(status_code=401, detail="Hash not found")
return {"msg": "Auth success", "tg_id": tg_agent.tg_id}
@app.post("/withdraw", tags=["agent-tg"], response_model=WithdrawResponse)
async def withdraw_funds(
req: WithdrawRequest,
db: Session = Depends(get_integration_db)
):
"""
Запрос на вывод средств для Telegram-агента.
"""
tg_agent = db.exec(select(TgAgent).where(TgAgent.tg_id == req.tg_id)).first()
if not tg_agent:
raise HTTPException(status_code=404, detail="Telegram-агент не найден")
company = db.exec(select(Company).where(Company.id == tg_agent.company_id)).first()
if not company:
raise HTTPException(status_code=404, detail="Компания не найдена для агента")
if req.amount <= 0:
raise HTTPException(status_code=400, detail="Сумма для вывода должна быть положительной")
agent_balance = db.exec(select(AgentBalance).where(AgentBalance.tg_agent_id == tg_agent.id)).first()
if not agent_balance or agent_balance.available_balance < req.amount:
raise HTTPException(status_code=400, detail="Недостаточно средств на балансе для вывода")
# Определяем статус транзакции
transaction_status = TransactionStatus.WAITING
if company.auto_approve_transactions:
transaction_status = TransactionStatus.NEW
# Создаем запись AgentTransaction
new_agent_transaction = AgentTransaction(
tg_agent_id=tg_agent.id,
amount=req.amount,
status=transaction_status.value,
transaction_group=uuid.uuid4()
)
db.add(new_agent_transaction)
# Обновляем баланс агента
agent_balance.available_balance -= req.amount
if transaction_status == TransactionStatus.WAITING: # Если автоматически одобряется, переводим на замороженный баланс компании (т.е. компания должна выплатить)
agent_balance.frozen_balance += req.amount # Удерживаем средства, пока они не будут выведены
db.add(agent_balance)
db.commit()
db.refresh(new_agent_transaction)
db.refresh(agent_balance)
return {"msg": "Запрос на вывод средств успешно создан", "transaction_id": new_agent_transaction.transaction_group}
@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)
):
"""
Регистрирует новую продажу в системе.
"""
# Устанавливаем уровень изоляции для текущей транзакции
db.connection(execution_options={'isolation_level': 'SERIALIZABLE'})
# Проверка входных данных
if not req.ref and not req.promocode:
raise HTTPException(status_code=400, detail="Необходимо передать либо ref, либо promocode")
# 1. Найти Ref по ref и/или promocode
referral = None
if req.ref and req.promocode:
referral_by_ref = db.exec(select(Ref).where(Ref.ref == req.ref)).first()
referral_by_code = db.exec(select(Ref).where(Ref.promocode == req.promocode)).first()
if not referral_by_ref or not referral_by_code:
raise HTTPException(status_code=404, detail="Реферальная ссылка или промокод не найдены")
if referral_by_ref.id != referral_by_code.id:
raise HTTPException(status_code=400, detail="ref и promocode не соответствуют одной ссылке")
referral = referral_by_ref
elif req.ref:
referral = db.exec(select(Ref).where(Ref.ref == req.ref)).first()
if not referral:
raise HTTPException(status_code=404, detail="Реферальная ссылка не найдена")
elif req.promocode:
referral = db.exec(select(Ref).where(Ref.promocode == req.promocode)).first()
if not referral:
raise HTTPException(status_code=404, detail="Промокод не найден")
# Проверяем, что реф действительно принадлежит компании
tg_agent = db.exec(select(TgAgent).where(TgAgent.id == referral.tg_agent_id, TgAgent.company_id == company.id)).first()
if not tg_agent:
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. Проверить и обновить AgentBalance и CompanyBalance
agent_balance = db.exec(select(AgentBalance).where(AgentBalance.tg_agent_id == tg_agent.id)).first()
if not agent_balance:
raise HTTPException(status_code=404, detail="Баланс агента не найден")
company_balance = db.exec(select(CompanyBalance).where(CompanyBalance.company_id == company.id)).first()
if not company_balance:
raise HTTPException(status_code=404, detail="Баланс компании не найден")
# 5. Создать 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)
# Создать AgentTransaction
agent_transaction_status = TransactionStatus.DONE
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)
# Обновление балансов для продаж - всегда в замороженный/ожидающий баланс
agent_balance.frozen_balance += crediting_amount
company_balance.pending_balance -= crediting_amount
company_balance.updated_dttm = datetime.utcnow()
db.commit()
db.refresh(new_sale)
db.refresh(agent_balance)
db.refresh(company_balance)
db.refresh(agent_transaction)
return {
"msg": "Продажа успешно зарегистрирована",
"sale_id": new_sale.sale_id,
"crediting": new_sale.crediting
}
@app.post("/register", tags=["agent-tg"], response_model=RegisterResponse)
def register(req: RegisterRequest, db: Session = Depends(get_integration_db)):
"""
Регистрирует нового Telegram-агента в системе.
"""
tg_id = req.tg_id
chat_id = req.chat_id
phone = req.phone
name = getattr(req, 'name', None)
login = getattr(req, 'login', None)
company_key = req.company_key
print(f'tg_id: {tg_id}, chat_id: {chat_id}, phone: {phone}, name: {name}, login: {login}, company_key: {company_key}')
tg_agent = get_tg_agent_by_tg_id(db, tg_id)
if tg_agent:
raise HTTPException(status_code=400, detail="tg_id already registered")
# Поиск компании по ключу
company = db.exec(select(Company).where(Company.key == company_key)).first()
if not company:
raise HTTPException(status_code=400, detail="Компания с таким ключом не найдена")
hash_value = hashlib.sha256(f"{tg_id}sold".encode()).hexdigest()
new_tg_agent = TgAgent(
tg_id=tg_id,
chat_id=chat_id,
phone=phone,
name=name,
login=login,
hash=hash_value,
company_id=company.id
)
db.add(new_tg_agent)
db.commit()
db.refresh(new_tg_agent)
return {"msg": "TgAgent registered successfully"}