解决alipay-sdk-python不支持Decimal数据类型

2020年10月10日 分类:原创 作者:清心涟漪

预计所需阅读时间:12分钟

最近在用Flask框架对商城进行二次开发,其中要引入支付宝的Python的SDK包。然后从结算页面跳转到支付宝的收款页里,出现了下下问题:

Traceback (most recent call last)
File "D:\Programing\Anaconda3\envs\flask_env\lib\site-packages\flask\app.py", line 2464, in call
return self.wsgi_app(environ, start_response)
File "D:\Programing\Anaconda3\envs\flask_env\lib\site-packages\flask\app.py", line 2450, in wsgi_app
response = self.handle_exception(e)
File "D:\Programing\Anaconda3\envs\flask_env\lib\site-packages\flask\app.py", line 1867, in handle_exception
reraise(exc_type, exc_value, tb)
File "D:\Programing\Anaconda3\envs\flask_env\lib\site-packages\flask_compat.py", line 39, in reraise
raise value
File "D:\Programing\Anaconda3\envs\flask_env\lib\site-packages\flask\app.py", line 2447, in wsgi_app
response = self.full_dispatch_request()
File "D:\Programing\Anaconda3\envs\flask_env\lib\site-packages\flask\app.py", line 1952, in full_dispatch_request
rv = self.handle_user_exception(e)
File "D:\Programing\Anaconda3\envs\flask_env\lib\site-packages\flask\app.py", line 1821, in handle_user_exception
reraise(exc_type, exc_value, tb)
File "D:\Programing\Anaconda3\envs\flask_env\lib\site-packages\flask_compat.py", line 39, in reraise
raise value
File "D:\Programing\Anaconda3\envs\flask_env\lib\site-packages\flask\app.py", line 1950, in full_dispatch_request
rv = self.dispatch_request()
File "D:\Programing\Anaconda3\envs\flask_env\lib\site-packages\flask_debugtoolbar_init_.py", line 125, in dispatch_request
return view_func(**req.view_args)
File "D:\Projects\python_projects\flask_mall\xp_mall\member\buy.py", line 85, in pay_order
res = pay.pay_order(order)
File "D:\Projects\python_projects\flask_mall\pay\alipay\create_pay.py", line 61, in pay_order
_response = self.client.page_execute(_request, http_method="GET")
File "D:\Programing\Anaconda3\envs\flask_env\lib\site-packages\alipay\aop\api\DefaultAlipayClient.py", line 234, in page_execute
query_string, params = self.__prepare_request(request)
File "D:\Programing\Anaconda3\envs\flask_env\lib\site-packages\alipay\aop\api\DefaultAlipayClient.py", line 89, in __prepare_request
common_params, params = self.__prepare_request_params(request)
File "D:\Programing\Anaconda3\envs\flask_env\lib\site-packages\alipay\aop\api\DefaultAlipayClient.py", line 109, in _prepare_request_params
params = request.get_params()
File "D:\Programing\Anaconda3\envs\flask_env\lib\site-packages\alipay\aop\api\request\AlipayTradePagePayRequest.py", line 122, in get_params
params[P_BIZ_CONTENT] = json.dumps(obj=self.biz_model.to_alipay_dict(), ensure_ascii=False, sort_keys=True, separators=(',', ':'))
File "D:\Programing\Anaconda3\envs\flask_env\lib\json_init.py", line 238, in dumps
**kw).encode(obj)
File "D:\Programing\Anaconda3\envs\flask_env\lib\json\encoder.py", line 199, in encode
chunks = self.iterencode(o, _one_shot=True)
File "D:\Programing\Anaconda3\envs\flask_env\lib\json\encoder.py", line 257, in iterencode
return _iterencode(o, 0)
File "D:\Programing\Anaconda3\envs\flask_env\lib\json\encoder.py", line 179, in default
raise TypeError(f'Object of type {o.class.name} '
TypeError: Object of type Decimal is not JSON serializable

错误核心是TypeError: Object of type Decimal is not JSON serializable

经过分析,这里的语句是关键,涉及了json这个包调用的过程:

File "D:\Programing\Anaconda3\envs\flask_env\lib\site-packages\alipay\aop\api\request\AlipayTradePagePayRequest.py", line 122, in get_params
params[P_BIZ_CONTENT] = json.dumps(obj=self.biz_model.to_alipay_dict(), ensure_ascii=False, sort_keys=True, separators=(',', ':'))

说明问题代码行出现丰支付包的SDK包,调用网页支付的PY文件里,json.dumps()函数无法将Decimal的数据类型转为JSON对象。

因为商城中关于价格、金额的字段在Mysql数据中是用Decimal数据类型保存,这是为了保证金额计算的准确,而不会因为使用浮点数计算小数点后面的数字不准确的问题,例如0.1+0.2+0.3可能会等于0.59999999999999这样的结果。

而标准库的json包是不支持将Decimal类型转成JSON对象,而网上很多教程都是将这个字段先转成浮点数或字段串,再进行JSON化,但是这样做要写先多行,加入另外的逻辑。如果要对支付宝的SDK包进行改去,很有可能将其原来的逻辑破坏,导致无法调用支付宝的支付接口。经过方案的比较,在Stackoverflow里找到简明的方案,是使用了simplejson这个包来代替json包,我也看了这个包的文档使用Decimal类型数据的例子,确保从数据库传出的Decimal类型的订单金额,到支付宝的服务器能识别的JSON对象,而且金额是一致的。

当时,只是调用网页支付功能,那就只修改AlipayTradePagePayRequest.py这个文件即可,修改方法如下:

# 原来的注释注释掉,import json
import simplejson as json
# 在使用json.dumps函数里加入use_decimal=True这个参数
    def get_params(self):
        params = dict()
        params[P_METHOD] = 'alipay.trade.page.pay'
        params[P_VERSION] = self.version
        if self.biz_model:
            params[P_BIZ_CONTENT] = json.dumps(obj=self.biz_model.to_alipay_dict(), use_decimal=True, ensure_ascii=False, sort_keys=True, separators=(',', ':'))
        if self.biz_content:
            if hasattr(self.biz_content, 'to_alipay_dict'):
                params['biz_content'] = json.dumps(obj=self.biz_content.to_alipay_dict(), use_decimal=True, ensure_ascii=False, sort_keys=True, separators=(',', ':'))
            else:
                params['biz_content'] = self.biz_content
        if self.terminal_type:
            params['terminal_type'] = self.terminal_type
        if self.terminal_info:
            params['terminal_info'] = self.terminal_info
        if self.prod_code:
            params['prod_code'] = self.prod_code
        if self.notify_url:
            params['notify_url'] = self.notify_url
        if self.return_url:
            params['return_url'] = self.return_url
        if self.udf_params:
            params.update(self.udf_params)
        return params

最主要是在在使用json.dumps函数里加入use_decimal=True这个参数。

这样就通过一个简明的方法去修改支付宝官方的SDK包。

然后,整个支付宝Python的SDK包有8000多个文件都使用了json,而主要是在request这个目录的文件都使用的json.dumps()这个函数。通过IDE的批量替换功能,并启用正则表达式匹配,解决了几千个文件的修改。

这是我从官方Github项目网址Fork一份过来,完成修改后的项目地址,让支付宝的Python的SDK包支付Decimal数据类型:

修改后Github项目地址。

这个不支付Decimal类型的问题Bug,已经作为issues提交到官方项目的地址(点击查看),不知道会不会修复。

如果出现同样的问题,使用了Decimal这个数据类型,那么可以先用pip安装官方的SDK包,然后下载我这个项目的文件,找到所在安装路经将文件全部覆盖,即可正常调用支付宝的支付接口。

继续阅读