From 6e37ea3bfec7bc24bb77d20c574fcd2f5bb4e2d6 Mon Sep 17 00:00:00 2001 From: aaron <> Date: Fri, 24 Jan 2025 23:01:31 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E6=88=90=E6=8F=90=E7=8E=B0=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/api/endpoints/withdraw.py | 214 ++++++++++++++++++++++++++++++++++ app/main.py | 3 +- app/models/user_account.py | 1 + app/models/withdraw.py | 43 +++++++ 4 files changed, 260 insertions(+), 1 deletion(-) create mode 100644 app/api/endpoints/withdraw.py create mode 100644 app/models/withdraw.py diff --git a/app/api/endpoints/withdraw.py b/app/api/endpoints/withdraw.py new file mode 100644 index 0000000..d3fb1a6 --- /dev/null +++ b/app/api/endpoints/withdraw.py @@ -0,0 +1,214 @@ +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 + +router = APIRouter() + +@router.post("", response_model=ResponseModel) +async def create_withdraw( + 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="银行卡不存在") + + # 验证余额 + 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) + + 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, + 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 + + # 更新账户余额 + account = db.query(UserAccountDB).filter( + UserAccountDB.user_id == withdraw.user_id + ).first() + + account.lock_balance -= withdraw.amount + + # 添加账户明细记录 + detail = AccountDetailDB( + user_id=withdraw.user_id, + type=AccountDetailType.WITHDRAW, + amount=withdraw.amount, + balance=account.balance, + description=f"提现到银行卡(尾号{withdraw.bank_card.card_number[-4:]})" + ) + db.add(detail) + + 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, + remark: str, + 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 = 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, + db: Session = Depends(get_db), + current_user: UserDB = Depends(get_current_user) +): + """获取提现记录列表""" + query = db.query(WithdrawDB).filter(WithdrawDB.user_id == current_user.userid) + + if status: + query = query.filter(WithdrawDB.status == status) + + withdraws = query.order_by(WithdrawDB.create_time.desc()).all() + + return success_response(data=[WithdrawInfo.model_validate(w) for w in withdraws]) + +@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 + }) \ No newline at end of file diff --git a/app/main.py b/app/main.py index ae74a1f..81527f5 100644 --- a/app/main.py +++ b/app/main.py @@ -1,6 +1,6 @@ from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware -from app.api.endpoints import wechat,user, address, community, station, order, coupon, community_building, upload, merchant, merchant_product, merchant_order, point, config, merchant_category, log, account,merchant_pay_order, message, bank_card +from app.api.endpoints import wechat,user, address, community, station, order, coupon, community_building, upload, merchant, merchant_product, merchant_order, point, config, merchant_category, log, account,merchant_pay_order, message, bank_card, withdraw from app.models.database import Base, engine from fastapi.exceptions import RequestValidationError from fastapi.responses import JSONResponse @@ -33,6 +33,7 @@ app.add_middleware(RequestLoggerMiddleware) app.include_router(wechat.router,prefix="/api/wechat",tags=["微信"]) app.include_router(user.router, prefix="/api/user", tags=["用户"]) app.include_router(bank_card.router, prefix="/api/bank-cards", tags=["用户银行卡"]) +app.include_router(withdraw.router, prefix="/api/withdraw", tags=["提现"]) app.include_router(point.router, prefix="/api/point", tags=["用户积分"]) app.include_router(account.router, prefix="/api/account", tags=["账户"]) app.include_router(address.router, prefix="/api/address", tags=["配送地址"]) diff --git a/app/models/user_account.py b/app/models/user_account.py index 4a2e1e4..a0d7517 100644 --- a/app/models/user_account.py +++ b/app/models/user_account.py @@ -17,6 +17,7 @@ class UserAccountDB(Base): id = Column(Integer, primary_key=True, autoincrement=True) user_id = Column(Integer, ForeignKey("users.userid"), unique=True, nullable=False) balance = Column(DECIMAL(10, 2), nullable=False, default=0) + lock_balance = Column(DECIMAL(10, 2), nullable=False, default=0) create_time = Column(DateTime(timezone=True), server_default=func.now()) update_time = Column(DateTime(timezone=True), onupdate=func.now()) diff --git a/app/models/withdraw.py b/app/models/withdraw.py new file mode 100644 index 0000000..aaf4f23 --- /dev/null +++ b/app/models/withdraw.py @@ -0,0 +1,43 @@ +from sqlalchemy import Column, String, Integer, DateTime, ForeignKey, Enum, DECIMAL +from sqlalchemy.sql import func +from pydantic import BaseModel, Field +from typing import Optional +from datetime import datetime +from .database import Base +import enum +from sqlalchemy.orm import relationship + +class WithdrawStatus(str, enum.Enum): + PENDING = "PENDING" # 已申请 + APPROVED = "APPROVED" # 已通过 + REJECTED = "REJECTED" # 已驳回 + +class WithdrawDB(Base): + __tablename__ = "withdraws" + + id = Column(Integer, primary_key=True, autoincrement=True) + user_id = Column(Integer, ForeignKey("users.userid"), nullable=False) + bank_card_id = Column(Integer, ForeignKey("user_bank_cards.id"), nullable=False) + amount = Column(DECIMAL(10, 2), nullable=False) + status = Column(Enum(WithdrawStatus), nullable=False, default=WithdrawStatus.PENDING) + remark = Column(String(200)) # 备注(驳回原因等) + create_time = Column(DateTime(timezone=True), server_default=func.now()) + update_time = Column(DateTime(timezone=True), onupdate=func.now()) + + # 关联关系 + bank_card = relationship("UserBankCardDB", backref="withdraws") + +class WithdrawCreate(BaseModel): + bank_card_id: int + amount: float = Field(..., gt=0) + +class WithdrawInfo(BaseModel): + id: int + bank_card_id: int + amount: float + status: WithdrawStatus + remark: Optional[str] + create_time: datetime + + class Config: + from_attributes = True \ No newline at end of file