ZHIWEI666 commited on
Commit
bc186bd
·
verified ·
1 Parent(s): 5e33680

Upload 2 files

Browse files
Files changed (2) hide show
  1. models_sql.py +10 -12
  2. router_wallet.py +60 -118
models_sql.py CHANGED
@@ -11,14 +11,15 @@ class Wallet(Base):
11
 
12
  account = Column(String, primary_key=True, index=True)
13
  balance = Column(Integer, default=0) # 用于消费的余额 (充值获得)
14
- earn_balance = Column(Integer, default=0) # 创作者收益余额 (别人购买获得)
 
15
  frozen_balance = Column(Integer, default=0) # 提现审核冻结中的余额
16
 
17
  # 乐观锁版本号,防止并发扣款被击穿
18
  version = Column(Integer, default=1)
19
 
20
  class Ownership(Base):
21
- """资源所有权表 (解决Req 4:一次购买,终身免费)"""
22
  __tablename__ = "ownerships"
23
 
24
  id = Column(Integer, primary_key=True, autoincrement=True)
@@ -27,17 +28,14 @@ class Ownership(Base):
27
  purchased_at = Column(DateTime, default=datetime.datetime.utcnow)
28
 
29
  class Transaction(Base):
30
- """交易流水表 (复式记账,解决Req 6, 7:税务对账与哈希防篡改)"""
31
  __tablename__ = "transactions"
32
 
33
- tx_id = Column(String, primary_key=True) # 全局唯一流水号 (如 ORDER_uuid)
34
  account = Column(String, index=True)
35
- tx_type = Column(String) # RECHARGE, CONSUME, EARN, WITHDRAW
36
- amount = Column(Integer) # 交易额 (正负值)
37
- target_id = Column(String, nullable=True) # 关联的 item_id 或第三方支付宝流水号
38
-
39
- # 区块链级安全:哈希防篡改链
40
- prev_hash = Column(String, nullable=True) # 上一笔交易的哈希值
41
- tx_hash = Column(String) # 当前交易哈希值
42
-
43
  created_at = Column(DateTime, default=datetime.datetime.utcnow)
 
11
 
12
  account = Column(String, primary_key=True, index=True)
13
  balance = Column(Integer, default=0) # 用于消费的余额 (充值获得)
14
+ earn_balance = Column(Integer, default=0) # 创作者销售收益余额 (别人购买获得)
15
+ tip_balance = Column(Integer, default=0) # 【新增】创作者打赏收益余额 (粉丝赞助获得)
16
  frozen_balance = Column(Integer, default=0) # 提现审核冻结中的余额
17
 
18
  # 乐观锁版本号,防止并发扣款被击穿
19
  version = Column(Integer, default=1)
20
 
21
  class Ownership(Base):
22
+ """资源所有权表"""
23
  __tablename__ = "ownerships"
24
 
25
  id = Column(Integer, primary_key=True, autoincrement=True)
 
28
  purchased_at = Column(DateTime, default=datetime.datetime.utcnow)
29
 
30
  class Transaction(Base):
31
+ """交易流水表"""
32
  __tablename__ = "transactions"
33
 
34
+ tx_id = Column(String, primary_key=True)
35
  account = Column(String, index=True)
36
+ tx_type = Column(String)
37
+ amount = Column(Integer)
38
+ target_id = Column(String, nullable=True)
39
+ prev_hash = Column(String, nullable=True)
40
+ tx_hash = Column(String)
 
 
 
41
  created_at = Column(DateTime, default=datetime.datetime.utcnow)
router_wallet.py CHANGED
@@ -7,16 +7,12 @@ import hashlib
7
  import os
8
  from database_sql import get_db
9
  from models_sql import Wallet, Transaction, Ownership
10
- # 【修复 1】:在这里补充导入了 TipRequest,防止云端崩溃!
11
  from models import RechargeRequest, WithdrawRequest, PurchaseRequest, TipRequest
12
  import 数据库连接 as json_db
13
  from router_users import VERIFY_CODES
14
 
15
  router = APIRouter()
16
 
17
- # =======================================================
18
- # 🏦 支付宝 SDK 初始化 (带防崩溃保护)
19
- # =======================================================
20
  try:
21
  from alipay import AliPay
22
  from alipay.utils import AliPayConfig
@@ -26,159 +22,110 @@ try:
26
  app_private_key_string=os.environ.get("ALIPAY_PRIVATE_KEY", "").replace("\\n", "\n"),
27
  alipay_public_key_string=os.environ.get("ALIPAY_PUBLIC_KEY", "").replace("\\n", "\n"),
28
  sign_type="RSA2",
29
- debug=False, # 生产环境设为 False
30
  config=AliPayConfig(timeout=15)
31
  )
32
  except Exception as e:
33
  alipay = None
34
- print("⚠️ 支付宝 SDK 初始化跳过 (环境变量未配置,处于开发模式):", e)
35
-
36
 
37
  def calculate_tx_hash(tx_id, account, tx_type, amount, prev_hash):
38
- """计算哈希值:依赖前一笔订单的哈希,实现链式防篡改"""
39
  data = f"{tx_id}{account}{tx_type}{amount}{prev_hash}"
40
  return hashlib.sha256(data.encode()).hexdigest()
41
 
42
  def record_transaction(db: Session, account: str, tx_type: str, amount: int, target_id: str = None):
43
- """安全记录交易流水"""
44
  last_tx = db.query(Transaction).filter(Transaction.account == account).order_by(Transaction.created_at.desc()).first()
45
  prev_hash = last_tx.tx_hash if last_tx else "GENESIS"
46
-
47
  tx_id = f"TX_{int(time.time())}_{uuid.uuid4().hex[:8]}"
48
  tx_hash = calculate_tx_hash(tx_id, account, tx_type, amount, prev_hash)
49
-
50
  new_tx = Transaction(tx_id=tx_id, account=account, tx_type=tx_type, amount=amount, target_id=target_id, prev_hash=prev_hash, tx_hash=tx_hash)
51
  db.add(new_tx)
52
  return new_tx
53
 
54
- # =======================================================
55
- # 💰 充值入金链路 (真实预下单 -> 扫码 -> 异步回调 -> 查单)
56
- # =======================================================
 
 
 
 
 
 
 
 
 
57
 
58
  @router.post("/api/wallet/create_recharge_order")
59
  async def create_recharge_order(req: RechargeRequest, db: Session = Depends(get_db)):
60
- """生成真实的支付宝当面付二维码"""
61
- if not alipay:
62
- raise HTTPException(status_code=500, detail="后台未配置支付密钥,暂不支持真实付款")
63
-
64
- # 1. 使用 target_id 存储订单状态 "PENDING",不影响哈希校验
65
  tx = record_transaction(db, req.account, "RECHARGE", req.amount, "PENDING")
66
  out_trade_no = tx.tx_id
67
  db.commit()
68
-
69
  try:
70
- # 2. 向支付宝发起真实预下单
71
- result = alipay.api_alipay_trade_precreate(
72
- subject=f"ComfyUI社区精选 - 充值 {req.amount} 积分",
73
- out_trade_no=out_trade_no,
74
- total_amount=str(req.amount),
75
- )
76
- if result.get("code") == "10000":
77
- return {"status": "success", "order_id": out_trade_no, "qr_code_url": result.get("qr_code")}
78
- else:
79
- raise HTTPException(status_code=500, detail=f"生成支付码失败: {result.get('msg')}")
80
  except Exception as e:
81
  raise HTTPException(status_code=500, detail=f"支付接口异常: {str(e)}")
82
 
83
  @router.post("/api/wallet/alipay_notify")
84
  async def alipay_notify(request: Request, db: Session = Depends(get_db)):
85
- """🚨 核心:接收支付宝付款成功的异步回调,验签并加钱"""
86
  data = dict(await request.form())
87
  signature = data.pop("sign", None)
88
-
89
- if not alipay or not signature:
90
- return "fail"
91
-
92
- # 1. 严格验签,防伪造
93
- success = alipay.verify(data, signature)
94
- if success and data.get("trade_status") in ("TRADE_SUCCESS", "TRADE_FINISHED"):
95
  out_trade_no = data.get("out_trade_no")
96
  total_amount = float(data.get("total_amount", 0))
97
-
98
- # 2. 悲观锁查询该笔 PENDING 订单,防并发重复加款
99
  tx = db.query(Transaction).filter(Transaction.tx_id == out_trade_no).with_for_update().first()
100
- if not tx or tx.target_id == "SUCCESS":
101
- return "success" # 已经处理过,直接返回成功让支付宝停止轰炸
102
-
103
- if tx.amount != int(total_amount):
104
- return "fail" # 金额对不上,拦截黑客攻击
105
-
106
- # 3. 安全加款
107
  wallet = db.query(Wallet).filter(Wallet.account == tx.account).with_for_update().first()
108
  if not wallet:
109
  wallet = Wallet(account=tx.account)
110
  db.add(wallet)
111
-
112
  wallet.balance += tx.amount
113
- tx.target_id = "SUCCESS" # 将流水标记为已完成
114
  db.commit()
115
-
116
- return "success" # 必须返回纯文本 success 给支付宝
117
  return "fail"
118
 
119
  @router.get("/api/wallet/check_order/{order_id}")
120
  async def check_order(order_id: str, db: Session = Depends(get_db)):
121
- """前端轮询查单接口"""
122
  tx = db.query(Transaction).filter(Transaction.tx_id == order_id).first()
123
- if not tx:
124
- raise HTTPException(status_code=404, detail="订单不存在")
125
- return {"status": tx.target_id} # 返回 PENDING 或 SUCCESS
126
-
127
-
128
- # =======================================================
129
- # 🛒 消费闭环链路
130
- # =======================================================
131
 
132
  @router.post("/api/wallet/purchase")
133
  async def purchase_item(req: PurchaseRequest, db: Session = Depends(get_db)):
134
- """消费购买接口:严密的并发防刷控制,以及早鸟保护机制"""
135
  items_db = json_db.load_data("items.json", default_data=[])
136
  item = next((i for i in items_db if i["id"] == req.item_id), None)
137
  if not item: raise HTTPException(status_code=404, detail="商品不存在")
138
-
139
  price = int(item.get("price", 0))
140
  author = item.get("author")
141
-
142
  owned = db.query(Ownership).filter(Ownership.account == req.account, Ownership.item_id == req.item_id).first()
143
- if owned:
144
- return {"status": "success", "message": "已永久授权,无需重复扣费", "already_owned": True}
145
-
146
  if price <= 0 or req.account == author:
147
- new_owner = Ownership(account=req.account, item_id=req.item_id)
148
- db.add(new_owner)
149
  record_transaction(db, req.account, "CONSUME", 0, req.item_id)
150
  db.commit()
151
- return {"status": "success", "message": "免费商品或创作者本人,已永久授权", "already_owned": True}
152
 
153
  buyer_wallet = db.query(Wallet).filter(Wallet.account == req.account).with_for_update().first()
154
- if not buyer_wallet or buyer_wallet.balance < price:
155
- raise HTTPException(status_code=400, detail="余额不足")
156
-
157
  buyer_wallet.balance -= price
158
  record_transaction(db, req.account, "CONSUME", -price, req.item_id)
159
-
160
- new_owner = Ownership(account=req.account, item_id=req.item_id)
161
- db.add(new_owner)
162
 
163
  author_wallet = db.query(Wallet).filter(Wallet.account == author).with_for_update().first()
164
  if not author_wallet:
165
  author_wallet = Wallet(account=author)
166
  db.add(author_wallet)
167
-
168
  author_wallet.earn_balance += price
169
  record_transaction(db, author, "EARN", price, req.item_id)
170
-
171
  db.commit()
172
  return {"status": "success", "already_owned": False}
173
 
174
-
175
- # =======================================================
176
- # 💸 提现出金链路 (引入支付宝自动单笔打款)
177
- # =======================================================
178
-
179
  @router.post("/api/wallet/withdraw")
180
  async def withdraw_earnings(req: WithdrawRequest, db: Session = Depends(get_db)):
181
- """提现接口"""
182
  users_db = json_db.load_data("users.json", default_data={})
183
  user = users_db.get(req.account)
184
  if not user: raise HTTPException(status_code=404, detail="账号异常")
@@ -189,67 +136,59 @@ async def withdraw_earnings(req: WithdrawRequest, db: Session = Depends(get_db))
189
  raise HTTPException(status_code=400, detail="验证码不正确或已过期")
190
 
191
  wallet = db.query(Wallet).filter(Wallet.account == req.account).with_for_update().first()
192
- if not wallet or wallet.earn_balance < req.amount:
193
- raise HTTPException(status_code=400, detail="可提现收益不足")
194
-
195
- if req.amount < 100:
196
- raise HTTPException(status_code=400, detail="最低提现金额为 100 积分")
197
-
198
- if not alipay:
199
- # 降级模式:如果没配置真实支付,仅记录冻结流水,需人工审核打款
 
 
200
  wallet.earn_balance -= req.amount
 
 
 
 
 
 
201
  wallet.frozen_balance += req.amount
202
  record_transaction(db, req.account, "WITHDRAW_FREEZE", -req.amount, req.alipay_account)
203
  VERIFY_CODES.pop(cache_key, None)
204
  db.commit()
205
- return {"status": "success", "earn_balance": wallet.earn_balance, "message": "提现申请已提交 (暂为开发模式,已冻结)"}
206
 
207
- # 真实企业级模式:直接调用支付宝单笔转账接口打款到用户余额
208
  out_biz_no = f"WD_{int(time.time())}_{uuid.uuid4().hex[:6]}"
209
  try:
210
  result = alipay.api_alipay_fund_trans_toaccount_transfer(
211
- out_biz_no=out_biz_no,
212
- payee_type="ALIPAY_LOGONID",
213
- payee_account=req.alipay_account,
214
- amount=str(req.amount),
215
- payee_real_name=req.real_name,
216
- remark="ComfyUI社区精选创作者收益提现"
217
  )
218
-
219
  if result.get("code") == "10000":
220
- # 打款秒成功,彻底扣除可提现收益
221
- wallet.earn_balance -= req.amount
222
  record_transaction(db, req.account, "WITHDRAW", -req.amount, req.alipay_account)
223
  VERIFY_CODES.pop(cache_key, None)
224
  db.commit()
225
- return {"status": "success", "earn_balance": wallet.earn_balance, "message": "打款已秒到账您的支付宝!"}
226
  else:
227
  raise HTTPException(status_code=400, detail=f"打款风控拦截: {result.get('sub_msg')}")
228
  except Exception as e:
229
  raise HTTPException(status_code=500, detail=f"提现接口异常: {str(e)}")
230
 
231
- # 【修复 2】:去除了底部重复多余的旧代码,只保留最新正确的一个打赏接口
232
  @router.post("/api/wallet/tip")
233
  async def tip_user(req: TipRequest, db: Session = Depends(get_db)):
234
- """赞接口:处理扣费、收益及赞赏榜单记录"""
235
- if req.sender_account == req.target_account:
236
- raise HTTPException(status_code=400, detail="不能打赏自己")
237
- if req.amount <= 0:
238
- raise HTTPException(status_code=400, detail="打赏金额必须大于0")
239
 
240
  users_db = json_db.load_data("users.json", default_data={})
241
- if req.target_account not in users_db:
242
- raise HTTPException(status_code=404, detail="目标用户不存在")
243
-
244
  target_user = users_db[req.target_account]
245
  tips_received = target_user.get("tips_received", {})
246
 
247
- # 计算等级额度 (100分=1星, 500分=1月, 2500分=1太阳。上限9太阳=22500分)
248
  current_tip = tips_received.get(req.sender_account, {}).get("amount", 0)
249
  if current_tip + req.amount > 22500:
250
  raise HTTPException(status_code=400, detail=f"您对该用户的打赏已达上限 (9个太阳/22500积分),最多还能打赏 {22500 - current_tip} 积分")
251
 
252
- # 1. 扣除打赏者余额 (悲观锁防并发)
253
  sender_wallet = db.query(Wallet).filter(Wallet.account == req.sender_account).with_for_update().first()
254
  if not sender_wallet or sender_wallet.balance < req.amount:
255
  raise HTTPException(status_code=400, detail="积分余额不足,请先充值")
@@ -257,22 +196,25 @@ async def tip_user(req: TipRequest, db: Session = Depends(get_db)):
257
  sender_wallet.balance -= req.amount
258
  record_transaction(db, req.sender_account, "TIP_SEND", -req.amount, req.target_account)
259
 
260
- # 2. 增加作者收益
261
  target_wallet = db.query(Wallet).filter(Wallet.account == req.target_account).with_for_update().first()
262
  if not target_wallet:
263
  target_wallet = Wallet(account=req.target_account)
264
  db.add(target_wallet)
265
- target_wallet.earn_balance += req.amount
 
 
 
 
 
 
266
  record_transaction(db, req.target_account, "TIP_RECEIVE", req.amount, req.sender_account)
267
 
268
- # 3. 永久记录到该用户的榜单 JSON 中
269
  tips_received[req.sender_account] = {
270
  "amount": current_tip + req.amount,
271
  "is_anonymous": req.is_anonymous,
272
  "sender_name": users_db.get(req.sender_account, {}).get("name", req.sender_account)
273
  }
274
  target_user["tips_received"] = tips_received
275
-
276
  db.commit()
277
  json_db.save_data("users.json", users_db)
278
 
 
7
  import os
8
  from database_sql import get_db
9
  from models_sql import Wallet, Transaction, Ownership
 
10
  from models import RechargeRequest, WithdrawRequest, PurchaseRequest, TipRequest
11
  import 数据库连接 as json_db
12
  from router_users import VERIFY_CODES
13
 
14
  router = APIRouter()
15
 
 
 
 
16
  try:
17
  from alipay import AliPay
18
  from alipay.utils import AliPayConfig
 
22
  app_private_key_string=os.environ.get("ALIPAY_PRIVATE_KEY", "").replace("\\n", "\n"),
23
  alipay_public_key_string=os.environ.get("ALIPAY_PUBLIC_KEY", "").replace("\\n", "\n"),
24
  sign_type="RSA2",
25
+ debug=False,
26
  config=AliPayConfig(timeout=15)
27
  )
28
  except Exception as e:
29
  alipay = None
 
 
30
 
31
  def calculate_tx_hash(tx_id, account, tx_type, amount, prev_hash):
 
32
  data = f"{tx_id}{account}{tx_type}{amount}{prev_hash}"
33
  return hashlib.sha256(data.encode()).hexdigest()
34
 
35
  def record_transaction(db: Session, account: str, tx_type: str, amount: int, target_id: str = None):
 
36
  last_tx = db.query(Transaction).filter(Transaction.account == account).order_by(Transaction.created_at.desc()).first()
37
  prev_hash = last_tx.tx_hash if last_tx else "GENESIS"
 
38
  tx_id = f"TX_{int(time.time())}_{uuid.uuid4().hex[:8]}"
39
  tx_hash = calculate_tx_hash(tx_id, account, tx_type, amount, prev_hash)
 
40
  new_tx = Transaction(tx_id=tx_id, account=account, tx_type=tx_type, amount=amount, target_id=target_id, prev_hash=prev_hash, tx_hash=tx_hash)
41
  db.add(new_tx)
42
  return new_tx
43
 
44
+ @router.get("/api/wallet/{account}")
45
+ async def get_wallet(account: str, db: Session = Depends(get_db)):
46
+ """【新增】获取用户真实的金融余额数据"""
47
+ wallet = db.query(Wallet).filter(Wallet.account == account).first()
48
+ if not wallet:
49
+ return {"balance": 0, "earn_balance": 0, "tip_balance": 0, "frozen_balance": 0}
50
+ return {
51
+ "balance": wallet.balance,
52
+ "earn_balance": wallet.earn_balance,
53
+ "tip_balance": getattr(wallet, 'tip_balance', 0),
54
+ "frozen_balance": wallet.frozen_balance
55
+ }
56
 
57
  @router.post("/api/wallet/create_recharge_order")
58
  async def create_recharge_order(req: RechargeRequest, db: Session = Depends(get_db)):
59
+ if not alipay: raise HTTPException(status_code=500, detail="后台未配置支付密钥")
 
 
 
 
60
  tx = record_transaction(db, req.account, "RECHARGE", req.amount, "PENDING")
61
  out_trade_no = tx.tx_id
62
  db.commit()
 
63
  try:
64
+ result = alipay.api_alipay_trade_precreate(subject=f"充值 {req.amount} 积分", out_trade_no=out_trade_no, total_amount=str(req.amount))
65
+ if result.get("code") == "10000": return {"status": "success", "order_id": out_trade_no, "qr_code_url": result.get("qr_code")}
66
+ else: raise HTTPException(status_code=500, detail=f"生成支付码失败: {result.get('msg')}")
 
 
 
 
 
 
 
67
  except Exception as e:
68
  raise HTTPException(status_code=500, detail=f"支付接口异常: {str(e)}")
69
 
70
  @router.post("/api/wallet/alipay_notify")
71
  async def alipay_notify(request: Request, db: Session = Depends(get_db)):
 
72
  data = dict(await request.form())
73
  signature = data.pop("sign", None)
74
+ if not alipay or not signature: return "fail"
75
+ if alipay.verify(data, signature) and data.get("trade_status") in ("TRADE_SUCCESS", "TRADE_FINISHED"):
 
 
 
 
 
76
  out_trade_no = data.get("out_trade_no")
77
  total_amount = float(data.get("total_amount", 0))
 
 
78
  tx = db.query(Transaction).filter(Transaction.tx_id == out_trade_no).with_for_update().first()
79
+ if not tx or tx.target_id == "SUCCESS": return "success"
80
+ if tx.amount != int(total_amount): return "fail"
 
 
 
 
 
81
  wallet = db.query(Wallet).filter(Wallet.account == tx.account).with_for_update().first()
82
  if not wallet:
83
  wallet = Wallet(account=tx.account)
84
  db.add(wallet)
 
85
  wallet.balance += tx.amount
86
+ tx.target_id = "SUCCESS"
87
  db.commit()
88
+ return "success"
 
89
  return "fail"
90
 
91
  @router.get("/api/wallet/check_order/{order_id}")
92
  async def check_order(order_id: str, db: Session = Depends(get_db)):
 
93
  tx = db.query(Transaction).filter(Transaction.tx_id == order_id).first()
94
+ if not tx: raise HTTPException(status_code=404, detail="订单不存在")
95
+ return {"status": tx.target_id}
 
 
 
 
 
 
96
 
97
  @router.post("/api/wallet/purchase")
98
  async def purchase_item(req: PurchaseRequest, db: Session = Depends(get_db)):
 
99
  items_db = json_db.load_data("items.json", default_data=[])
100
  item = next((i for i in items_db if i["id"] == req.item_id), None)
101
  if not item: raise HTTPException(status_code=404, detail="商品不存在")
 
102
  price = int(item.get("price", 0))
103
  author = item.get("author")
 
104
  owned = db.query(Ownership).filter(Ownership.account == req.account, Ownership.item_id == req.item_id).first()
105
+ if owned: return {"status": "success", "message": "已永久授权", "already_owned": True}
 
 
106
  if price <= 0 or req.account == author:
107
+ db.add(Ownership(account=req.account, item_id=req.item_id))
 
108
  record_transaction(db, req.account, "CONSUME", 0, req.item_id)
109
  db.commit()
110
+ return {"status": "success", "message": "免费获取", "already_owned": True}
111
 
112
  buyer_wallet = db.query(Wallet).filter(Wallet.account == req.account).with_for_update().first()
113
+ if not buyer_wallet or buyer_wallet.balance < price: raise HTTPException(status_code=400, detail="余额不足")
 
 
114
  buyer_wallet.balance -= price
115
  record_transaction(db, req.account, "CONSUME", -price, req.item_id)
116
+ db.add(Ownership(account=req.account, item_id=req.item_id))
 
 
117
 
118
  author_wallet = db.query(Wallet).filter(Wallet.account == author).with_for_update().first()
119
  if not author_wallet:
120
  author_wallet = Wallet(account=author)
121
  db.add(author_wallet)
 
122
  author_wallet.earn_balance += price
123
  record_transaction(db, author, "EARN", price, req.item_id)
 
124
  db.commit()
125
  return {"status": "success", "already_owned": False}
126
 
 
 
 
 
 
127
  @router.post("/api/wallet/withdraw")
128
  async def withdraw_earnings(req: WithdrawRequest, db: Session = Depends(get_db)):
 
129
  users_db = json_db.load_data("users.json", default_data={})
130
  user = users_db.get(req.account)
131
  if not user: raise HTTPException(status_code=404, detail="账号异常")
 
136
  raise HTTPException(status_code=400, detail="验证码不正确或已过期")
137
 
138
  wallet = db.query(Wallet).filter(Wallet.account == req.account).with_for_update().first()
139
+ if not wallet: raise HTTPException(status_code=400, detail="钱包不存在")
140
+
141
+ # 【核心修改】:合并计算可提现金额,但分别扣除以确保账目清晰
142
+ tip_bal = getattr(wallet, 'tip_balance', 0)
143
+ total_withdrawable = wallet.earn_balance + tip_bal
144
+
145
+ if total_withdrawable < req.amount: raise HTTPException(status_code=400, detail="可提现收益不足")
146
+ if req.amount < 100: raise HTTPException(status_code=400, detail="最低提现金额为 100 积分")
147
+
148
+ if wallet.earn_balance >= req.amount:
149
  wallet.earn_balance -= req.amount
150
+ else:
151
+ remaining = req.amount - wallet.earn_balance
152
+ wallet.earn_balance = 0
153
+ wallet.tip_balance -= remaining
154
+
155
+ if not alipay:
156
  wallet.frozen_balance += req.amount
157
  record_transaction(db, req.account, "WITHDRAW_FREEZE", -req.amount, req.alipay_account)
158
  VERIFY_CODES.pop(cache_key, None)
159
  db.commit()
160
+ return {"status": "success", "earn_balance": wallet.earn_balance, "tip_balance": wallet.tip_balance, "message": "提现申请已提交 (暂为开发模式)"}
161
 
 
162
  out_biz_no = f"WD_{int(time.time())}_{uuid.uuid4().hex[:6]}"
163
  try:
164
  result = alipay.api_alipay_fund_trans_toaccount_transfer(
165
+ out_biz_no=out_biz_no, payee_type="ALIPAY_LOGONID", payee_account=req.alipay_account,
166
+ amount=str(req.amount), payee_real_name=req.real_name, remark="收益提现"
 
 
 
 
167
  )
 
168
  if result.get("code") == "10000":
 
 
169
  record_transaction(db, req.account, "WITHDRAW", -req.amount, req.alipay_account)
170
  VERIFY_CODES.pop(cache_key, None)
171
  db.commit()
172
+ return {"status": "success", "earn_balance": wallet.earn_balance, "tip_balance": wallet.tip_balance, "message": "打款已秒到账!"}
173
  else:
174
  raise HTTPException(status_code=400, detail=f"打款风控拦截: {result.get('sub_msg')}")
175
  except Exception as e:
176
  raise HTTPException(status_code=500, detail=f"提现接口异常: {str(e)}")
177
 
 
178
  @router.post("/api/wallet/tip")
179
  async def tip_user(req: TipRequest, db: Session = Depends(get_db)):
180
+ if req.sender_account == req.target_account: raise HTTPException(status_code=400, detail="不能打自己")
181
+ if req.amount <= 0: raise HTTPException(status_code=400, detail="打赏金额必须大于0")
 
 
 
182
 
183
  users_db = json_db.load_data("users.json", default_data={})
184
+ if req.target_account not in users_db: raise HTTPException(status_code=404, detail="目标用户不存在")
 
 
185
  target_user = users_db[req.target_account]
186
  tips_received = target_user.get("tips_received", {})
187
 
 
188
  current_tip = tips_received.get(req.sender_account, {}).get("amount", 0)
189
  if current_tip + req.amount > 22500:
190
  raise HTTPException(status_code=400, detail=f"您对该用户的打赏已达上限 (9个太阳/22500积分),最多还能打赏 {22500 - current_tip} 积分")
191
 
 
192
  sender_wallet = db.query(Wallet).filter(Wallet.account == req.sender_account).with_for_update().first()
193
  if not sender_wallet or sender_wallet.balance < req.amount:
194
  raise HTTPException(status_code=400, detail="积分余额不足,请先充值")
 
196
  sender_wallet.balance -= req.amount
197
  record_transaction(db, req.sender_account, "TIP_SEND", -req.amount, req.target_account)
198
 
 
199
  target_wallet = db.query(Wallet).filter(Wallet.account == req.target_account).with_for_update().first()
200
  if not target_wallet:
201
  target_wallet = Wallet(account=req.target_account)
202
  db.add(target_wallet)
203
+
204
+ # 【核心修改】:打赏金额独立进入 tip_balance 账目
205
+ if hasattr(target_wallet, 'tip_balance'):
206
+ target_wallet.tip_balance += req.amount
207
+ else:
208
+ target_wallet.earn_balance += req.amount
209
+
210
  record_transaction(db, req.target_account, "TIP_RECEIVE", req.amount, req.sender_account)
211
 
 
212
  tips_received[req.sender_account] = {
213
  "amount": current_tip + req.amount,
214
  "is_anonymous": req.is_anonymous,
215
  "sender_name": users_db.get(req.sender_account, {}).get("name", req.sender_account)
216
  }
217
  target_user["tips_received"] = tips_received
 
218
  db.commit()
219
  json_db.save_data("users.json", users_db)
220