from fastapi import APIRouter, Depends, HTTPException, status from sqlalchemy.orm import Session from sqlalchemy import func as sql_func from datetime import datetime from typing import List from ..database import get_db from ..models import User, WithdrawalRequest, DealerInfo, ShareReward, ReferralReward from ..schemas import ( WithdrawalRequestCreate, WithdrawalRequestResponse, WithdrawalProcess, WithdrawalBalance, ) from .auth import get_current_user from .notification import notify_withdrawal_processed router = APIRouter(prefix="/withdrawal", tags=["withdrawal"]) # Tax rate (3.3% withholding) TAX_RATE = 0.033 def calculate_user_balance(user: User, db: Session) -> WithdrawalBalance: """Calculate user's withdrawal balance from all sources""" total_earned = 0.0 total_withdrawn = 0.0 pending_withdrawal = 0.0 # Get dealer earnings if user is a dealer if user.is_dealer: dealer_info = db.query(DealerInfo).filter(DealerInfo.user_id == user.id).first() if dealer_info: total_earned += dealer_info.total_commission_earned total_withdrawn += dealer_info.total_withdrawn # Get share rewards share_rewards = db.query(ShareReward).filter( ShareReward.user_id == user.id, ShareReward.status.in_(["approved", "withdrawn"]) ).all() for reward in share_rewards: total_earned += reward.net_amount if reward.status == "withdrawn": total_withdrawn += reward.net_amount # Get referral rewards referral_rewards = db.query(ReferralReward).filter( ReferralReward.referrer_id == user.id, ReferralReward.status.in_(["credited", "withdrawn"]) ).all() for reward in referral_rewards: total_earned += reward.reward_amount if reward.status == "withdrawn": total_withdrawn += reward.reward_amount # Get pending withdrawals pending_requests = db.query(WithdrawalRequest).filter( WithdrawalRequest.user_id == user.id, WithdrawalRequest.status.in_(["pending", "approved"]) ).all() for req in pending_requests: pending_withdrawal += req.net_amount available_balance = total_earned - total_withdrawn - pending_withdrawal return WithdrawalBalance( total_earned=total_earned, total_withdrawn=total_withdrawn, pending_withdrawal=pending_withdrawal, available_balance=max(0, available_balance) ) @router.get("/balance", response_model=WithdrawalBalance) def get_balance( current_user: User = Depends(get_current_user), db: Session = Depends(get_db) ): """Get current user's withdrawal balance""" return calculate_user_balance(current_user, db) @router.post("/request", response_model=WithdrawalRequestResponse) def create_withdrawal_request( request_data: WithdrawalRequestCreate, current_user: User = Depends(get_current_user), db: Session = Depends(get_db) ): """Create a new withdrawal request""" # Check balance balance = calculate_user_balance(current_user, db) if request_data.amount <= 0: raise HTTPException(status_code=400, detail="Amount must be positive") if request_data.amount > balance.available_balance: raise HTTPException( status_code=400, detail=f"Insufficient balance. Available: {balance.available_balance}" ) # Minimum withdrawal amount MIN_WITHDRAWAL = 10 # 10 USD minimum if request_data.amount < MIN_WITHDRAWAL: raise HTTPException( status_code=400, detail=f"Minimum withdrawal amount is ${MIN_WITHDRAWAL} USD" ) # Calculate tax and net amount tax_amount = request_data.amount * TAX_RATE net_amount = request_data.amount - tax_amount # Create withdrawal request withdrawal = WithdrawalRequest( user_id=current_user.id, amount=request_data.amount, tax_withheld=tax_amount, net_amount=net_amount, bank_name=request_data.bank_name, bank_account=request_data.bank_account, account_holder=request_data.account_holder, status="pending" ) db.add(withdrawal) db.commit() db.refresh(withdrawal) return withdrawal @router.get("/my-requests", response_model=List[WithdrawalRequestResponse]) def get_my_requests( current_user: User = Depends(get_current_user), db: Session = Depends(get_db) ): """Get current user's withdrawal requests""" requests = db.query(WithdrawalRequest).filter( WithdrawalRequest.user_id == current_user.id ).order_by(WithdrawalRequest.requested_at.desc()).all() return requests # Admin endpoints @router.get("/admin/list", response_model=List[WithdrawalRequestResponse]) def get_all_requests( status_filter: str = None, current_user: User = Depends(get_current_user), db: Session = Depends(get_db) ): """[Admin] Get all withdrawal requests""" if not current_user.is_admin: raise HTTPException(status_code=403, detail="Admin access required") query = db.query(WithdrawalRequest) if status_filter: query = query.filter(WithdrawalRequest.status == status_filter) requests = query.order_by(WithdrawalRequest.requested_at.desc()).all() return requests @router.put("/admin/{request_id}/process", response_model=WithdrawalRequestResponse) def process_withdrawal( request_id: int, process_data: WithdrawalProcess, current_user: User = Depends(get_current_user), db: Session = Depends(get_db) ): """[Admin] Process a withdrawal request""" if not current_user.is_admin: raise HTTPException(status_code=403, detail="Admin access required") withdrawal = db.query(WithdrawalRequest).filter( WithdrawalRequest.id == request_id ).first() if not withdrawal: raise HTTPException(status_code=404, detail="Request not found") valid_statuses = ["approved", "completed", "rejected"] if process_data.status not in valid_statuses: raise HTTPException( status_code=400, detail=f"Invalid status. Must be one of: {valid_statuses}" ) # Update status withdrawal.status = process_data.status withdrawal.admin_note = process_data.admin_note withdrawal.processed_at = datetime.utcnow() # If completed, update user's withdrawal totals if process_data.status == "completed": user = db.query(User).filter(User.id == withdrawal.user_id).first() # Update dealer info if applicable if user.is_dealer: dealer_info = db.query(DealerInfo).filter( DealerInfo.user_id == user.id ).first() if dealer_info: dealer_info.total_withdrawn += withdrawal.net_amount # Mark related share rewards as withdrawn # (This is a simplified version - in production you'd track which specific rewards were withdrawn) db.commit() db.refresh(withdrawal) # Send notification to user about withdrawal status notify_withdrawal_processed(db, withdrawal.user_id, withdrawal.id, process_data.status, withdrawal.net_amount) return withdrawal