245 lines
8.2 KiB
Python

import json
import logging
from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy.orm import Session
from app.core.database import get_db
from app.dependencies.auth import get_current_active_user, get_current_admin
from app.models.order import Order, OrderStatus
from app.models.payment import Payment, PaymentMethod, PaymentStatus
from app.models.user import User, UserRole
from app.schemas.payment import (
Payment as PaymentSchema,
)
from app.schemas.payment import (
PaymentCreate,
PaymentResponse,
PaymentUpdate,
)
from app.services.payment import PaymentService
router = APIRouter()
logger = logging.getLogger(__name__)
@router.get("/", response_model=list[PaymentSchema])
async def get_payments(
skip: int = 0,
limit: int = 100,
order_id: str | None = None,
status: PaymentStatus | None = None,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_active_user)
):
"""
Get payments.
Regular users can only see their own payments.
Admins can see all payments and filter by user ID.
"""
query = db.query(Payment)
# Join with orders to filter by user
if current_user.role != UserRole.ADMIN:
query = query.join(Order).filter(Order.user_id == current_user.id)
# Filter by order ID if provided
if order_id:
query = query.filter(Payment.order_id == order_id)
# Filter by status if provided
if status:
query = query.filter(Payment.status == status)
# Apply pagination
payments = query.offset(skip).limit(limit).all()
# Ensure payment details are parsed as JSON
for payment in payments:
if payment.payment_details and isinstance(payment.payment_details, str):
try:
payment.payment_details = json.loads(payment.payment_details)
except:
payment.payment_details = {}
return payments
@router.get("/{payment_id}", response_model=PaymentSchema)
async def get_payment(
payment_id: str,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_active_user)
):
"""
Get a specific payment by ID.
Regular users can only get their own payments.
Admins can get any payment.
"""
payment = db.query(Payment).filter(Payment.id == payment_id).first()
if not payment:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Payment not found"
)
# Check if the payment belongs to the user (if not admin)
if current_user.role != UserRole.ADMIN:
order = db.query(Order).filter(Order.id == payment.order_id).first()
if not order or order.user_id != current_user.id:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Not enough permissions to access this payment"
)
# Parse payment details as JSON
if payment.payment_details and isinstance(payment.payment_details, str):
try:
payment.payment_details = json.loads(payment.payment_details)
except:
payment.payment_details = {}
return payment
@router.post("/process", response_model=PaymentResponse)
async def process_payment(
payment_in: PaymentCreate,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_active_user)
):
"""
Process a payment for an order.
"""
# Check if order exists
order = db.query(Order).filter(Order.id == payment_in.order_id).first()
if not order:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Order not found"
)
# Check if the order belongs to the user (if not admin)
if current_user.role != UserRole.ADMIN and order.user_id != current_user.id:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Not enough permissions to process payment for this order"
)
# Check if order is in a state that can be paid
if order.status != OrderStatus.PENDING and order.status != OrderStatus.FAILED:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=f"Cannot process payment for order with status {order.status.value}"
)
# Process payment based on payment method
try:
if payment_in.payment_method == PaymentMethod.STRIPE:
result = await PaymentService.process_stripe_payment(
db, order, payment_in.payment_details
)
elif payment_in.payment_method == PaymentMethod.PAYPAL:
result = await PaymentService.process_paypal_payment(
db, order, payment_in.payment_details
)
else:
# Process generic payment
result = await PaymentService.process_payment(
db, order, payment_in.payment_method, payment_in.payment_details
)
logger.info(f"Payment processed for order {order.id}: {result}")
return result
except Exception as e:
logger.error(f"Error processing payment: {str(e)}")
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"Error processing payment: {str(e)}"
)
@router.post("/verify/{payment_id}", response_model=PaymentResponse)
async def verify_payment(
payment_id: str,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_active_user)
):
"""
Verify a payment status.
"""
payment = db.query(Payment).filter(Payment.id == payment_id).first()
if not payment:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Payment not found"
)
# Check if the payment belongs to the user (if not admin)
if current_user.role != UserRole.ADMIN:
order = db.query(Order).filter(Order.id == payment.order_id).first()
if not order or order.user_id != current_user.id:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Not enough permissions to verify this payment"
)
# Verify payment
updated_payment = await PaymentService.verify_payment(db, payment_id)
return {
"success": updated_payment.status == PaymentStatus.COMPLETED,
"payment_id": updated_payment.id,
"status": updated_payment.status,
"transaction_id": updated_payment.transaction_id,
"error_message": updated_payment.error_message,
}
@router.put("/{payment_id}", response_model=PaymentSchema)
async def update_payment(
payment_id: str,
payment_in: PaymentUpdate,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_admin)
):
"""
Update a payment (admin only).
"""
payment = db.query(Payment).filter(Payment.id == payment_id).first()
if not payment:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Payment not found"
)
# Update payment attributes
update_data = payment_in.dict(exclude_unset=True)
# Convert payment_details to JSON string if provided
if "payment_details" in update_data and update_data["payment_details"] is not None:
update_data["payment_details"] = json.dumps(update_data["payment_details"])
for key, value in update_data.items():
setattr(payment, key, value)
# If payment status is changing, update order status as well
if payment_in.status and payment_in.status != payment.status:
order = db.query(Order).filter(Order.id == payment.order_id).first()
if order:
if payment_in.status == PaymentStatus.COMPLETED:
order.status = OrderStatus.PROCESSING
elif payment_in.status == PaymentStatus.FAILED:
order.status = OrderStatus.PENDING
elif payment_in.status == PaymentStatus.REFUNDED:
order.status = OrderStatus.REFUNDED
db.commit()
db.refresh(payment)
# Parse payment details for response
if payment.payment_details and isinstance(payment.payment_details, str):
try:
payment.payment_details = json.loads(payment.payment_details)
except:
payment.payment_details = {}
logger.info(f"Payment updated: ID {payment_id}")
return payment