赞
踩
由于微信支付分开发没有python3版本的示例,整个网络找AEAD_AES_256_GCM验签python版本都找不到几个案例,所以有必要根据流程记录一下本次开发遇到的一些坑
1.首先是创建免押租借订单,因为对json相对熟悉一点,将所有参数写成json格式,然后封装成方法,供视图调用
- def createrentbill(out_order_no, rent_unit_fee):
- UNIFIED_ORDER_URL = 'https://api.mch.weixin.qq.com/wxv/createrentbill'
- nonce_str = ''.join(random.sample(string.ascii_letters + string.digits, 32))
- order_info = {
- 'version': '1.0', # 版本号
- 'appid': WEIXIN_APP_ID, # 公众账号ID
- 'mch_id': WEIXIN_MCHID, # 商户ID
- 'nonce_str': nonce_str, # 随机字符串
- 'sign_type': 'HMAC-SHA256',
- 'out_order_no': out_order_no, # 传入的商户平台订单号,也就是自己系统生成的订单号
- 'service_id': WEIXIN_SERVICE_ID, # 在接入支付分的时候,微信方会提供这个service_id,
- 'goods_name': '雨伞一个', # 商品名称
- 'deposit_amount': 5000, # 押金总额,单位/分
- 'rent_unit': 'FEN_1_HOUR', # 计费方式: 小时/分
- 'rent_unit_fee': rent_unit_fee #传入的单价
- }
- order_info['sign'] = hmac_sha256(order_info)
- data = xmltodict.unparse({'xml': order_info}, pretty=True, full_document=False).encode('utf-8')
- headers = {'Content-Type': 'application/xml'}
- weixinapiclient_cert = WX_CERT_PATH # 需要传入证书和密钥
- weixinapiclient_key = WX_KEY_PATH # 需要传入证书和密钥
- response = requests.post(UNIFIED_ORDER_URL, data=data, headers=headers,
- cert=(weixinapiclient_cert, weixinapiclient_key), verify=True)
- msg = response.text
- xmlmsg = xmltodict.parse(msg) # 将xml转成字典
- if xmlmsg['xml']['return_code'] == 'SUCCESS':
- if xmlmsg['xml']['result_code'] == 'SUCCESS':
- order_id = xmlmsg['xml']['order_id'] # 微信支付服务订单号
- package = xmlmsg['xml']['package'] # 跳转微信侧小程序订单数据
- info = {
- 'mch_id': WEIXIN_MCHID,
- 'package': package,
- 'timestamp': str(int(time.time())), # 时间戳为string类型
- 'nonce_str': nonce_str,
- 'sign_type': 'HMAC-SHA256',
- }
- info['sign'] = hmac_sha256(info)
- return info,order_id # 返回给微信小程序端的数据,根据自己的业务需要返回
- return False, False
- return False, False
上面需要使用到HMAC-SHA256做签名,参数名还要按ASCII码从小到大排序(字典序):
- def hmac_sha256(param):
- '''使用hmac-sha256生成签名'''
- appkey = settings.WEIXIN_APP_KEY
- stringA = ''
- ks = sorted(param.keys())
- # 参数排序
- for k in ks:
- stringA += (str(k) + '=' + str(param[k]) + '&')
- # 拼接商户KEY
- stringSignTemp = stringA + "key=" + appkey
- signature = hmac.new(bytes(appkey, encoding='utf-8'), bytes(stringSignTemp, encoding='utf-8'),
- digestmod=hashlib.sha256).digest()
- # 二进制转为HEX
- HEX = signature.hex()
- # 转化为大写
- sign = HEX.upper()
- return sign
由于我在定义参数的时候使用的是字典,而发送给微信的时候需要转成xml,所以又写了个方法,后面还会用到:
- def trans_dict_to_xml(data_dict):
- '''定义字典转XML的函数'''
- data_xml = []
- for k in sorted(data_dict.keys()): # 遍历字典排序后的key
- v = data_dict.get(k) # 取出字典中key对应的value
- if k == 'detail' and not v.startswith('<![CDATA['): # 添加XML标记
- v = '<![CDATA[{}]]>'.format(v)
- data_xml.append('<{key}>{value}</{key}>'.format(key=k, value=v))
- return '<xml>{}</xml>'.format(''.join(data_xml)) # 返回XML
使用 requests.post给微信端发送消息,post请求:
- response = requests.post(UNIFIED_ORDER_URL, data=data, headers=headers,
- cert=(weixinapiclient_cert, weixinapiclient_key), verify=True)
第一步创建免押租借订单就算是完成了,有些固定值需要自己配置,比如appid,mchid之类的
这里碰到的第一个坑是,租金规则的示例值:
根据文档显示示例值为DAY_FEN,我的需求就应该是HOUR_FEN,然后微信那边一直给返回传参错误,后来根据右边的例如的例子,改为FEN_1_HOUR才对,然后当场给微信那边反应,第二天发现他们已经改回来了,用的还是我在代码里用的,他们这个文档写得确实是乱
2. 创建订单成功后,还要接收微信方发送过来的回调,回调地址必须是HTTPS的,在商户接入支付分的时候,微信的工作人员会让接入的商户填写一个表格,里面就包括要填的回调地址,注意:这个地址在这个时候开始就算是已经被使用了,其他的接口不能再使用这个路由地址
- class AuthConfirmView(APIView):
- '''确认订单回调通知'''
-
- def post(self, request):
- key = WEIXIN_APIV3_KEY # apiv3的key在商户后台里设置,一定要是32位的,作为验签的key使用
- msg = request.body.decode('utf-8')
- dic = trans_xml_to_dict(msg) # 微信方发过来的是xml格式的字符串,注意是字符串,所以又写了个方法给它转成字典
- associated_data = dic['event_associated_data'] # 验签需要的数据
- ciphertext = dic['event_ciphertext'] # 验签需要的数据
- nonce = dic['event_nonce'] # 验签需要的数据
- ciphert = decrypt(ciphertext, key, nonce, associated_data) # 解密验签 ,解出来的还是xml格式的
- res = trans_xml_to_dict(ciphert) # 因为不会从xml中取值,所有又转成字典
- finish_ticket = res['finish_ticket'] # 完结凭证,一定要存起来,后面创建完结订单的时候需要用到
- out_order_no = res['out_order_no'] # 商户自己的订单号,微信给返回回来
- try:
- order = Order.objects.get(order_id=out_order_no) # 自己查库
- except:
- return Response({'message': '订单不存在'})
- order.finish_ticket = finish_ticket # 将完结凭证村库
- order.save()
- return Response(trans_dict_to_xml({'return_code': '<![CDATA[SUCCESS]]>', 'return_msg': '<![CDATA[OK]]>'}))
将xml转成字典的方法:
- def trans_xml_to_dict(xml_data):
- #将xml字符串转换为字典
- soup = BeautifulSoup(xml_data, features='xml')
- xml = soup.find('xml') # 解析XML
- if not xml:
- return {}
- data_dict = dict([(item.name, item.text) for item in xml.find_all()])
- return data_dict
使用AEAD_AES_256_GCM解密验签,这个方法在网上好难找,后来是微信那边给提供的
- def decrypt(ciphertext, key, nonce, associated_data):
- '''使用AEAD_AES_256_GCM解密'''
-
- key_bytes = str.encode(key)
- aesgcm = AESGCM(key_bytes)
- nonce_bytes = str.encode(nonce)
- ad_bytes = str.encode(associated_data)
- data = base64.b64decode(ciphertext)
- return aesgcm.decrypt(nonce_bytes, data, ad_bytes)
如果整个过程都是成功的,那么返回给微信方的数据为:
return Response(trans_dict_to_xml({'return_code': '<![CDATA[SUCCESS]]>', 'return_msg': '<![CDATA[OK]]>'}))
这第二步的确认订单成功回调通知的处理就算完成了,这里又遇到的一个坑是:
也就是说,开发文档中写着,微信方那边返回来的数据时json格式的,后来怎么试都不对,然后鬼使神差就试了xml格式,居然对了。又跟微信那边的技术人员反映,后来他们又改回来了:
多加了两句话。。。
3.先总结完结免押租借订单吧,因为查询,修改,撤销之类的不用回调,而创建和完结需要用到回调
- # 完结免压支付订单,还是一堆熟悉的数据
- def finishrentbill(out_order_no, total_amount, rent_fee, finish_ticket):
- UNIFIED_ORDER_URL = 'https://api.mch.weixin.qq.com/wxv/finishrentbill'
- nonce_str = ''.join(random.sample(string.ascii_letters + string.digits, 32))
- realtime = datetime.datetime.now().strftime('%Y%m%d%H%M%S')
- order_info = {
- 'version': '1.0',
- 'appid': WEIXIN_APP_ID,
- 'mch_id': WEIXIN_MCHID,
- 'nonce_str': nonce_str,
- 'sign_type': 'HMAC-SHA256',
- 'out_order_no': out_order_no,
- 'service_id': WEIXIN_SERVICE_ID,
- 'returned': 'TRUE', # 注意这里,这个是string类型,不是布尔类型,如果这里填了TRUE,下面的归还时间就一定要
- 'real_end_time': realtime, # 归还时间,选的是当前时间,也必须是string类型
- 'total_amount': int(total_amount), # 总金额,一定要是int类型的,否则还是会报错
- 'rent_fee': int(rent_fee), # 租金费用,也一定要是int类型的,如果没有赔偿金,其实总金额就是等于租金费用
- 'finish_ticket': finish_ticket, # 从数据库中取出来,然后传过来,一个订单一个完结凭证
- }
- order_info['sign'] = hmac_sha256(order_info) # 签名,并且加入到参数里面去,签名方法和前面的一样
- data = xmltodict.unparse({'xml': order_info}, pretty=True, full_document=False).encode('utf-8')
- headers = {'Content-Type': 'application/xml'}
- weixinapiclient_cert = WX_CERT_PATH
- weixinapiclient_key = WX_KEY_PATH
- response = requests.post(UNIFIED_ORDER_URL, data=data, headers=headers,
- cert=(weixinapiclient_cert, weixinapiclient_key), verify=True)
- msg = response.text
- xmlmsg = xmltodict.parse(msg)
- if xmlmsg['xml']['return_code'] == 'SUCCESS':
- if xmlmsg['xml']['result_code'] == 'SUCCESS':
- order_id = xmlmsg['xml']['order_id'] # 微信支付服务订单号
- data = {
- 'order_id': order_id, # 其实需要什么数据,自己可以看微信文档,然后按照自己的业务逻辑去取
- }
- return data
- return False
- return False
注意看右边的描述,如果单纯看是否必填就很容易忽略掉实际归还时间这个参数
这第三步完结免押租借订单就算是大功告成了,这里也遇到一个坑,手动捂脸
微信支付分文档里面,这个分账标识是必须传的,当时我们没有指定服务商分账,我就填了False,然后这个错就找了好久,后面鬼使神差的把这个分账标识去掉,它居然可以跑通了。。。4.既然完结订单也已经写好了,接下来就是处理微信发过来的回调了
- class PaySuccessView(APIView):
- '''支付成功回调通知'''
-
- def post(self, request):
- key = WEIXIN_APIV3_KEY
- msg = request.body.decode('utf-8')
- dic = trans_xml_to_dict(msg)
- associated_data = dic['event_associated_data']
- ciphertext = dic['event_ciphertext']
- nonce = dic['event_nonce']
- ciphert = decrypt(ciphertext, key, nonce, associated_data) # 解密验签
- res = trans_xml_to_dict(ciphert)
- out_order_no = res['out_order_no']
- finish_transaction_id = res['finish_transaction_id']
- try:
- order = Order.objects.get(order_id=out_order_no)
- except:
- return Response({'message': '订单不存在'})
- order.status = True # 修改订单状态为已支付
- order.finish_transaction_id = finish_transaction_id # 退款时需要这个字段,所以也存起来
- order.save()
- return Response(trans_dict_to_xml({'return_code': '<![CDATA[SUCCESS]]>', 'return_msg': '<![CDATA[OK]]>'}))
写了上面的三个接口,这次就没有遇到什么坑啦啦啦啦啦
微信支付分明确提到,支付分这边是没有独立的退款接口的,如果实在是需要退款,用的是普通支付的退款接口
微信把这个东西写了问题集锦里面去了,当调用普通退款接口给支付分退款的时候,将finish_transaction_id 替换transaction_id 就可以了
5.查询,撤销,修改这三个接口就不做总结了,按照文档去写,不会遇到什么坑了
使用python3的Django框架开发微信支付分后端业务处理就算是大功告成了,也许是因为微信那边的支付分功能刚开通不久吧,文档上的漏洞还是很多,遇到问题就像微信那边的技术人员寻求帮助就可以了
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。