ZHIWEI666 commited on
Commit
5863217
·
verified ·
1 Parent(s): 4c9deec

实现支付系统的商业闭环。

Browse files
Files changed (3) hide show
  1. app.py +31 -3
  2. requirements.txt +2 -1
  3. router_wallet.py +140 -36
app.py CHANGED
@@ -8,6 +8,7 @@ from huggingface_hub import hf_hub_download, HfApi
8
  import hashlib
9
  import urllib.parse
10
  import urllib.request
 
11
  import os
12
  import 数据库连接 as db
13
 
@@ -16,7 +17,7 @@ from router_items import router as items_router
16
  from router_comments import router as comments_router
17
  from router_messages import router as messages_router
18
  from router_wallet import router as wallet_router
19
- from router_proxy import router as proxy_router # 【新增】:导入新的代理路由
20
 
21
  from database_sql import init_sql_db, get_db
22
  from models_sql import Ownership
@@ -41,7 +42,7 @@ app.include_router(items_router)
41
  app.include_router(comments_router)
42
  app.include_router(messages_router)
43
  app.include_router(wallet_router)
44
- app.include_router(proxy_router) # 【新增】:挂载私有库 ZIP 下载路由
45
 
46
  @app.get("/")
47
  def read_root():
@@ -92,8 +93,35 @@ async def validate_resource(req: ValidateRequest):
92
  itype = item.get("type", "")
93
 
94
  if itype.startswith("tool"):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
95
  try:
96
- req_obj = urllib.request.Request(link, method="HEAD", headers={'User-Agent': 'Mozilla/5.0'})
97
  with urllib.request.urlopen(req_obj, timeout=5) as response:
98
  if response.status >= 400:
99
  return JSONResponse(content={"error": "原作者的 Git 仓库已失效或设为私有"}, status_code=400)
 
8
  import hashlib
9
  import urllib.parse
10
  import urllib.request
11
+ import urllib.error # 【新增】:用于捕获 HTTPError 以判断私有库状态
12
  import os
13
  import 数据库连接 as db
14
 
 
17
  from router_comments import router as comments_router
18
  from router_messages import router as messages_router
19
  from router_wallet import router as wallet_router
20
+ from router_proxy import router as proxy_router # 导入代理路由
21
 
22
  from database_sql import init_sql_db, get_db
23
  from models_sql import Ownership
 
42
  app.include_router(comments_router)
43
  app.include_router(messages_router)
44
  app.include_router(wallet_router)
45
+ app.include_router(proxy_router)
46
 
47
  @app.get("/")
48
  def read_root():
 
93
  itype = item.get("type", "")
94
 
95
  if itype.startswith("tool"):
96
+ headers = {'User-Agent': 'Mozilla/5.0'}
97
+ # 【核心升级】:提取该资源绑定的私有库密匙,如果没有则用全局兜底
98
+ github_token = item.get("github_token") or os.environ.get("GITHUB_PAT")
99
+
100
+ if github_token and link.startswith("https://github.com/"):
101
+ # 针对私有库:调用 GitHub API 带着 Token 进行身份核验探测
102
+ repo_parts = link.rstrip("/").split("/")
103
+ if len(repo_parts) >= 2:
104
+ owner, repo = repo_parts[-2], repo_parts[-1]
105
+ api_link = f"https://api.github.com/repos/{owner}/{repo}"
106
+ headers["Authorization"] = f"Bearer {github_token}"
107
+ headers["Accept"] = "application/vnd.github.v3+json"
108
+ try:
109
+ req_obj = urllib.request.Request(api_link, method="GET", headers=headers)
110
+ with urllib.request.urlopen(req_obj, timeout=5) as response:
111
+ if response.status >= 400:
112
+ return JSONResponse(content={"error": "私有仓库访问失败,可能密匙已失效"}, status_code=400)
113
+ except urllib.error.HTTPError as e:
114
+ # 如果抛出 HTTPError (如 401 Unauthorized 或 404 Not Found),说明 Token 假了或库被删了
115
+ return JSONResponse(content={"error": f"该私有库的访问密匙已失效或仓库已被原作者删除 (HTTP {e.code})"}, status_code=400)
116
+ except Exception:
117
+ return JSONResponse(content={"error": "无法连接到 GitHub 验证仓库有效性"}, status_code=400)
118
+
119
+ # 走到这里说明私有库和密匙都是 100% 有效的,放行!
120
+ return {"status": "success"}
121
+
122
+ # 针对普通公开库的无感探测
123
  try:
124
+ req_obj = urllib.request.Request(link, method="HEAD", headers=headers)
125
  with urllib.request.urlopen(req_obj, timeout=5) as response:
126
  if response.status >= 400:
127
  return JSONResponse(content={"error": "原作者的 Git 仓库已失效或设为私有"}, status_code=400)
requirements.txt CHANGED
@@ -6,4 +6,5 @@ datasets
6
  python-multipart
7
  alibabacloud_dysmsapi20170525==2.0.24
8
  sqlalchemy
9
- httpx
 
 
6
  python-multipart
7
  alibabacloud_dysmsapi20170525==2.0.24
8
  sqlalchemy
9
+ httpx
10
+ python-alipay-sdk
router_wallet.py CHANGED
@@ -1,9 +1,10 @@
1
  # router_wallet.py
2
- from fastapi import APIRouter, Depends, HTTPException
3
  from sqlalchemy.orm import Session
4
  import time
5
  import uuid
6
  import hashlib
 
7
  from database_sql import get_db
8
  from models_sql import Wallet, Transaction, Ownership
9
  from models import RechargeRequest, WithdrawRequest, PurchaseRequest
@@ -12,6 +13,26 @@ from router_users import VERIFY_CODES
12
 
13
  router = APIRouter()
14
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15
  def calculate_tx_hash(tx_id, account, tx_type, amount, prev_hash):
16
  """计算哈希值:依赖前一笔订单的哈希,实现链式防篡改"""
17
  data = f"{tx_id}{account}{tx_type}{amount}{prev_hash}"
@@ -29,20 +50,83 @@ def record_transaction(db: Session, account: str, tx_type: str, amount: int, tar
29
  db.add(new_tx)
30
  return new_tx
31
 
32
- @router.post("/api/wallet/recharge")
33
- async def recharge_points(req: RechargeRequest, db: Session = Depends(get_db)):
34
- """充值接口"""
35
- wallet = db.query(Wallet).filter(Wallet.account == req.account).first()
36
- if not wallet:
37
- wallet = Wallet(account=req.account)
38
- db.add(wallet)
39
-
40
- # 模拟支付成功后直接给用户加钱
41
- wallet.balance += req.amount
42
- record_transaction(db, req.account, "RECHARGE", req.amount, "MOCK_ALIPAY_ORDER")
43
-
 
44
  db.commit()
45
- return {"status": "success", "balance": wallet.balance}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
46
 
47
  @router.post("/api/wallet/purchase")
48
  async def purchase_item(req: PurchaseRequest, db: Session = Depends(get_db)):
@@ -54,36 +138,27 @@ async def purchase_item(req: PurchaseRequest, db: Session = Depends(get_db)):
54
  price = int(item.get("price", 0))
55
  author = item.get("author")
56
 
57
- # 1. 优先检查是否已经购买或获取过 (核心:无论后续怎么改价,只要所有权表里有你,直接放行)
58
  owned = db.query(Ownership).filter(Ownership.account == req.account, Ownership.item_id == req.item_id).first()
59
- if owned: return {"status": "success", "message": "已永久授权,无需重复扣费"}
 
60
 
61
- # ==========================================
62
- # 2. 【核心修改】免费商品或创作者本人的“0元购”固化
63
- # ==========================================
64
  if price <= 0 or req.account == author:
65
- # 直接写入所有权表,赋予永久权限
66
  new_owner = Ownership(account=req.account, item_id=req.item_id)
67
  db.add(new_owner)
68
- # 记录一笔金额为 0 的流水,保证账本的完整溯源
69
  record_transaction(db, req.account, "CONSUME", 0, req.item_id)
70
  db.commit()
71
- return {"status": "success", "message": "免费商品或创作者本人,已永久授权"}
72
 
73
- # 3. 开启数据库锁防止并发重复扣款 (排他锁)
74
  buyer_wallet = db.query(Wallet).filter(Wallet.account == req.account).with_for_update().first()
75
  if not buyer_wallet or buyer_wallet.balance < price:
76
- raise HTTPException(status_code=400, detail="余额不足,请先充值")
77
 
78
- # 4. 执行扣费
79
  buyer_wallet.balance -= price
80
  record_transaction(db, req.account, "CONSUME", -price, req.item_id)
81
 
82
- # 5. 记录所有权
83
  new_owner = Ownership(account=req.account, item_id=req.item_id)
84
  db.add(new_owner)
85
 
86
- # 6. 卖家获得收益
87
  author_wallet = db.query(Wallet).filter(Wallet.account == author).with_for_update().first()
88
  if not author_wallet:
89
  author_wallet = Wallet(account=author)
@@ -93,7 +168,12 @@ async def purchase_item(req: PurchaseRequest, db: Session = Depends(get_db)):
93
  record_transaction(db, author, "EARN", price, req.item_id)
94
 
95
  db.commit()
96
- return {"status": "success"}
 
 
 
 
 
97
 
98
  @router.post("/api/wallet/withdraw")
99
  async def withdraw_earnings(req: WithdrawRequest, db: Session = Depends(get_db)):
@@ -114,11 +194,35 @@ async def withdraw_earnings(req: WithdrawRequest, db: Session = Depends(get_db))
114
  if req.amount < 100:
115
  raise HTTPException(status_code=400, detail="最低提现金额为 100 积分")
116
 
117
- wallet.earn_balance -= req.amount
118
- wallet.frozen_balance += req.amount
119
- record_transaction(db, req.account, "WITHDRAW_FREEZE", -req.amount, req.alipay_account)
120
-
121
- VERIFY_CODES.pop(cache_key, None)
122
-
123
- db.commit()
124
- return {"status": "success", "earn_balance": wallet.earn_balance}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  # router_wallet.py
2
+ from fastapi import APIRouter, Depends, HTTPException, Request
3
  from sqlalchemy.orm import Session
4
  import time
5
  import uuid
6
  import hashlib
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
 
13
 
14
  router = APIRouter()
15
 
16
+ # =======================================================
17
+ # 🏦 支付宝 SDK 初始化 (带防崩溃保护)
18
+ # =======================================================
19
+ try:
20
+ from alipay import AliPay
21
+ from alipay.utils import AliPayConfig
22
+ alipay = AliPay(
23
+ appid=os.environ.get("ALIPAY_APPID", ""),
24
+ app_notify_url="https://zhiwei666-comfyui-ranking-api.hf.space/api/wallet/alipay_notify",
25
+ app_private_key_string=os.environ.get("ALIPAY_PRIVATE_KEY", "").replace("\\n", "\n"),
26
+ alipay_public_key_string=os.environ.get("ALIPAY_PUBLIC_KEY", "").replace("\\n", "\n"),
27
+ sign_type="RSA2",
28
+ debug=False, # 生产环境设为 False
29
+ config=AliPayConfig(timeout=15)
30
+ )
31
+ except Exception as e:
32
+ alipay = None
33
+ print("⚠️ 支付宝 SDK 初始化跳过 (环境变量未配置,处于开发模式):", e)
34
+
35
+
36
  def calculate_tx_hash(tx_id, account, tx_type, amount, prev_hash):
37
  """计算哈希值:依赖前一笔订单的哈希,实现链式防篡改"""
38
  data = f"{tx_id}{account}{tx_type}{amount}{prev_hash}"
 
50
  db.add(new_tx)
51
  return new_tx
52
 
53
+ # =======================================================
54
+ # 💰 充值入金链路 (真实预下单 -> 扫码 -> 异步回调 -> 查单)
55
+ # =======================================================
56
+
57
+ @router.post("/api/wallet/create_recharge_order")
58
+ async def create_recharge_order(req: RechargeRequest, db: Session = Depends(get_db)):
59
+ """生成真实的支付宝当面付二维码"""
60
+ if not alipay:
61
+ raise HTTPException(status_code=500, detail="后台未配置支付密钥,暂不支持真实付款")
62
+
63
+ # 1. 使用 target_id 存储订单状态 "PENDING",不影响哈希校验
64
+ tx = record_transaction(db, req.account, "RECHARGE", req.amount, "PENDING")
65
+ out_trade_no = tx.tx_id
66
  db.commit()
67
+
68
+ try:
69
+ # 2. 向支付宝发起真实预下单
70
+ result = alipay.api_alipay_trade_precreate(
71
+ subject=f"ComfyUI社区精选 - 充值 {req.amount} 积分",
72
+ out_trade_no=out_trade_no,
73
+ total_amount=str(req.amount),
74
+ )
75
+ if result.get("code") == "10000":
76
+ return {"status": "success", "order_id": out_trade_no, "qr_code_url": result.get("qr_code")}
77
+ else:
78
+ raise HTTPException(status_code=500, detail=f"生成支付码失败: {result.get('msg')}")
79
+ except Exception as e:
80
+ raise HTTPException(status_code=500, detail=f"支付接口异常: {str(e)}")
81
+
82
+ @router.post("/api/wallet/alipay_notify")
83
+ async def alipay_notify(request: Request, db: Session = Depends(get_db)):
84
+ """🚨 核心:接收支付宝付款成功的异步回调,验签并加钱"""
85
+ data = dict(await request.form())
86
+ signature = data.pop("sign", None)
87
+
88
+ if not alipay or not signature:
89
+ return "fail"
90
+
91
+ # 1. 严格验签,防伪造
92
+ success = alipay.verify(data, signature)
93
+ if success and data.get("trade_status") in ("TRADE_SUCCESS", "TRADE_FINISHED"):
94
+ out_trade_no = data.get("out_trade_no")
95
+ total_amount = float(data.get("total_amount", 0))
96
+
97
+ # 2. 悲观锁查询该笔 PENDING 订单,防并发重复加款
98
+ tx = db.query(Transaction).filter(Transaction.tx_id == out_trade_no).with_for_update().first()
99
+ if not tx or tx.target_id == "SUCCESS":
100
+ return "success" # 已经处理过,直接返回成功让支付宝停止轰炸
101
+
102
+ if tx.amount != int(total_amount):
103
+ return "fail" # 金额对不上,拦截黑客攻击
104
+
105
+ # 3. 安全加款
106
+ wallet = db.query(Wallet).filter(Wallet.account == tx.account).with_for_update().first()
107
+ if not wallet:
108
+ wallet = Wallet(account=tx.account)
109
+ db.add(wallet)
110
+
111
+ wallet.balance += tx.amount
112
+ tx.target_id = "SUCCESS" # 将流水标记为已完成
113
+ db.commit()
114
+
115
+ return "success" # 必须返回纯文本 success 给支付宝
116
+ return "fail"
117
+
118
+ @router.get("/api/wallet/check_order/{order_id}")
119
+ async def check_order(order_id: str, db: Session = Depends(get_db)):
120
+ """前端轮询查单接口"""
121
+ tx = db.query(Transaction).filter(Transaction.tx_id == order_id).first()
122
+ if not tx:
123
+ raise HTTPException(status_code=404, detail="订单不存在")
124
+ return {"status": tx.target_id} # 返回 PENDING 或 SUCCESS
125
+
126
+
127
+ # =======================================================
128
+ # 🛒 消费闭环链路
129
+ # =======================================================
130
 
131
  @router.post("/api/wallet/purchase")
132
  async def purchase_item(req: PurchaseRequest, db: Session = Depends(get_db)):
 
138
  price = int(item.get("price", 0))
139
  author = item.get("author")
140
 
 
141
  owned = db.query(Ownership).filter(Ownership.account == req.account, Ownership.item_id == req.item_id).first()
142
+ if owned:
143
+ return {"status": "success", "message": "已永久授权,无需重复扣费", "already_owned": True}
144
 
 
 
 
145
  if price <= 0 or req.account == author:
 
146
  new_owner = Ownership(account=req.account, item_id=req.item_id)
147
  db.add(new_owner)
 
148
  record_transaction(db, req.account, "CONSUME", 0, req.item_id)
149
  db.commit()
150
+ return {"status": "success", "message": "免费商品或创作者本人,已永久授权", "already_owned": True}
151
 
 
152
  buyer_wallet = db.query(Wallet).filter(Wallet.account == req.account).with_for_update().first()
153
  if not buyer_wallet or buyer_wallet.balance < price:
154
+ raise HTTPException(status_code=400, detail="余额不足")
155
 
 
156
  buyer_wallet.balance -= price
157
  record_transaction(db, req.account, "CONSUME", -price, req.item_id)
158
 
 
159
  new_owner = Ownership(account=req.account, item_id=req.item_id)
160
  db.add(new_owner)
161
 
 
162
  author_wallet = db.query(Wallet).filter(Wallet.account == author).with_for_update().first()
163
  if not author_wallet:
164
  author_wallet = Wallet(account=author)
 
168
  record_transaction(db, author, "EARN", price, req.item_id)
169
 
170
  db.commit()
171
+ return {"status": "success", "already_owned": False}
172
+
173
+
174
+ # =======================================================
175
+ # 💸 提现出金链路 (引入支付宝自动单笔打款)
176
+ # =======================================================
177
 
178
  @router.post("/api/wallet/withdraw")
179
  async def withdraw_earnings(req: WithdrawRequest, db: Session = Depends(get_db)):
 
194
  if req.amount < 100:
195
  raise HTTPException(status_code=400, detail="最低提现金额为 100 积分")
196
 
197
+ if not alipay:
198
+ # 降级模式:如果没配置真实支付,仅记录冻结流水,需人工审核打款
199
+ wallet.earn_balance -= req.amount
200
+ wallet.frozen_balance += req.amount
201
+ record_transaction(db, req.account, "WITHDRAW_FREEZE", -req.amount, req.alipay_account)
202
+ VERIFY_CODES.pop(cache_key, None)
203
+ db.commit()
204
+ return {"status": "success", "earn_balance": wallet.earn_balance, "message": "提现申请已提交 (暂为开发模式,已冻结)"}
205
+
206
+ # 真实企业级模式:直接调用支付宝单笔转账接口打款到用户余额
207
+ out_biz_no = f"WD_{int(time.time())}_{uuid.uuid4().hex[:6]}"
208
+ try:
209
+ result = alipay.api_alipay_fund_trans_toaccount_transfer(
210
+ out_biz_no=out_biz_no,
211
+ payee_type="ALIPAY_LOGONID",
212
+ payee_account=req.alipay_account,
213
+ amount=str(req.amount),
214
+ payee_real_name=req.real_name,
215
+ remark="ComfyUI社区精选创作者收益提现"
216
+ )
217
+
218
+ if result.get("code") == "10000":
219
+ # 打款秒成功,彻底扣除可提现收益
220
+ wallet.earn_balance -= req.amount
221
+ record_transaction(db, req.account, "WITHDRAW", -req.amount, req.alipay_account)
222
+ VERIFY_CODES.pop(cache_key, None)
223
+ db.commit()
224
+ return {"status": "success", "earn_balance": wallet.earn_balance, "message": "打款已秒到账您的支付宝!"}
225
+ else:
226
+ raise HTTPException(status_code=400, detail=f"打款风控拦截: {result.get('sub_msg')}")
227
+ except Exception as e:
228
+ raise HTTPException(status_code=500, detail=f"提现接口异常: {str(e)}")