霖雨寺

This commit is contained in:
wenfp
2026-07-27 09:57:35 +08:00
commit 6e531cf7af
57 changed files with 7529 additions and 0 deletions
+132
View File
@@ -0,0 +1,132 @@
import json
from decimal import Decimal, ROUND_HALF_UP
from uuid import uuid4
from fastapi import APIRouter, HTTPException, Request, status
from app.config import wechat_pay_settings
from app.database import Database
from app.schemas import WeChatJSAPIPayRequest, WeChatJSAPIPayResponse
from app.temple_scope import TempleId, ensure_temple_exists
from app.wechat_pay import (
build_jsapi_pay_params,
decrypt_notify_resource,
post_wechat_json,
verify_notify_signature,
)
router = APIRouter(prefix="/payments", tags=["payments"])
def yuan_to_fen(amount: float) -> int:
return int((Decimal(str(amount)) * Decimal("100")).quantize(Decimal("1"), rounding=ROUND_HALF_UP))
def get_order_row(db: Database, temple_id: int, order_id: int):
row = db.execute(
"""
SELECT id, temple_id, item_name, amount, status
FROM orders
WHERE id = ? AND temple_id = ?
""",
(order_id, temple_id),
).fetchone()
if row is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="订单不存在")
return row
@router.post("/wechat/jsapi", response_model=WeChatJSAPIPayResponse)
def create_wechat_jsapi_payment(payload: WeChatJSAPIPayRequest, db: Database, temple_id: TempleId) -> WeChatJSAPIPayResponse:
ensure_temple_exists(db, temple_id)
order = get_order_row(db, temple_id, payload.order_id)
if order["status"] == "paid":
raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail="订单已支付,无需重复发起支付")
amount = yuan_to_fen(float(order["amount"]))
if amount <= 0:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="订单金额必须大于 0")
out_trade_no = f"T{temple_id}O{order['id']}{uuid4().hex[:16]}"
description = (payload.description or order["item_name"])[:127]
wechat_payload = {
"appid": wechat_pay_settings.appid,
"mchid": wechat_pay_settings.mch_id,
"description": description,
"out_trade_no": out_trade_no,
"notify_url": wechat_pay_settings.notify_url,
"amount": {
"total": amount,
"currency": "CNY",
},
"payer": {
"openid": payload.openid,
},
}
result = post_wechat_json("/v3/pay/transactions/jsapi", wechat_payload)
prepay_id = result.get("prepay_id")
if not prepay_id:
raise HTTPException(status_code=status.HTTP_502_BAD_GATEWAY, detail="微信支付未返回 prepay_id")
db.execute(
"""
INSERT INTO payment_transactions
(temple_id, order_id, out_trade_no, amount, status, prepay_id)
VALUES (?, ?, ?, ?, ?, ?)
""",
(temple_id, order["id"], out_trade_no, amount, "prepay", prepay_id),
)
db.commit()
return WeChatJSAPIPayResponse(
**build_jsapi_pay_params(prepay_id),
out_trade_no=out_trade_no,
prepay_id=prepay_id,
)
@router.post("/wechat/notify")
async def handle_wechat_pay_notify(request: Request, db: Database) -> dict[str, str]:
body = (await request.body()).decode("utf-8")
headers = {key.lower(): value for key, value in request.headers.items()}
if not verify_notify_signature(headers, body):
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="微信支付通知验签失败")
notify_payload = json.loads(body)
resource = notify_payload.get("resource")
if not isinstance(resource, dict):
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="微信支付通知缺少 resource")
trade = decrypt_notify_resource(resource)
out_trade_no = trade.get("out_trade_no")
trade_state = trade.get("trade_state")
transaction_id = trade.get("transaction_id")
if not out_trade_no:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="微信支付通知缺少商户订单号")
payment = db.execute(
"""
SELECT id, order_id
FROM payment_transactions
WHERE out_trade_no = ?
""",
(out_trade_no,),
).fetchone()
if payment is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="支付流水不存在")
status_value = "paid" if trade_state == "SUCCESS" else str(trade_state or "unknown").lower()
db.execute(
"""
UPDATE payment_transactions
SET status = ?, transaction_id = ?, raw_notify_json = ?, updated_at = CURRENT_TIMESTAMP
WHERE id = ?
""",
(status_value, transaction_id, json.dumps(trade, ensure_ascii=False), payment["id"]),
)
if trade_state == "SUCCESS":
db.execute("UPDATE orders SET status = 'paid' WHERE id = ?", (payment["order_id"],))
db.commit()
return {"code": "SUCCESS", "message": "成功"}