2026-02-10 14:23:29 +08:00
|
|
|
|
"""
|
2026-03-18 17:18:23 +08:00
|
|
|
|
支付宝 OpenAPI 封装(从 payment 迁入 app)
|
2026-02-10 14:23:29 +08:00
|
|
|
|
"""
|
2026-03-19 14:36:14 +08:00
|
|
|
|
|
2026-03-18 17:18:23 +08:00
|
|
|
|
from app.core.logging import get_logger
|
2026-02-10 14:23:29 +08:00
|
|
|
|
from typing import Dict, Optional
|
|
|
|
|
|
|
2026-03-18 17:18:23 +08:00
|
|
|
|
from app.features.payment.payment_config import AlipayConfig
|
|
|
|
|
|
from app.features.payment.schemas import NotifyResult, PaymentResult, PaymentStatus
|
|
|
|
|
|
from app.features.payment.payment_exceptions import (
|
|
|
|
|
|
PaymentConfigError,
|
|
|
|
|
|
PaymentCreateError,
|
|
|
|
|
|
PaymentNotifyError,
|
|
|
|
|
|
PaymentQueryError,
|
|
|
|
|
|
)
|
2026-02-10 14:23:29 +08:00
|
|
|
|
|
2026-03-18 17:18:23 +08:00
|
|
|
|
logger = get_logger(__name__)
|
2026-02-10 14:23:29 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class AlipayClient:
|
|
|
|
|
|
def __init__(self, config: AlipayConfig):
|
|
|
|
|
|
self._config = config
|
|
|
|
|
|
self._client = None
|
|
|
|
|
|
|
|
|
|
|
|
def _ensure_client(self):
|
|
|
|
|
|
if not self._config.is_configured:
|
|
|
|
|
|
raise PaymentConfigError("支付宝配置不完整,请检查环境变量")
|
|
|
|
|
|
if self._client is None:
|
|
|
|
|
|
try:
|
|
|
|
|
|
from alipay import AliPay
|
2026-03-19 14:36:14 +08:00
|
|
|
|
|
2026-02-10 14:23:29 +08:00
|
|
|
|
self._client = AliPay(
|
|
|
|
|
|
appid=self._config.app_id,
|
|
|
|
|
|
app_notify_url=self._config.notify_url,
|
|
|
|
|
|
app_private_key_string=self._config.private_key,
|
|
|
|
|
|
alipay_public_key_string=self._config.alipay_public_key,
|
|
|
|
|
|
sign_type=self._config.sign_type,
|
2026-03-18 17:18:23 +08:00
|
|
|
|
debug=False,
|
2026-02-10 14:23:29 +08:00
|
|
|
|
)
|
|
|
|
|
|
logger.info("支付宝客户端初始化成功")
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
raise PaymentConfigError(f"支付宝客户端初始化失败: {e}")
|
|
|
|
|
|
|
|
|
|
|
|
def create_app_order(
|
|
|
|
|
|
self,
|
|
|
|
|
|
out_trade_no: str,
|
|
|
|
|
|
total_amount: int,
|
|
|
|
|
|
subject: str,
|
|
|
|
|
|
) -> PaymentResult:
|
|
|
|
|
|
self._ensure_client()
|
|
|
|
|
|
try:
|
|
|
|
|
|
amount_yuan = f"{total_amount / 100:.2f}"
|
|
|
|
|
|
order_string = self._client.api_alipay_trade_app_pay(
|
|
|
|
|
|
out_trade_no=out_trade_no,
|
|
|
|
|
|
total_amount=amount_yuan,
|
|
|
|
|
|
subject=subject,
|
|
|
|
|
|
notify_url=self._config.notify_url,
|
|
|
|
|
|
)
|
|
|
|
|
|
if order_string:
|
2026-03-18 17:18:23 +08:00
|
|
|
|
logger.info("支付宝订单创建成功: %s", out_trade_no)
|
2026-02-10 14:23:29 +08:00
|
|
|
|
return PaymentResult(
|
|
|
|
|
|
success=True,
|
|
|
|
|
|
payment_method="alipay",
|
|
|
|
|
|
out_trade_no=out_trade_no,
|
|
|
|
|
|
alipay_order_string=order_string,
|
|
|
|
|
|
)
|
2026-03-18 17:18:23 +08:00
|
|
|
|
raise PaymentCreateError("支付宝下单失败: 返回空订单字符串")
|
2026-02-10 14:23:29 +08:00
|
|
|
|
except PaymentCreateError:
|
|
|
|
|
|
raise
|
|
|
|
|
|
except Exception as e:
|
2026-03-18 17:18:23 +08:00
|
|
|
|
logger.error("支付宝订单创建异常: %s", e)
|
2026-02-10 14:23:29 +08:00
|
|
|
|
raise PaymentCreateError(f"支付宝下单异常: {e}")
|
|
|
|
|
|
|
|
|
|
|
|
def verify_notify(self, params: Dict[str, str]) -> NotifyResult:
|
|
|
|
|
|
self._ensure_client()
|
|
|
|
|
|
try:
|
|
|
|
|
|
sign = params.pop("sign", None)
|
2026-03-18 17:18:23 +08:00
|
|
|
|
params.pop("sign_type", None)
|
2026-02-10 14:23:29 +08:00
|
|
|
|
if not sign:
|
|
|
|
|
|
raise PaymentNotifyError("支付宝回调缺少签名参数")
|
|
|
|
|
|
success = self._client.verify(params, sign)
|
|
|
|
|
|
if success:
|
|
|
|
|
|
trade_status = params.get("trade_status", "")
|
|
|
|
|
|
out_trade_no = params.get("out_trade_no", "")
|
|
|
|
|
|
trade_no = params.get("trade_no", "")
|
|
|
|
|
|
total_amount_str = params.get("total_amount", "0")
|
|
|
|
|
|
total_amount = int(float(total_amount_str) * 100)
|
|
|
|
|
|
return NotifyResult(
|
|
|
|
|
|
success=True,
|
|
|
|
|
|
out_trade_no=out_trade_no,
|
|
|
|
|
|
trade_no=trade_no,
|
|
|
|
|
|
total_amount=total_amount,
|
|
|
|
|
|
trade_status=trade_status,
|
|
|
|
|
|
)
|
2026-03-18 17:18:23 +08:00
|
|
|
|
raise PaymentNotifyError("支付宝回调验签失败")
|
2026-02-10 14:23:29 +08:00
|
|
|
|
except PaymentNotifyError:
|
|
|
|
|
|
raise
|
|
|
|
|
|
except Exception as e:
|
2026-03-18 17:18:23 +08:00
|
|
|
|
logger.error("支付宝回调处理异常: %s", e)
|
2026-02-10 14:23:29 +08:00
|
|
|
|
raise PaymentNotifyError(f"支付宝回调处理失败: {e}")
|
|
|
|
|
|
|
|
|
|
|
|
def query_order(self, out_trade_no: str) -> PaymentStatus:
|
|
|
|
|
|
self._ensure_client()
|
|
|
|
|
|
try:
|
|
|
|
|
|
result = self._client.api_alipay_trade_query(out_trade_no=out_trade_no)
|
2026-03-18 17:18:23 +08:00
|
|
|
|
if result and result.get("code") == "10000":
|
|
|
|
|
|
trade_status = result.get("trade_status", "")
|
|
|
|
|
|
trade_no = result.get("trade_no", "")
|
|
|
|
|
|
total_amount_str = result.get("total_amount", "0")
|
|
|
|
|
|
total_amount = int(float(total_amount_str) * 100)
|
|
|
|
|
|
unified_status = self._map_trade_status(trade_status)
|
|
|
|
|
|
return PaymentStatus(
|
|
|
|
|
|
success=True,
|
|
|
|
|
|
out_trade_no=out_trade_no,
|
|
|
|
|
|
trade_no=trade_no,
|
|
|
|
|
|
trade_status=unified_status,
|
|
|
|
|
|
total_amount=total_amount,
|
|
|
|
|
|
)
|
2026-03-19 14:36:14 +08:00
|
|
|
|
error_msg = (
|
|
|
|
|
|
result.get("sub_msg", result.get("msg", "未知错误"))
|
|
|
|
|
|
if result
|
|
|
|
|
|
else "空结果"
|
|
|
|
|
|
)
|
2026-03-18 17:18:23 +08:00
|
|
|
|
raise PaymentQueryError(f"查询支付宝订单失败: {error_msg}")
|
2026-02-10 14:23:29 +08:00
|
|
|
|
except PaymentQueryError:
|
|
|
|
|
|
raise
|
|
|
|
|
|
except Exception as e:
|
2026-03-18 17:18:23 +08:00
|
|
|
|
logger.error("查询支付宝订单异常: %s", e)
|
2026-02-10 14:23:29 +08:00
|
|
|
|
raise PaymentQueryError(f"查询支付宝订单异常: {e}")
|
|
|
|
|
|
|
|
|
|
|
|
def close_order(self, out_trade_no: str) -> bool:
|
|
|
|
|
|
self._ensure_client()
|
|
|
|
|
|
try:
|
|
|
|
|
|
result = self._client.api_alipay_trade_close(out_trade_no=out_trade_no)
|
|
|
|
|
|
if result and result.get("code") == "10000":
|
2026-03-18 17:18:23 +08:00
|
|
|
|
logger.info("支付宝订单已关闭: %s", out_trade_no)
|
2026-02-10 14:23:29 +08:00
|
|
|
|
return True
|
2026-03-18 17:18:23 +08:00
|
|
|
|
return False
|
2026-02-10 14:23:29 +08:00
|
|
|
|
except Exception as e:
|
2026-03-18 17:18:23 +08:00
|
|
|
|
logger.error("关闭支付宝订单异常: %s", e)
|
2026-02-10 14:23:29 +08:00
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
|
|
def _map_trade_status(alipay_status: str) -> str:
|
|
|
|
|
|
status_map = {
|
|
|
|
|
|
"WAIT_BUYER_PAY": "NOTPAY",
|
|
|
|
|
|
"TRADE_CLOSED": "CLOSED",
|
|
|
|
|
|
"TRADE_SUCCESS": "SUCCESS",
|
|
|
|
|
|
"TRADE_FINISHED": "SUCCESS",
|
|
|
|
|
|
}
|
|
|
|
|
|
return status_map.get(alipay_status, alipay_status)
|