from fastapi import APIRouter, Depends, HTTPException, Body, Header, Request from sqlalchemy.orm import Session from core.db import get_db, Sale, User, Poster from core.pay import YooKassaPayment from pydantic import BaseModel, Field from typing import Optional, List from datetime import datetime import json class PaymentRequest(BaseModel): amount: float currency: str description: str return_url: str user_id: Optional[int] = Field(None) # Делаем необязательным poster_id: Optional[int] = None router = APIRouter() # Конфигурация YooKassa SHOP_ID = "1017909" API_KEY = "test_udCBy7nXqGavVoj3RrzIRXN9UL02_UnF0FBBykxWH60" payment_service = YooKassaPayment(SHOP_ID, API_KEY) @router.post("/create-payment/") async def create_payment( payment_data: dict = Body(...), # Принимаем любой JSON db: Session = Depends(get_db) ): try: # Логирование для отладки print(f"Received payment data: {json.dumps(payment_data, ensure_ascii=False)}") # Создание платежа через YooKassa payment_response = payment_service.create_payment( amount=float(payment_data.get("amount", 0)), currency=payment_data.get("currency", "RUB"), description=payment_data.get("description", ""), return_url=payment_data.get("return_url", "http://localhost:3000/payments") ) # Логирование ответа YooKassa print(f"YooKassa response: {json.dumps(payment_response, ensure_ascii=False)}") # Сохранение данных о платеже в БД new_sale = Sale( amount=float(payment_data.get("amount", 0)), status="pending", description=payment_data.get("description", ""), poster_id=payment_data.get("poster_id"), user_id=payment_data.get("user_id", 1), # Используем 1 как значение по умолчанию user_email=payment_data.get("user_email", "example@example.com"), payment_id=payment_response["id"], confirmation_url=payment_response["confirmation"]["confirmation_url"] ) db.add(new_sale) db.commit() db.refresh(new_sale) return {"confirmation_url": new_sale.confirmation_url, "payment_id": payment_response["id"]} except Exception as e: print(f"ERROR creating payment: {str(e)}") raise HTTPException(status_code=400, detail=str(e)) @router.get("/payment-status/{payment_id}") async def get_payment_status(payment_id: str, db: Session = Depends(get_db)): try: print(f"Checking payment status for ID: {payment_id}") # Получение информации о платеже из БД sale = db.query(Sale).filter(Sale.payment_id == payment_id).first() if not sale: raise HTTPException(status_code=404, detail=f"Платеж с ID {payment_id} не найден") # Обновление статуса из YooKassa yookassa_status = payment_service.get_payment_status(payment_id) if yookassa_status and "status" in yookassa_status: # Маппинг статусов YooKassa на наши статусы if yookassa_status["status"] == "succeeded" or yookassa_status["paid"] == True: sale.status = "paid" elif yookassa_status["status"] == "canceled": sale.status = "canceled" elif yookassa_status["status"] == "waiting_for_capture": sale.status = "waiting_for_capture" # Сохраняем обновленный статус db.commit() print(f"Updated payment status from YooKassa: {yookassa_status['status']} -> {sale.status}") return {"payment_id": sale.payment_id, "status": sale.status} except Exception as e: print(f"ERROR checking payment status: {str(e)}") raise HTTPException(status_code=500, detail=str(e)) @router.post("/notifications/") async def process_notification(request: Request, db: Session = Depends(get_db)): try: # Получаем тело запроса notification_data = await request.json() print(f"Received notification: {json.dumps(notification_data, ensure_ascii=False)}") # Проверяем тип события event = notification_data.get("event") payment_id = notification_data.get("object", {}).get("id") if not payment_id: raise HTTPException(status_code=400, detail="Missing payment ID") # Находим платеж в БД sale = db.query(Sale).filter(Sale.payment_id == payment_id).first() if not sale: raise HTTPException(status_code=404, detail=f"Payment {payment_id} not found") # Обновляем статус платежа в зависимости от события if event == "payment.waiting_for_capture": sale.status = "waiting_for_capture" elif event == "payment.succeeded": sale.status = "paid" elif event == "payment.canceled": sale.status = "canceled" else: print(f"Unknown event: {event}") db.commit() return {"success": True} except Exception as e: print(f"ERROR processing notification: {str(e)}") raise HTTPException(status_code=500, detail=str(e)) @router.post("/update-payment-status/") async def manual_update_payment_status(payment_id: str, new_status: str, db: Session = Depends(get_db)): try: sale = db.query(Sale).filter(Sale.payment_id == payment_id).first() if not sale: raise HTTPException(status_code=404, detail="Платеж не найден") # Обновляем статус sale.status = new_status db.commit() return {"message": "Статус платежа обновлен", "payment_id": sale.payment_id, "status": sale.status} except Exception as e: print(f"ERROR updating payment status: {str(e)}") raise HTTPException(status_code=500, detail=str(e)) @router.get("/user-tickets/{user_id}") async def get_user_tickets(user_id: int, db: Session = Depends(get_db)): """Получение всех оплаченных билетов пользователя""" try: # Проверяем существование пользователя user = db.query(User).filter(User.id == user_id).first() if not user: raise HTTPException(status_code=404, detail="Пользователь не найден") # Получаем все успешно оплаченные билеты пользователя sales = db.query(Sale).filter(Sale.user_id == user_id, Sale.status == "paid").all() result = [] for sale in sales: # Получаем информацию о мероприятии poster = None if sale.poster_id: poster = db.query(Poster).filter(Poster.id == sale.poster_id).first() # Формирование даты покупки purchase_date = sale.date if hasattr(sale, 'date') else datetime.now().isoformat() ticket_data = { "id": sale.id, "amount": sale.amount, "description": sale.description, "payment_id": sale.payment_id, "purchase_date": purchase_date, "poster": None } if poster: # Используем только те поля, которые точно есть в модели Poster ticket_data["poster"] = { "id": poster.id, "title": poster.title, "date": poster.date # Убрали поле img, которого нет в модели } result.append(ticket_data) return result except HTTPException as he: # Пробрасываем дальше HTTPException raise he except Exception as e: print(f"ERROR getting user tickets: {str(e)}") raise HTTPException(status_code=500, detail=str(e)) # Для тестирования, также добавим временный эндпоинт для обновления статуса билета @router.post("/update-ticket-status/{sale_id}") async def update_ticket_status(sale_id: int, status: str, db: Session = Depends(get_db)): """Временный эндпоинт для обновления статуса билета (для тестирования)""" try: sale = db.query(Sale).filter(Sale.id == sale_id).first() if not sale: raise HTTPException(status_code=404, detail="Билет не найден") sale.status = status db.commit() return {"success": True, "message": f"Статус билета с ID {sale_id} обновлен на {status}"} except Exception as e: print(f"ERROR updating ticket status: {str(e)}") raise HTTPException(status_code=500, detail=str(e))