146 lines
5.7 KiB
Python
146 lines
5.7 KiB
Python
"""
|
||
支付宝 OpenAPI 封装(从 payment 迁入 app)
|
||
"""
|
||
from app.core.logging import get_logger
|
||
from typing import Dict, Optional
|
||
|
||
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,
|
||
)
|
||
|
||
logger = get_logger(__name__)
|
||
|
||
|
||
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
|
||
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,
|
||
debug=False,
|
||
)
|
||
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:
|
||
logger.info("支付宝订单创建成功: %s", out_trade_no)
|
||
return PaymentResult(
|
||
success=True,
|
||
payment_method="alipay",
|
||
out_trade_no=out_trade_no,
|
||
alipay_order_string=order_string,
|
||
)
|
||
raise PaymentCreateError("支付宝下单失败: 返回空订单字符串")
|
||
except PaymentCreateError:
|
||
raise
|
||
except Exception as e:
|
||
logger.error("支付宝订单创建异常: %s", e)
|
||
raise PaymentCreateError(f"支付宝下单异常: {e}")
|
||
|
||
def verify_notify(self, params: Dict[str, str]) -> NotifyResult:
|
||
self._ensure_client()
|
||
try:
|
||
sign = params.pop("sign", None)
|
||
params.pop("sign_type", None)
|
||
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,
|
||
)
|
||
raise PaymentNotifyError("支付宝回调验签失败")
|
||
except PaymentNotifyError:
|
||
raise
|
||
except Exception as e:
|
||
logger.error("支付宝回调处理异常: %s", e)
|
||
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)
|
||
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,
|
||
)
|
||
error_msg = result.get("sub_msg", result.get("msg", "未知错误")) if result else "空结果"
|
||
raise PaymentQueryError(f"查询支付宝订单失败: {error_msg}")
|
||
except PaymentQueryError:
|
||
raise
|
||
except Exception as e:
|
||
logger.error("查询支付宝订单异常: %s", e)
|
||
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":
|
||
logger.info("支付宝订单已关闭: %s", out_trade_no)
|
||
return True
|
||
return False
|
||
except Exception as e:
|
||
logger.error("关闭支付宝订单异常: %s", e)
|
||
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)
|