# 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"}