2026-01-14 17:00:43 +08:00
|
|
|
|
from alipay import AliPay
|
|
|
|
|
|
from flask import current_app
|
|
|
|
|
|
import os
|
2026-01-14 19:48:00 +08:00
|
|
|
|
import logging
|
|
|
|
|
|
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
2026-01-14 17:00:43 +08:00
|
|
|
|
|
|
|
|
|
|
class AlipayService:
|
|
|
|
|
|
def __init__(self):
|
|
|
|
|
|
self.app_id = current_app.config.get('ALIPAY_APP_ID')
|
|
|
|
|
|
self.private_key = current_app.config.get('ALIPAY_APP_PRIVATE_KEY')
|
|
|
|
|
|
self.public_key = current_app.config.get('ALIPAY_PUBLIC_KEY')
|
|
|
|
|
|
self.return_url = current_app.config.get('ALIPAY_RETURN_URL')
|
|
|
|
|
|
self.notify_url = current_app.config.get('ALIPAY_NOTIFY_URL')
|
|
|
|
|
|
self.debug = current_app.config.get('ALIPAY_DEBUG', True)
|
|
|
|
|
|
|
|
|
|
|
|
def get_alipay_client(self):
|
|
|
|
|
|
"""获取支付宝客户端实例"""
|
|
|
|
|
|
return AliPay(
|
|
|
|
|
|
appid=self.app_id,
|
|
|
|
|
|
app_notify_url=self.notify_url,
|
|
|
|
|
|
app_private_key_string=self.private_key,
|
|
|
|
|
|
alipay_public_key_string=self.public_key,
|
|
|
|
|
|
sign_type="RSA2",
|
|
|
|
|
|
debug=self.debug
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
def create_order_url(self, out_trade_no, total_amount, subject):
|
|
|
|
|
|
"""生成支付链接 (电脑网站支付)"""
|
|
|
|
|
|
alipay = self.get_alipay_client()
|
|
|
|
|
|
order_string = alipay.api_alipay_trade_page_pay(
|
|
|
|
|
|
out_trade_no=out_trade_no,
|
|
|
|
|
|
total_amount=str(total_amount),
|
|
|
|
|
|
subject=subject,
|
|
|
|
|
|
return_url=self.return_url,
|
|
|
|
|
|
notify_url=self.notify_url
|
|
|
|
|
|
)
|
|
|
|
|
|
# 拼接支付网关 (使用最新的支付宝沙箱域名)
|
|
|
|
|
|
gateway = "https://openapi-sandbox.dl.alipaydev.com/gateway.do" if self.debug else "https://openapi.alipay.com/gateway.do"
|
|
|
|
|
|
return f"{gateway}?{order_string}"
|
|
|
|
|
|
|
|
|
|
|
|
def verify_notify(self, data, signature):
|
|
|
|
|
|
"""验证通知签名"""
|
2026-01-14 19:48:00 +08:00
|
|
|
|
try:
|
2026-01-14 19:57:43 +08:00
|
|
|
|
if not signature or not data:
|
|
|
|
|
|
logger.error("签名或数据为空")
|
2026-01-14 19:48:00 +08:00
|
|
|
|
return False
|
|
|
|
|
|
|
2026-01-14 19:57:43 +08:00
|
|
|
|
# 创建数据副本
|
2026-01-14 19:48:00 +08:00
|
|
|
|
verify_data = data.copy()
|
|
|
|
|
|
|
2026-01-14 19:57:43 +08:00
|
|
|
|
# python-alipay-sdk 的 verify 方法会自动处理 sign 的移除
|
|
|
|
|
|
# 但为了保险,我们手动移除它,保留其他所有字段
|
2026-01-14 19:48:00 +08:00
|
|
|
|
verify_data.pop('sign', None)
|
2026-01-23 15:10:16 +08:00
|
|
|
|
verify_data.pop('sign_type', None)
|
2026-01-14 19:48:00 +08:00
|
|
|
|
|
|
|
|
|
|
alipay = self.get_alipay_client()
|
2026-01-14 19:57:43 +08:00
|
|
|
|
# 对于同步回调,sign_type 实际上是参与签名的(某些版本/接口)
|
|
|
|
|
|
# 对于异步通知,sign_type 通常不参与签名
|
|
|
|
|
|
# alipay.verify 会根据情况处理 sign_type
|
2026-01-14 19:48:00 +08:00
|
|
|
|
result = alipay.verify(verify_data, signature)
|
|
|
|
|
|
|
2026-01-14 19:57:43 +08:00
|
|
|
|
if not result:
|
2026-01-14 19:48:00 +08:00
|
|
|
|
logger.error("签名验证失败")
|
2026-01-14 19:57:43 +08:00
|
|
|
|
logger.error(f"待验证数据: {verify_data}")
|
|
|
|
|
|
logger.error(f"签名值: {signature}")
|
|
|
|
|
|
logger.error(f"使用的公钥前50位: {self.public_key[:50]}...")
|
|
|
|
|
|
|
|
|
|
|
|
# 检查公钥是否可能是应用公钥而非支付宝公钥
|
|
|
|
|
|
if self.private_key and self.public_key:
|
|
|
|
|
|
logger.warning("提示:如果签名持续验证失败,请确认 ALIPAY_PUBLIC_KEY 是“支付宝公钥”而非“应用公钥”")
|
|
|
|
|
|
else:
|
|
|
|
|
|
logger.info("签名验证成功")
|
2026-01-14 19:48:00 +08:00
|
|
|
|
|
|
|
|
|
|
return result
|
|
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
logger.error(f"验证签名时发生异常: {str(e)}", exc_info=True)
|
|
|
|
|
|
return False
|
2026-01-23 17:40:23 +08:00
|
|
|
|
|
|
|
|
|
|
def query_order_status(self, out_trade_no):
|
|
|
|
|
|
"""查询订单状态 - 主动查询支付宝获取真实支付状态"""
|
|
|
|
|
|
try:
|
|
|
|
|
|
alipay = self.get_alipay_client()
|
|
|
|
|
|
# 调用支付宝订单查询接口
|
|
|
|
|
|
result = alipay.api_alipay_trade_query(out_trade_no=out_trade_no)
|
|
|
|
|
|
|
|
|
|
|
|
if result:
|
|
|
|
|
|
logger.info(f"订单查询成功: {out_trade_no}, 状态: {result.get('trade_status')}")
|
|
|
|
|
|
return result
|
|
|
|
|
|
else:
|
|
|
|
|
|
logger.warning(f"订单查询返回为空: {out_trade_no}")
|
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
logger.error(f"查询订单状态异常: {str(e)}", exc_info=True)
|
|
|
|
|
|
return None
|