Spaces:
Running
Running
Upload 3 files
Browse files- models.py +3 -3
- models_sql.py +1 -1
- router_wallet.py +41 -7
models.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
| 1 |
# models.py
|
| 2 |
-
from pydantic import BaseModel, validator
|
| 3 |
from typing import Optional, List
|
| 4 |
|
| 5 |
class SendCodeRequest(BaseModel):
|
|
@@ -118,7 +118,7 @@ class RatingRequest(BaseModel):
|
|
| 118 |
|
| 119 |
class RechargeRequest(BaseModel):
|
| 120 |
account: str
|
| 121 |
-
amount: int
|
| 122 |
method: Optional[str] = "alipay"
|
| 123 |
|
| 124 |
class PurchaseRequest(BaseModel):
|
|
@@ -134,7 +134,7 @@ class TipRequest(BaseModel):
|
|
| 134 |
|
| 135 |
class WithdrawRequest(BaseModel):
|
| 136 |
account: str
|
| 137 |
-
amount: int
|
| 138 |
alipayAccount: str
|
| 139 |
real_name: str
|
| 140 |
code: str
|
|
|
|
| 1 |
# models.py
|
| 2 |
+
from pydantic import BaseModel, Field, validator
|
| 3 |
from typing import Optional, List
|
| 4 |
|
| 5 |
class SendCodeRequest(BaseModel):
|
|
|
|
| 118 |
|
| 119 |
class RechargeRequest(BaseModel):
|
| 120 |
account: str
|
| 121 |
+
amount: int = Field(..., ge=1, le=10000) # 单次充值1-10000
|
| 122 |
method: Optional[str] = "alipay"
|
| 123 |
|
| 124 |
class PurchaseRequest(BaseModel):
|
|
|
|
| 134 |
|
| 135 |
class WithdrawRequest(BaseModel):
|
| 136 |
account: str
|
| 137 |
+
amount: int = Field(..., ge=1, le=5000) # 单次提现1-5000
|
| 138 |
alipayAccount: str
|
| 139 |
real_name: str
|
| 140 |
code: str
|
models_sql.py
CHANGED
|
@@ -61,7 +61,7 @@ class Transaction(Base):
|
|
| 61 |
|
| 62 |
tx_id = Column(String, primary_key=True)
|
| 63 |
account = Column(String, index=True)
|
| 64 |
-
tx_type = Column(String) # 枚举: RECHARGE, PURCHASE,
|
| 65 |
amount = Column(Integer)
|
| 66 |
related_account = Column(String, nullable=True)
|
| 67 |
item_id = Column(String, nullable=True)
|
|
|
|
| 61 |
|
| 62 |
tx_id = Column(String, primary_key=True)
|
| 63 |
account = Column(String, index=True)
|
| 64 |
+
tx_type = Column(String) # 枚举: RECHARGE, PURCHASE, SALE, TIP_OUT, TIP_IN, WITHDRAW, WITHDRAW_FEE, REFUND, TASK_FREEZE, TASK_DEPOSIT, TASK_PAYMENT, TASK_INCOME, TASK_REFUND, TASK_CANCEL_REFUND
|
| 65 |
amount = Column(Integer)
|
| 66 |
related_account = Column(String, nullable=True)
|
| 67 |
item_id = Column(String, nullable=True)
|
router_wallet.py
CHANGED
|
@@ -124,13 +124,15 @@ def calculate_tx_hash(tx_id, account, tx_type, amount, prev_hash):
|
|
| 124 |
return hashlib.sha256(data.encode()).hexdigest()
|
| 125 |
|
| 126 |
@router.post("/api/wallet/create_recharge_order")
|
| 127 |
-
async def create_recharge_order(req: RechargeRequest):
|
| 128 |
if not alipay:
|
| 129 |
# 这里会将真实的错误原因直接弹窗发给前端!
|
| 130 |
raise HTTPException(status_code=500, detail=f"支付网关配置错误: {alipay_error_msg}")
|
| 131 |
|
|
|
|
|
|
|
| 132 |
order_id = f"PAY_{int(time.time())}_{uuid.uuid4().hex[:6]}"
|
| 133 |
-
subject = f"ComfyUI Community Points - {
|
| 134 |
|
| 135 |
order_string = alipay.api_alipay_trade_precreate(
|
| 136 |
out_trade_no=order_id,
|
|
@@ -270,7 +272,9 @@ async def get_wallet(account: str, db: Session = Depends(get_db)):
|
|
| 270 |
|
| 271 |
@router.post("/api/wallet/purchase")
|
| 272 |
@limiter.limit("10/minute") # 🔒 P0安全优化:购买每分钟最多10次
|
| 273 |
-
async def purchase_item(request: Request, req: PurchaseRequest, db: Session = Depends(get_db)):
|
|
|
|
|
|
|
| 274 |
items_db = json_db.load_data("items.json", default_data=[])
|
| 275 |
item = next((i for i in items_db if i["id"] == req.item_id), None)
|
| 276 |
|
|
@@ -381,6 +385,24 @@ async def purchase_item(request: Request, req: PurchaseRequest, db: Session = De
|
|
| 381 |
related_user_name=seller_user_name
|
| 382 |
)
|
| 383 |
db.add(new_tx)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 384 |
db.commit()
|
| 385 |
|
| 386 |
# 📝 P2优化:购买审计日志
|
|
@@ -413,7 +435,9 @@ async def purchase_item(request: Request, req: PurchaseRequest, db: Session = De
|
|
| 413 |
|
| 414 |
@router.post("/api/wallet/tip")
|
| 415 |
@limiter.limit("20/minute") # 🔒 P0安全优化:打赏每分钟最多20次
|
| 416 |
-
async def tip_user(request: Request, req: TipRequest, db: Session = Depends(get_db)):
|
|
|
|
|
|
|
| 417 |
if req.amount <= 0:
|
| 418 |
raise HTTPException(status_code=400, detail="打赏金额必须大于0")
|
| 419 |
if req.sender_account == req.target_account:
|
|
@@ -568,7 +592,9 @@ async def tip_user(request: Request, req: TipRequest, db: Session = Depends(get_
|
|
| 568 |
|
| 569 |
@router.post("/api/wallet/withdraw")
|
| 570 |
@limiter.limit("3/minute") # 🔒 P0安全优化:提现每分钟最多3次
|
| 571 |
-
async def withdraw(request: Request, req: WithdrawRequest, db: Session = Depends(get_db)):
|
|
|
|
|
|
|
| 572 |
# 🔒 P1幂等性防护:检查最近10秒内是否存在相同提现请求
|
| 573 |
recent_cutoff = datetime.datetime.utcnow() - datetime.timedelta(seconds=10)
|
| 574 |
duplicate_tx = db.query(Transaction).filter(
|
|
@@ -882,6 +908,12 @@ async def get_sales_stats(account: str, db: Session = Depends(get_db)):
|
|
| 882 |
total_purchase = abs(purchase_stats.total or 0)
|
| 883 |
purchase_count = purchase_stats.count or 0
|
| 884 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 885 |
# 净销售 = 销售收入 - 购买支出
|
| 886 |
net_sales = total_sales - total_purchase
|
| 887 |
|
|
@@ -889,7 +921,7 @@ async def get_sales_stats(account: str, db: Session = Depends(get_db)):
|
|
| 889 |
"status": "success",
|
| 890 |
"data": {
|
| 891 |
"total_sales": total_sales,
|
| 892 |
-
"sales_count":
|
| 893 |
"total_purchase": total_purchase,
|
| 894 |
"purchase_count": purchase_count,
|
| 895 |
"net_sales": net_sales
|
|
@@ -974,7 +1006,9 @@ async def get_purchase_status(account: str, item_id: str, db: Session = Depends(
|
|
| 974 |
|
| 975 |
@router.post("/api/wallet/refund")
|
| 976 |
@limiter.limit("3/minute") # 🔒 P0安全优化:退款每分钟最多3次
|
| 977 |
-
async def refund_purchase(request: Request,
|
|
|
|
|
|
|
| 978 |
"""
|
| 979 |
🔄 P7后悔模式:申请退款
|
| 980 |
- 24小时内可退款
|
|
|
|
| 124 |
return hashlib.sha256(data.encode()).hexdigest()
|
| 125 |
|
| 126 |
@router.post("/api/wallet/create_recharge_order")
|
| 127 |
+
async def create_recharge_order(req: RechargeRequest, current_user: str = Depends(require_auth)):
|
| 128 |
if not alipay:
|
| 129 |
# 这里会将真实的错误原因直接弹窗发给前端!
|
| 130 |
raise HTTPException(status_code=500, detail=f"支付网关配置错误: {alipay_error_msg}")
|
| 131 |
|
| 132 |
+
# 🔒 使用已认证用户账号,忽略客户端传入的 account
|
| 133 |
+
authenticated_account = current_user
|
| 134 |
order_id = f"PAY_{int(time.time())}_{uuid.uuid4().hex[:6]}"
|
| 135 |
+
subject = f"ComfyUI Community Points - {authenticated_account}"
|
| 136 |
|
| 137 |
order_string = alipay.api_alipay_trade_precreate(
|
| 138 |
out_trade_no=order_id,
|
|
|
|
| 272 |
|
| 273 |
@router.post("/api/wallet/purchase")
|
| 274 |
@limiter.limit("10/minute") # 🔒 P0安全优化:购买每分钟最多10次
|
| 275 |
+
async def purchase_item(request: Request, req: PurchaseRequest, current_user: str = Depends(require_auth), db: Session = Depends(get_db)):
|
| 276 |
+
# 🔒 使用已认证用户账号,忽略客户端传入的 account
|
| 277 |
+
req.account = current_user
|
| 278 |
items_db = json_db.load_data("items.json", default_data=[])
|
| 279 |
item = next((i for i in items_db if i["id"] == req.item_id), None)
|
| 280 |
|
|
|
|
| 385 |
related_user_name=seller_user_name
|
| 386 |
)
|
| 387 |
db.add(new_tx)
|
| 388 |
+
|
| 389 |
+
# 为卖家生成销售收入记录
|
| 390 |
+
buyer_info = users_db.get(req.account, {})
|
| 391 |
+
buyer_user_name = buyer_info.get("name", req.account)
|
| 392 |
+
seller_tx_id = f"SALE_{int(time.time())}_{uuid.uuid4().hex[:6]}"
|
| 393 |
+
seller_tx = Transaction(
|
| 394 |
+
tx_id=seller_tx_id,
|
| 395 |
+
account=seller_account,
|
| 396 |
+
tx_type="SALE",
|
| 397 |
+
amount=price,
|
| 398 |
+
related_account=req.account,
|
| 399 |
+
item_id=req.item_id,
|
| 400 |
+
item_title=item.get('title', ''),
|
| 401 |
+
item_type=item.get('type', ''),
|
| 402 |
+
related_user_name=buyer_user_name,
|
| 403 |
+
description=f"销售收入: {item.get('title', req.item_id)}"
|
| 404 |
+
)
|
| 405 |
+
db.add(seller_tx)
|
| 406 |
db.commit()
|
| 407 |
|
| 408 |
# 📝 P2优化:购买审计日志
|
|
|
|
| 435 |
|
| 436 |
@router.post("/api/wallet/tip")
|
| 437 |
@limiter.limit("20/minute") # 🔒 P0安全优化:打赏每分钟最多20次
|
| 438 |
+
async def tip_user(request: Request, req: TipRequest, current_user: str = Depends(require_auth), db: Session = Depends(get_db)):
|
| 439 |
+
# 🔒 使用已认证用户账号,忽略客户端传入的 sender_account
|
| 440 |
+
req.sender_account = current_user
|
| 441 |
if req.amount <= 0:
|
| 442 |
raise HTTPException(status_code=400, detail="打赏金额必须大于0")
|
| 443 |
if req.sender_account == req.target_account:
|
|
|
|
| 592 |
|
| 593 |
@router.post("/api/wallet/withdraw")
|
| 594 |
@limiter.limit("3/minute") # 🔒 P0安全优化:提现每分钟最多3次
|
| 595 |
+
async def withdraw(request: Request, req: WithdrawRequest, current_user: str = Depends(require_auth), db: Session = Depends(get_db)):
|
| 596 |
+
# 🔒 使用已认证用户账号,忽略客户端传入的 account
|
| 597 |
+
req.account = current_user
|
| 598 |
# 🔒 P1幂等性防护:检查最近10秒内是否存在相同提现请求
|
| 599 |
recent_cutoff = datetime.datetime.utcnow() - datetime.timedelta(seconds=10)
|
| 600 |
duplicate_tx = db.query(Transaction).filter(
|
|
|
|
| 908 |
total_purchase = abs(purchase_stats.total or 0)
|
| 909 |
purchase_count = purchase_stats.count or 0
|
| 910 |
|
| 911 |
+
# 统计销售笔数(SALE类型交易记录)
|
| 912 |
+
sales_count = db.query(sql_func.count(Transaction.tx_id)).filter(
|
| 913 |
+
Transaction.account == account,
|
| 914 |
+
Transaction.tx_type == "SALE"
|
| 915 |
+
).scalar() or 0
|
| 916 |
+
|
| 917 |
# 净销售 = 销售收入 - 购买支出
|
| 918 |
net_sales = total_sales - total_purchase
|
| 919 |
|
|
|
|
| 921 |
"status": "success",
|
| 922 |
"data": {
|
| 923 |
"total_sales": total_sales,
|
| 924 |
+
"sales_count": sales_count,
|
| 925 |
"total_purchase": total_purchase,
|
| 926 |
"purchase_count": purchase_count,
|
| 927 |
"net_sales": net_sales
|
|
|
|
| 1006 |
|
| 1007 |
@router.post("/api/wallet/refund")
|
| 1008 |
@limiter.limit("3/minute") # 🔒 P0安全优化:退款每分钟最多3次
|
| 1009 |
+
async def refund_purchase(request: Request, item_id: str, current_user: str = Depends(require_auth), db: Session = Depends(get_db)):
|
| 1010 |
+
# 🔒 使用已认证用户账号,忽略客户端传入的 account
|
| 1011 |
+
account = current_user
|
| 1012 |
"""
|
| 1013 |
🔄 P7后悔模式:申请退款
|
| 1014 |
- 24小时内可退款
|