deliveryman-api/app/api/endpoints/withdraw.py
2025-03-26 11:01:44 +08:00

300 lines
9.8 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

from fastapi import APIRouter, Depends
from sqlalchemy.orm import Session
from app.models.withdraw import WithdrawDB, WithdrawCreate, WithdrawInfo, WithdrawStatus
from app.models.user_bank_card import UserBankCardDB
from app.models.user_account import UserAccountDB, AccountDetailDB, AccountDetailType
from app.models.database import get_db
from app.api.deps import get_current_user, get_admin_user
from app.models.user import UserDB
from app.core.response import success_response, error_response, ResponseModel
from decimal import Decimal
from typing import List, Optional, Dict
from datetime import datetime
from pydantic import BaseModel, Field
from app.core.account import AccountManager
from fastapi import BackgroundTasks
from app.core.wecombot import WecomBot
router = APIRouter()
class WithdrawApproveRequest(BaseModel):
"""提现审核请求"""
transaction_id: str = Field(..., max_length=64) # 银行交易流水号
class WithdrawRejectRequest(BaseModel):
"""提现驳回请求"""
remark: str = Field(..., max_length=200) # 驳回原因
@router.post("", response_model=ResponseModel)
async def create_withdraw(
background_tasks: BackgroundTasks,
withdraw: WithdrawCreate,
db: Session = Depends(get_db),
current_user: UserDB = Depends(get_current_user)
):
"""申请提现"""
# 验证银行卡
bank_card = db.query(UserBankCardDB).filter(
UserBankCardDB.id == withdraw.bank_card_id,
UserBankCardDB.user_id == current_user.userid
).first()
if not bank_card:
return error_response(code=404, message="银行卡不存在")
# 最低提现金额
min_withdraw_amount = 10
if withdraw.amount < min_withdraw_amount:
return error_response(code=400, message=f"最低提现金额为{min_withdraw_amount}")
# 验证余额
account = db.query(UserAccountDB).filter(
UserAccountDB.user_id == current_user.userid
).first()
if not account or account.balance < withdraw.amount:
return error_response(code=400, message="余额不足")
try:
# 创建提现记录
withdraw_record = WithdrawDB(
user_id=current_user.userid,
bank_card_id=withdraw.bank_card_id,
amount=withdraw.amount,
status=WithdrawStatus.PENDING
)
db.add(withdraw_record)
# 更新账户余额
account.balance -= Decimal(str(withdraw.amount))
account.lock_balance += Decimal(str(withdraw.amount))
db.commit()
db.refresh(withdraw_record)
# 异步发送提现申请通知
wecombot = WecomBot()
await wecombot.send_withdrawal_apply_notification(withdraw_record)
return success_response(data=WithdrawInfo.model_validate(withdraw_record))
except Exception as e:
db.rollback()
return error_response(code=500, message=f"申请提现失败: {str(e)}")
@router.post("/{withdraw_id}/approve", response_model=ResponseModel)
async def approve_withdraw(
withdraw_id: int,
request: WithdrawApproveRequest,
db: Session = Depends(get_db),
admin: UserDB = Depends(get_admin_user)
):
"""审核通过提现申请(管理员)"""
withdraw = db.query(WithdrawDB).filter(
WithdrawDB.id == withdraw_id,
WithdrawDB.status == WithdrawStatus.PENDING
).first()
if not withdraw:
return error_response(code=404, message="提现申请不存在或状态不正确")
try:
# 更新提现状态
withdraw.status = WithdrawStatus.APPROVED
withdraw.transaction_id = request.transaction_id # 保存交易流水号
withdraw.remark = f"交易流水号: {request.transaction_id}"
# 返还锁定余额
account = db.query(UserAccountDB).filter(
UserAccountDB.user_id == withdraw.user_id
).first()
account.lock_balance -= withdraw.amount
# 使用lock_balance 创建 AccountDetailDB 记录
account_detail = AccountDetailDB(
user_id=withdraw.user_id,
amount=float(withdraw.amount),
type=AccountDetailType.EXPENSE,
description=f"提现到银行卡(尾号{withdraw.bank_card.card_number[-4:]}"
)
db.add(account_detail)
db.commit()
# 异步发送提现审核通知
wecombot = WecomBot()
await wecombot.send_withdrawal_approve_notification(withdraw)
return success_response(data=WithdrawInfo.model_validate(withdraw))
except Exception as e:
db.rollback()
return error_response(code=500, message=f"审核失败: {str(e)}")
@router.post("/{withdraw_id}/cancel", response_model=ResponseModel)
async def cancel_withdraw(
withdraw_id: int,
db: Session = Depends(get_db),
current_user: UserDB = Depends(get_current_user)
):
"""取消提现申请"""
withdraw = db.query(WithdrawDB).filter(
WithdrawDB.id == withdraw_id,
WithdrawDB.status == WithdrawStatus.PENDING,
WithdrawDB.user_id == current_user.userid
).first()
if not withdraw:
return error_response(code=404, message="提现申请不存在或已处理")
try:
# 更新提现状态
withdraw.status = WithdrawStatus.CANCELLED
withdraw.remark = "用户取消提现"
# 返还锁定余额
account = db.query(UserAccountDB).filter(
UserAccountDB.user_id == withdraw.user_id
).first()
account.balance += withdraw.amount
account.lock_balance -= withdraw.amount
db.commit()
return success_response(message="提现申请已取消")
except Exception as e:
db.rollback()
return error_response(code=500, message=f"处理失败: {str(e)}")
@router.post("/{withdraw_id}/reject", response_model=ResponseModel)
async def reject_withdraw(
withdraw_id: int,
request: WithdrawRejectRequest,
db: Session = Depends(get_db),
admin: UserDB = Depends(get_admin_user)
):
"""驳回提现申请"""
withdraw = db.query(WithdrawDB).filter(
WithdrawDB.id == withdraw_id,
WithdrawDB.status == WithdrawStatus.PENDING
).first()
if not withdraw:
return error_response(code=404, message="提现申请不存在或已处理")
try:
# 更新提现状态
withdraw.status = WithdrawStatus.REJECTED
withdraw.remark = request.remark
# 返还锁定余额
account = db.query(UserAccountDB).filter(
UserAccountDB.user_id == withdraw.user_id
).first()
account.balance += withdraw.amount
account.lock_balance -= withdraw.amount
db.commit()
return success_response(message="提现申请已驳回")
except Exception as e:
db.rollback()
return error_response(code=500, message=f"处理失败: {str(e)}")
@router.get("/user", response_model=ResponseModel)
async def get_user_withdraws(
status: Optional[WithdrawStatus] = None,
skip: int = 0,
limit: int = 20,
db: Session = Depends(get_db),
current_user: UserDB = Depends(get_current_user)
):
"""获取提现记录列表"""
query = db.query(WithdrawDB).join(
UserBankCardDB,
WithdrawDB.bank_card_id == UserBankCardDB.id
).filter(WithdrawDB.user_id == current_user.userid)
if status:
query = query.filter(WithdrawDB.status == status)
total = query.count()
withdraws = query.order_by(WithdrawDB.create_time.desc()).offset(skip).limit(limit).all()
withdraw_list = []
for w in withdraws:
withdraw_info = WithdrawInfo.model_validate(w)
# 添加额外信息
withdraw_info_dict = withdraw_info.model_dump()
withdraw_info_dict.update({
"bank_card_number": w.bank_card.card_number, # 银行卡号
"bank_name": w.bank_card.bank_name, # 银行名称
"name": w.bank_card.name, # 持卡人姓名
})
withdraw_list.append(withdraw_info_dict)
return success_response(data={
"items": withdraw_list,
"total": total
})
@router.get("", response_model=ResponseModel)
async def get_all_withdraws(
status: Optional[WithdrawStatus] = None,
start_time: Optional[datetime] = None,
end_time: Optional[datetime] = None,
skip: int = 0,
limit: int = 20,
db: Session = Depends(get_db),
admin: UserDB = Depends(get_admin_user)
):
"""
管理员获取所有提现记录
Args:
status: 提现状态筛选
start_time: 开始时间
end_time: 结束时间
skip: 跳过记录数
limit: 返回记录数
"""
# 构建查询
query = db.query(WithdrawDB).join(
UserBankCardDB,
WithdrawDB.bank_card_id == UserBankCardDB.id
)
# 应用筛选条件
if status:
query = query.filter(WithdrawDB.status == status)
if start_time:
query = query.filter(WithdrawDB.create_time >= start_time)
if end_time:
query = query.filter(WithdrawDB.create_time <= end_time)
# 计算总数
total = query.count()
# 分页
withdraws = query.order_by(WithdrawDB.create_time.desc())\
.offset(skip)\
.limit(limit)\
.all()
# 构建返回数据
withdraw_list = []
for w in withdraws:
withdraw_info = WithdrawInfo.model_validate(w)
# 添加额外信息
withdraw_info_dict = withdraw_info.model_dump()
withdraw_info_dict.update({
"bank_card_number": w.bank_card.card_number, # 银行卡号
"bank_name": w.bank_card.bank_name, # 银行名称
"name": w.bank_card.name, # 持卡人姓名
})
withdraw_list.append(withdraw_info_dict)
return success_response(data={
"items": withdraw_list,
"total": total
})