biletopad/backend/api/v0/payments.py
2025-06-10 18:21:58 +03:00

213 lines
9.3 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 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))