File size: 10,176 Bytes
85494ee
5863217
6f73fb4
85494ee
 
 
 
5863217
85494ee
 
5e33680
85494ee
 
 
 
5863217
 
 
 
 
 
 
 
 
bc186bd
5863217
 
 
 
 
85494ee
 
6f73fb4
85494ee
6f73fb4
 
 
 
13f2c73
6f73fb4
 
13f2c73
6f73fb4
 
 
 
13f2c73
 
6f73fb4
 
 
13f2c73
6f73fb4
13f2c73
6f73fb4
13f2c73
 
6f73fb4
 
 
 
d9592d3
6f73fb4
d9592d3
6f73fb4
d9592d3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6f73fb4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
# router_wallet.py
from fastapi import APIRouter, Depends, HTTPException, Request
from fastapi.responses import Response
from sqlalchemy.orm import Session
import time
import uuid
import hashlib
import os
from database_sql import get_db
from models_sql import Wallet, Transaction, Ownership
from models import RechargeRequest, WithdrawRequest, PurchaseRequest, TipRequest
import 数据库连接 as json_db

router = APIRouter()

try:
    from alipay import AliPay
    from alipay.utils import AliPayConfig
    alipay = AliPay(
        appid=os.environ.get("ALIPAY_APPID", ""),
        app_notify_url="https://zhiwei666-comfyui-ranking-api.hf.space/api/wallet/alipay_notify",
        app_private_key_string=os.environ.get("ALIPAY_PRIVATE_KEY", "").replace("\\n", "\n"),
        alipay_public_key_string=os.environ.get("ALIPAY_PUBLIC_KEY", "").replace("\\n", "\n"),
        sign_type="RSA2",
        debug=False,
        config=AliPayConfig(timeout=15)
    )
except Exception as e:
    alipay = None

def calculate_tx_hash(tx_id, account, tx_type, amount, prev_hash):
    data = f"{tx_id}{account}{tx_type}{amount}{prev_hash}"
    return hashlib.sha256(data.encode()).hexdigest()

@router.post("/api/wallet/create_recharge_order")
async def create_recharge_order(req: RechargeRequest):
    if not alipay:
        raise HTTPException(status_code=500, detail="支付网关未配置或初始化失败")
    
    order_id = f"PAY_{int(time.time())}_{uuid.uuid4().hex[:6]}"
    subject = f"ComfyUI Community Points - {req.account}"
    
    order_string = alipay.api_alipay_trade_precreate(
        out_trade_no=order_id,
        total_amount=str(req.amount),
        subject=subject
    )
    
    qr_code_url = order_string.get("qr_code")
    if not qr_code_url:
        raise HTTPException(status_code=500, detail="生成支付二维码失败")
        
    return {"status": "success", "order_id": order_id, "qr_code": qr_code_url}

# 🟢 业务流转细节修复:正确解析 application/x-www-form-urlencoded
@router.post("/api/wallet/alipay_notify")
async def alipay_notify(request: Request, db: Session = Depends(get_db)):
    # 强制将表单数据解析为纯字典,防止由于数据类型错误导致验签失败
    form_data = await request.form()
    data = dict(form_data.items())
    
    signature = data.pop("sign", None)
    data.pop("sign_type", None)
    
    if not alipay or not signature or not alipay.verify(data, signature):
        return Response(content="fail", media_type="text/plain")
        
    if data.get("trade_status") in ("TRADE_SUCCESS", "TRADE_FINISHED"):
        order_id = data.get("out_trade_no")
        
        existing_tx = db.query(Transaction).filter(Transaction.tx_id == order_id).first()
        if not existing_tx:
            amount = int(float(data.get("total_amount", 0)))
            account = data.get("subject", "").split(" - ")[-1]
            
            wallet = db.query(Wallet).filter(Wallet.account == account).with_for_update().first()
            if not wallet:
                wallet = Wallet(account=account)
                db.add(wallet)
            
            wallet.balance += amount
            
            last_tx = db.query(Transaction).filter(Transaction.account == account).order_by(Transaction.created_at.desc()).first()
            prev_hash = last_tx.tx_hash if last_tx else "GENESIS_HASH"
            tx_hash = calculate_tx_hash(order_id, account, "RECHARGE", amount, prev_hash)
            
            new_tx = Transaction(
                tx_id=order_id, account=account, tx_type="RECHARGE", amount=amount,
                prev_hash=prev_hash, tx_hash=tx_hash
            )
            db.add(new_tx)
            db.commit()
            
    return Response(content="success", media_type="text/plain")

@router.get("/api/wallet/check_order/{order_id}")
async def check_order(order_id: str, db: Session = Depends(get_db)):
    tx = db.query(Transaction).filter(Transaction.tx_id == order_id).first()
    if tx:
        return {"status": "SUCCESS"}
    return {"status": "PENDING"}

@router.get("/api/wallet/{account}")
async def get_wallet(account: str, db: Session = Depends(get_db)):
    wallet = db.query(Wallet).filter(Wallet.account == account).first()
    if not wallet:
        return {"balance": 0, "earn_balance": 0, "tip_balance": 0}
    return {
        "balance": wallet.balance,
        "earn_balance": wallet.earn_balance,
        "tip_balance": wallet.tip_balance
    }

@router.post("/api/wallet/purchase")
async def purchase_item(req: PurchaseRequest, db: Session = Depends(get_db)):
    items_db = json_db.load_data("items.json", default_data=[])
    item = next((i for i in items_db if i["id"] == req.item_id), None)
    
    if not item:
        raise HTTPException(status_code=404, detail="商品不存在")
        
    price = int(item.get("price", 0))
    seller_account = item.get("author")
    
    if price <= 0 or req.account == seller_account:
        return {"status": "success", "already_owned": True}
        
    owned = db.query(Ownership).filter(Ownership.account == req.account, Ownership.item_id == req.item_id).first()
    if owned:
        return {"status": "success", "already_owned": True}
        
    buyer_wallet = db.query(Wallet).filter(Wallet.account == req.account).with_for_update().first()
    if not buyer_wallet or buyer_wallet.balance < price:
        raise HTTPException(status_code=402, detail="余额不足,请先充值")
        
    seller_wallet = db.query(Wallet).filter(Wallet.account == seller_account).with_for_update().first()
    if not seller_wallet:
        seller_wallet = Wallet(account=seller_account)
        db.add(seller_wallet)
        
    buyer_wallet.balance -= price
    seller_wallet.earn_balance += price
    
    new_ownership = Ownership(account=req.account, item_id=req.item_id)
    db.add(new_ownership)
    
    tx_id = f"BUY_{int(time.time())}_{uuid.uuid4().hex[:6]}"
    last_tx = db.query(Transaction).filter(Transaction.account == req.account).order_by(Transaction.created_at.desc()).first()
    prev_hash = last_tx.tx_hash if last_tx else "GENESIS_HASH"
    tx_hash = calculate_tx_hash(tx_id, req.account, "PURCHASE", -price, prev_hash)
    
    new_tx = Transaction(
        tx_id=tx_id, account=req.account, tx_type="PURCHASE", amount=-price,
        target_account=seller_account, prev_hash=prev_hash, tx_hash=tx_hash
    )
    db.add(new_tx)
    db.commit()
    
    return {"status": "success", "already_owned": False}

@router.post("/api/wallet/tip")
async def tip_user(req: TipRequest, db: Session = Depends(get_db)):
    if req.amount <= 0:
        raise HTTPException(status_code=400, detail="打赏金额必须大于0")
        
    sender_wallet = db.query(Wallet).filter(Wallet.account == req.sender_account).with_for_update().first()
    if not sender_wallet or sender_wallet.balance < req.amount:
        raise HTTPException(status_code=402, detail="余额不足")
        
    target_wallet = db.query(Wallet).filter(Wallet.account == req.target_account).with_for_update().first()
    if not target_wallet:
        target_wallet = Wallet(account=req.target_account)
        db.add(target_wallet)
        
    sender_wallet.balance -= req.amount
    target_wallet.tip_balance += req.amount
    
    tx_id = f"TIP_{int(time.time())}_{uuid.uuid4().hex[:6]}"
    last_tx = db.query(Transaction).filter(Transaction.account == req.sender_account).order_by(Transaction.created_at.desc()).first()
    prev_hash = last_tx.tx_hash if last_tx else "GENESIS_HASH"
    tx_hash = calculate_tx_hash(tx_id, req.sender_account, "TIP", -req.amount, prev_hash)
    
    new_tx = Transaction(
        tx_id=tx_id, account=req.sender_account, tx_type="TIP", amount=-req.amount,
        target_account=req.target_account, prev_hash=prev_hash, tx_hash=tx_hash
    )
    db.add(new_tx)
    db.commit()
    
    from notifications import add_notification
    display_sender = "匿名用户" if req.is_anonymous else req.sender_account
    add_notification(req.target_account, {
        "type": "tip", 
        "from_user": "system", 
        "target_item_title": "您的主页",
        "content": f"🎉 {display_sender} 给您打赏了 {req.amount} 积分!"
    })
    
    return {"status": "success", "balance": sender_wallet.balance}

@router.post("/api/wallet/withdraw")
async def withdraw(req: WithdrawRequest, db: Session = Depends(get_db)):
    key = f"{req.account}_withdraw"
    code_data = VERIFY_CODES.get(key)
    if not code_data or code_data["code"] != req.code or time.time() > code_data["expires"]:
        raise HTTPException(status_code=400, detail="验证码无效或已过期")
        
    wallet = db.query(Wallet).filter(Wallet.account == req.account).with_for_update().first()
    if not wallet:
        raise HTTPException(status_code=400, detail="钱包不存在")
        
    total_withdrawable = wallet.earn_balance + wallet.tip_balance
    if req.amount > total_withdrawable:
        raise HTTPException(status_code=400, detail="可提现余额不足")
        
    if req.amount <= wallet.earn_balance:
        wallet.earn_balance -= req.amount
    else:
        remaining = req.amount - wallet.earn_balance
        wallet.earn_balance = 0
        wallet.tip_balance -= remaining
        
    wallet.frozen_balance += req.amount
    
    tx_id = f"WD_{int(time.time())}_{uuid.uuid4().hex[:6]}"
    last_tx = db.query(Transaction).filter(Transaction.account == req.account).order_by(Transaction.created_at.desc()).first()
    prev_hash = last_tx.tx_hash if last_tx else "GENESIS_HASH"
    tx_hash = calculate_tx_hash(tx_id, req.account, "WITHDRAW", -req.amount, prev_hash)
    
    new_tx = Transaction(
        tx_id=tx_id, account=req.account, tx_type="WITHDRAW", amount=-req.amount,
        prev_hash=prev_hash, tx_hash=tx_hash
    )
    db.add(new_tx)
    db.commit()
    
    del VERIFY_CODES[key]
    return {"status": "success"}