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