经过与客服的沟通,找到了问题真正的原因:
主程序中,这个写法是错误的:
client.execute(request)
正确的应该是:
client.page_execute(request)
下面都是错误的内容,全部弃用:
支付宝python的SDK 电脑网站支付模块错误及修正AlipayTradePagePay
别的不听,直接做,点这里:
正文:
①按照支付宝给的python结构(Python - 支付宝文档中心 (alipay.com)):使用电脑网站支付AlipayTradePagePay应该是这样的:
from alipay.aop.api.domain.AlipayTradePagePayModel import AlipayTradePagePayModel
from alipay.aop.api.request.AlipayTradePagePayRequest import AlipayTradePagePayRequest
from alipay.aop.api.response.AlipayTradePagePayResponse import AlipayTradePagePayResponse
#下面是构建支付宝通用的初始化内容 client
def alipay_client_sandbox():
# 日志配置
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s (%(filename)s:%(lineno)d)',
datefmt='%Y-%m-%d %H:%M:%S',
filemode='a', )
logger = logging.getLogger('')
# 配置
alipay_client_config = AlipayClientConfig()
# 支付宝网关(固定)
alipay_client_config.server_url = 'https://openapi.alipay.com/gateway.do'
# APPID 即创建应用后生成
alipay_client_config.app_id = ''
# AES秘钥,开放平台接口内容加密方式中获取
alipay_client_config.encrypt_key = ''
# 接口加密方式,目前支持AES
alipay_client_config.encrypt_type = 'AES'
# 生成签名字符串所使用的签名算法类型,目前支持 RSA2 算法。
alipay_client_config.sign_type = 'RSA2'
# 编码集,支持 GBK/UTF-8
alipay_client_config.charset = 'utf-8'
# 参数返回格式,只支持 JSON(固定)
alipay_client_config.format = 'json'
# 支付宝公钥
alipay_client_config.alipay_public_key = ''
# 开发者私钥,由开发者自己生成。格式:PKCS1
alipay_client_config.app_private_key = ''
return DefaultAlipayClient(alipay_client_config=alipay_client_config, logger=logger)
# 实例化客户端
client = alipay_client_sandbox()
#下面是业务请求参数
# 构造请求参数对象
model = AlipayTradePagePayModel()
#商户订单号
model.out_trade_no = '12345abcde'
#订单总金额,
model.total_amount = "1.01"
#订单标题
model.subject = '测试商品'
#销售产品码
model.product_code = 'FAST_INSTANT_TRADE_PAY'
#【可选】PC扫码支付的方式。
model.qr_pay_mode = "2"
request = AlipayTradePagePayRequest(biz_model=model)
# 执行API调用
response_content = False
try:
response_content = client.execute(request) # 支付宝的SDK大有问题!!!:aes_encrypt_content
except Exception as e:
logging.error(f"Error when executing request: {e}")
logging.error(traceback.format_exc())
if not response_content:
print("failed execute")
else:
response = AlipayTradePagePayResponse()
# 解析响应结果
response.parse_response_content(response_content)
if response.is_success():
# 如果业务成功,可以通过response属性获取需要的值
print("get response trade_no:" + response.trade_no)
else:
# 如果业务失败,可以从错误码中可以得知错误情况,具体错误码信息可以查看接口文档
print(response.code + "," + response.msg + "," + response.sub_code + "," + response.sub_msg)
②然而会报错:(我这里添加了traceback,报错能定位到位置,否则只按照官方给的logger是摸不着头脑的)
.env\Lib\site-packages\alipay\aop\api\util\WebUtils.py", line 161, in do_post
raise ResponseException('[' + THREAD_LOCAL.uuid + ']invalid http status ' + str(response.status) + \
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
TypeError: can only concatenate str (not "bytes") to str
③那么就根据报错来到了.env/Lib/site-packages/alipay/aop/api/util/WebUtils.py
通过print定位大法,找到了(在3.7.249这个版本里时147行)
response = connection.getresponse()
这个返回值时302的重定向,转到另一个链接了。然而下面的状态判断却只有“如果不是200该如何报错”,这里显然有问题!
if response.status != 200:
raise ResponseException('[' + THREAD_LOCAL.uuid + ']invalid http status ' + str(response.status) + ',detail body:' + response.read())
④那么,既然他们少写了一个“如果是302该如何”,那咱就补上一个:
(想到用response.getheader('Location')这个方法借助了AI的力量,AI解释道:
在HTTP协议中,Location
是一个响应头字段,用于指示客户端应该重定向到的新的URL地址。当服务器返回302状态码(Found)时,通常会包含一个Location
头,告诉客户端新的资源位置。)
# 检查是否发生了重定向
if response.status == 302:
# 获取重定向的新URL
pay_url = response.getheader('Location')
print(pay_url)
return pay_url
⑤好,现在再运行第一步的代码,就能出来一个可以访问的url了,点击访问:
是一个正常的支付页面啦!
用支付宝沙盒扫一扫试一试:
支付测试很顺利。
⑥虽然我们需要的电脑网站支付已经完成了,但是还有一个附加的问题:
⑥①主程序的->client.execute(request)使用的->.env/Lib/site-packages/alipay/aop/api/DefaultAlipayClient.py里的->执行请求接口:def execute最后的->return self.__parse_response(response)->
根据self.__parse_response(response)的返回值->应该是期望输出一个类似
b'"url_response": {返回的url},"sign": "支付宝返回的签名"}'
这样一个参数给到->"内部方法,解析请求返回结果并做验签"(def __parse_response)
进行校验->校验后再做一些什么操作(后面的我也有些看不懂了,诚心向有方法的人请教),
⑥②但问题在于,支付宝只是做了一个302重定向,然后返回了一个url,没有返回什么签名sign(我尝试过将上面“内部方法,通过请求requests对象构造请求参数__prepare_request_patams”中传出去的签名sign直接再传进来,但发现验签不通过,说明支付宝那边黑箱的结构并不是直接返回这么简单),-> 这就意味着咱自己是不能通过像是
response = str('"url_response"'+':{"'+response+'"},'+'"sign"'+':"'+self.sign+'"}').encode('utf-8')
这样的方式自己构建self.__parse_response(response)的返回值【缺少了sign】。
⑥③所以我目前还是想不到理想中的只需要改动一小部分就能实现原本sdk期望的所有功能的改进方法,
不给既然已经有了最终需要的pay_url电脑网站支付链接,其他的其实都还好说:就假设程序和支付宝的设置再上线前都将会配置好,不顾及程序异常的报错情况->
⑥③①将pay_url直接作为response输出去:
·位置:.env/Lib/site-packages/alipay/aop/api/util/WebUtils.py
·修改后:
# 检查是否发生了重定向
if response.status == 302: #好坑的支付宝,原本这是没有的,没有是错误的!
# 获取重定向的新URL
pay_url = response.getheader('Location')
return pay_url
if response.status != 200: #【原始内容】
raise ResponseException('[' + THREAD_LOCAL.uuid + ']invalid http status ' + str(response.status) + \
',detail body:' + response.read())
result = pay_url#response.read()
try:
response.close()
connection.close()
except Exception:
pass
return result
·原始:
# 检查是否发生了重定向
if response.status == 302: #好坑的支付宝,原本这是没有的,没有是错误的!
# 获取重定向的新URL
pay_url = response.getheader('Location')
return pay_url
if response.status != 200: #【原始内容】
raise ResponseException('[' + THREAD_LOCAL.uuid + ']invalid http status ' + str(response.status) + \
',detail body:' + response.read())
result = response.read() #这里有修改
try:
response.close()
connection.close()
except Exception:
pass
return result #这里有修改
⑥③②跳过“内部方法,解析请求返回结果并做验签(def __parse_response)”这一步,直接输出pay_url
·位置:.env/Lib/site-packages/alipay/aop/api/DefaultAlipayClient.py
·修改后:
"""
执行接口请求
"""
def execute(self, request):
THREAD_LOCAL.uuid = str(uuid.uuid1())
headers = {
'Content-type': 'application/x-www-form-urlencoded;charset=' + self.__config.charset,
"Cache-Control": "no-cache",
"Connection": "Keep-Alive",
"User-Agent": ALIPAY_SDK_PYTHON_VERSION,
"log-uuid": THREAD_LOCAL.uuid,
}
query_string, params = self.__prepare_request(request)
multipart_params = request.get_multipart_params()
if multipart_params and len(multipart_params) > 0:
response = do_multipart_post(self.__config.server_url, query_string, headers, params, multipart_params,
self.__config.charset, self.__config.timeout)
else:
response = do_post(self.__config.server_url, query_string, headers, params, self.__config.charset,
self.__config.timeout)
return response #这里有修改
·原始内容:
"""
执行接口请求
"""
def execute(self, request):
THREAD_LOCAL.uuid = str(uuid.uuid1())
headers = {
'Content-type': 'application/x-www-form-urlencoded;charset=' + self.__config.charset,
"Cache-Control": "no-cache",
"Connection": "Keep-Alive",
"User-Agent": ALIPAY_SDK_PYTHON_VERSION,
"log-uuid": THREAD_LOCAL.uuid,
}
query_string, params = self.__prepare_request(request)
multipart_params = request.get_multipart_params()
if multipart_params and len(multipart_params) > 0:
response = do_multipart_post(self.__config.server_url, query_string, headers, params, multipart_params,
self.__config.charset, self.__config.timeout)
else:
response = do_post(self.__config.server_url, query_string, headers, params, self.__config.charset,
self.__config.timeout)
return self.__parse_response(response)
总结:
总结一下所有修改的地方:
①:添加302跳转
·文件位置:.env/Lib/site-packages/alipay/aop/api/util/WebUtils.py
·代码位置:def do_post里
response = connection.getresponse()
与
if response.status != 200:
之间
·操作——插入:
# 检查是否发生了重定向
if response.status == 302: #好坑的支付宝,原本这是没有的,没有是错误的!
# 获取重定向的新URL
pay_url = response.getheader('Location')
return pay_url
②:让do_post输出支付页面的url
·文件位置:.env/Lib/site-packages/alipay/aop/api/util/WebUtils.py
·代码位置:def do_post里
if response.status != 200:
raise ResponseException('[' + THREAD_LOCAL.uuid + ']invalid http status ' + str(response.status) + \
',detail body:' + response.read())
与
try:
response.close()
connection.close()
之间
·操作:
将result = response.read()
修改为result = pay_url
③:跳过“解析请求返回结果并做验签(__parse_response)”
·文件位置:.env/Lib/site-packages/alipay/aop/api/DefaultAlipayClient.py
·代码位置: """执行接口请求"""def execute里————最后的return位置
·操作————修改:
将return self.__parse_response(response)
修改为return response
④:跳过结果解析
·文件位置:主文件
·代码位置:最后执行API调用的位置。
if not response_content:
print("failed execute")
之后。
·操作————删除:
else:
response = AlipayTradePagePayResponse()
# 解析响应结果
response.parse_response_content(response_content)
if response.is_success():
# 如果业务成功,可以通过response属性获取需要的值
print("get response trade_no:" + response.trade_no)
else:
# 如果业务失败,可以从错误码中可以得知错误情况,具体错误码信息可以查看接口文档
print(response.code + "," + response.msg + "," + response.sub_code + "," + response.sub_msg)
真的好坑啊支付宝,我只是想做一个用户,却要来研究你那问题一大堆连你们自己官网示例都不能直接运行跑通的SDK,既然写了pyrhon的代码请你们尊重自己的劳动成果,积极维护!