245 lines
8.2 KiB
Python
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
|