249 lines
7.7 KiB
Python
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 |