Files
FastapiAdmin/backend/app/core/payment/alipay.py
T
zhangtao 73f2823692 refactor: 大规模代码整理与功能优化
1. 重构后端API路由、CRUD与模块结构,整合日志管理,移除废弃demo代码
2. 优化前端组件类型定义、样式与路由配置,修复权限判断逻辑
3. 调整默认排序规则、滚动条样式与工具类函数,更新依赖与配置文件
4. 修复多处类型不匹配与默认值问题,完善表单与菜单验证逻辑
2026-06-17 01:56:31 +08:00

121 lines
4.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""支付宝支付网关
依赖 cryptography(已在 pyproject.toml 中)进行 RSA 签名验签。
"""
from __future__ import annotations
import json
from datetime import datetime
from typing import Any
from urllib.parse import urlencode
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import padding, rsa
from app.config.setting import settings
from .base import BasePaymentGateway, CallbackResult, PaymentInfo
class AlipayGateway(BasePaymentGateway):
"""支付宝支付网关(App 支付 / 网站支付 / Native 扫码)"""
GATEWAY_URL = "https://openapi.alipay.com/gateway.do"
DEV_GATEWAY_URL = "https://openapi-sandbox.dl.alipaydev.com/gateway.do"
def __init__(self) -> None:
self.app_id = settings.PAYMENT_ALIPAY_APP_ID or ""
self._private_key = (settings.PAYMENT_ALIPAY_PRIVATE_KEY or "").encode()
self._alipay_public_key = (settings.PAYMENT_ALIPAY_PUBLIC_KEY or "").encode()
self.is_sandbox = settings.PAYMENT_ALIPAY_SANDBOX
self._gateway = self.DEV_GATEWAY_URL if self.is_sandbox else self.GATEWAY_URL
def _sign(self, params: dict[str, Any]) -> str:
"""RSA2 签名"""
sorted_params = sorted((k, v) for k, v in params.items() if v != "" and v is not None)
sign_str = "&".join(f"{k}={v}" for k, v in sorted_params)
private_key_obj = serialization.load_pem_private_key(
self._private_key,
password=None,
)
if not isinstance(private_key_obj, rsa.RSAPrivateKey):
raise TypeError("私钥类型不是 RSA")
signature = private_key_obj.sign(
sign_str.encode("utf-8"),
padding.PKCS1v15(),
hashes.SHA256(),
)
import base64
return base64.b64encode(signature).decode("utf-8")
def _verify(self, params: dict[str, Any], signature: str) -> bool:
"""验证 RSA2 签名"""
sorted_params = sorted(
(k, v) for k, v in params.items()
if k != "sign" and k != "sign_type" and v != "" and v is not None
)
sign_str = "&".join(f"{k}={v}" for k, v in sorted_params)
try:
public_key_obj = serialization.load_pem_public_key(self._alipay_public_key)
if not isinstance(public_key_obj, rsa.RSAPublicKey):
return False
import base64
public_key_obj.verify(
base64.b64decode(signature),
sign_str.encode("utf-8"),
padding.PKCS1v15(),
hashes.SHA256(),
)
return True
except Exception:
return False
async def create_payment(
self, order_no: str, amount: int, subject: str, notify_url: str
) -> PaymentInfo:
"""创建支付宝支付(返回 H5 支付页面 URL"""
biz_content = json.dumps({
"out_trade_no": order_no,
"total_amount": f"{amount / 100:.2f}",
"subject": subject,
"product_code": "FAST_INSTANT_TRADE_PAY",
}, ensure_ascii=False)
params = {
"app_id": self.app_id,
"method": "alipay.trade.page.pay",
"format": "JSON",
"charset": "utf-8",
"sign_type": "RSA2",
"timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
"version": "1.0",
"notify_url": notify_url,
"biz_content": biz_content,
}
params["sign"] = self._sign(params)
pay_url = f"{self._gateway}?{urlencode(params)}"
return PaymentInfo(pay_url=pay_url, trade_no="", raw=params)
async def verify_callback(self, data: dict[str, Any]) -> CallbackResult:
"""验证支付宝异步通知回调"""
sign = data.pop("sign", "")
_ = data.pop("sign_type", "") # noqa
verified = self._verify(data, sign) if sign else False
if verified and data.get("trade_status") in ("TRADE_SUCCESS", "TRADE_FINISHED"):
return CallbackResult(
verified=True,
transaction_id=data.get("trade_no"),
order_id=None, # 由调用方从 out_trade_no 解析
amount=int(float(data.get("total_amount", 0)) * 100),
raw=data,
)
return CallbackResult(verified=False, raw=data)