解决alipay-sdk-python不支持Decimal数据类型
预计所需阅读时间: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数据类型:
这个不支付Decimal类型的问题Bug,已经作为issues提交到官方项目的地址(点击查看),不知道会不会修复。
如果出现同样的问题,使用了Decimal这个数据类型,那么可以先用pip安装官方的SDK包,然后下载我这个项目的文件,找到所在安装路经将文件全部覆盖,即可正常调用支付宝的支付接口。