霖雨寺
This commit is contained in:
@@ -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": "成功"}
|
||||
Reference in New Issue
Block a user