from typing import Any, List from fastapi import APIRouter, Depends, HTTPException, status from sqlalchemy.orm import Session from app import crud, models, schemas from app.api import deps from app.models.transaction import TransactionType from app.models.wallet import WalletType from app.core.email import send_withdrawal_confirmation from app.core.config import settings router = APIRouter() @router.post("/request", response_model=schemas.Withdrawal) def create_withdrawal_request( *, db: Session = Depends(deps.get_db), current_user: models.User = Depends(deps.get_current_active_verified_user), withdrawal_data: schemas.WithdrawalRequest, ) -> Any: """ Create a new withdrawal request. """ # Check if user is KYC verified (if required) if not current_user.is_kyc_verified: raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="KYC verification required for withdrawals", ) # Validate minimum withdrawal amount if withdrawal_data.amount < settings.MIN_WITHDRAWAL_AMOUNT: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail=f"Minimum withdrawal amount is {settings.MIN_WITHDRAWAL_AMOUNT} USDT", ) # Get user's spot wallet spot_wallet = crud.wallet.get_by_user_and_type( db, user_id=current_user.id, wallet_type=WalletType.SPOT ) if not spot_wallet: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="Spot wallet not found", ) # Calculate fee fee = withdrawal_data.amount * (settings.WITHDRAWAL_FEE_PERCENTAGE / 100) total_amount = withdrawal_data.amount + fee # Check if user has enough balance if spot_wallet.balance < total_amount: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail=f"Insufficient funds. Required: {total_amount} USDT (including {fee} USDT fee)", ) # Create withdrawal request withdrawal_in = schemas.WithdrawalCreate( user_id=current_user.id, amount=withdrawal_data.amount, fee=fee, wallet_address=withdrawal_data.wallet_address, ) withdrawal = crud.withdrawal.create(db, obj_in=withdrawal_in) # Reserve the funds by reducing the wallet balance crud.wallet.update_balance(db, wallet_id=spot_wallet.id, amount=total_amount, add=False) # Create transaction record for the reservation crud.transaction.create( db, obj_in=schemas.TransactionCreate( user_id=current_user.id, wallet_id=spot_wallet.id, amount=-total_amount, transaction_type=TransactionType.WITHDRAWAL, description="Withdrawal request - Reserved funds", withdrawal_id=withdrawal.id, ), ) return withdrawal @router.get("/", response_model=List[schemas.Withdrawal]) def read_withdrawals( *, db: Session = Depends(deps.get_db), current_user: models.User = Depends(deps.get_current_active_verified_user), skip: int = 0, limit: int = 100, ) -> Any: """ Retrieve user's withdrawal requests. """ withdrawals = crud.withdrawal.get_by_user(db, user_id=current_user.id, skip=skip, limit=limit) return withdrawals @router.get("/{withdrawal_id}", response_model=schemas.Withdrawal) def read_withdrawal( *, db: Session = Depends(deps.get_db), current_user: models.User = Depends(deps.get_current_active_verified_user), withdrawal_id: int, ) -> Any: """ Get a specific withdrawal by ID. """ withdrawal = crud.withdrawal.get(db, id=withdrawal_id) if not withdrawal: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Withdrawal not found", ) if withdrawal.user_id != current_user.id and current_user.role != "admin": raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="Access denied", ) return withdrawal # Admin endpoints @router.get("/admin/pending", response_model=List[schemas.Withdrawal]) def read_pending_withdrawals( *, db: Session = Depends(deps.get_db), current_user: models.User = Depends(deps.get_current_admin), skip: int = 0, limit: int = 100, ) -> Any: """ Retrieve all pending withdrawal requests (admin only). """ withdrawals = crud.withdrawal.get_all_pending(db, skip=skip, limit=limit) return withdrawals @router.put("/admin/{withdrawal_id}/approve", response_model=schemas.Withdrawal) def approve_withdrawal_request( *, db: Session = Depends(deps.get_db), current_user: models.User = Depends(deps.get_current_admin), withdrawal_id: int, withdrawal_data: schemas.WithdrawalApprove, ) -> Any: """ Approve a withdrawal request (admin only). """ withdrawal = crud.withdrawal.get(db, id=withdrawal_id) if not withdrawal: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Withdrawal not found", ) if withdrawal.status != "pending": raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail=f"Withdrawal is already {withdrawal.status}", ) # Approve withdrawal updated_withdrawal = crud.withdrawal.approve( db, db_obj=withdrawal, transaction_hash=withdrawal_data.transaction_hash, admin_notes=withdrawal_data.admin_notes ) # Send confirmation email to user user = crud.user.get(db, id=withdrawal.user_id) if user: send_withdrawal_confirmation( email_to=user.email, amount=withdrawal.amount, transaction_id=withdrawal_data.transaction_hash, ) return updated_withdrawal @router.put("/admin/{withdrawal_id}/reject", response_model=schemas.Withdrawal) def reject_withdrawal_request( *, db: Session = Depends(deps.get_db), current_user: models.User = Depends(deps.get_current_admin), withdrawal_id: int, withdrawal_data: schemas.WithdrawalReject, ) -> Any: """ Reject a withdrawal request (admin only). """ withdrawal = crud.withdrawal.get(db, id=withdrawal_id) if not withdrawal: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Withdrawal not found", ) if withdrawal.status != "pending": raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail=f"Withdrawal is already {withdrawal.status}", ) # Get user's spot wallet spot_wallet = crud.wallet.get_by_user_and_type( db, user_id=withdrawal.user_id, wallet_type=WalletType.SPOT ) if not spot_wallet: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="User's spot wallet not found", ) # Calculate total amount (amount + fee) total_amount = withdrawal.amount + withdrawal.fee # Refund the reserved funds crud.wallet.update_balance(db, wallet_id=spot_wallet.id, amount=total_amount, add=True) # Create transaction record for the refund crud.transaction.create( db, obj_in=schemas.TransactionCreate( user_id=withdrawal.user_id, wallet_id=spot_wallet.id, amount=total_amount, transaction_type=TransactionType.ADMIN_ADJUSTMENT, description="Withdrawal rejected - Funds returned", withdrawal_id=withdrawal.id, ), ) # Reject withdrawal updated_withdrawal = crud.withdrawal.reject( db, db_obj=withdrawal, admin_notes=withdrawal_data.admin_notes ) return updated_withdrawal