249 lines
7.7 KiB
Python

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