当前位置:   article > 正文

使用python3的Django框架开发微信支付分整体流程和AEAD_AES_256_GCM回调验签、HMAC-SHA256加密以及微信文档的各种坑_aes gcm hmacsha256

aes gcm hmacsha256

由于微信支付分开发没有python3版本的示例,整个网络找AEAD_AES_256_GCM验签python版本都找不到几个案例,所以有必要根据流程记录一下本次开发遇到的一些坑

1.首先是创建免押租借订单,因为对json相对熟悉一点,将所有参数写成json格式,然后封装成方法,供视图调用

  1. def createrentbill(out_order_no, rent_unit_fee):
  2.     UNIFIED_ORDER_URL = 'https://api.mch.weixin.qq.com/wxv/createrentbill'
  3.     nonce_str = ''.join(random.sample(string.ascii_letters + string.digits, 32))
  4.     order_info = {
  5.         'version': '1.0',   # 版本号
  6.         'appid': WEIXIN_APP_ID,  #  公众账号ID
  7.         'mch_id': WEIXIN_MCHID,  # 商户ID
  8.         'nonce_str': nonce_str,  # 随机字符串
  9.         'sign_type': 'HMAC-SHA256',  
  10.         'out_order_no': out_order_no,  # 传入的商户平台订单号,也就是自己系统生成的订单号
  11.         'service_id': WEIXIN_SERVICE_ID,  # 在接入支付分的时候,微信方会提供这个service_id,
  12.         'goods_name': '雨伞一个'# 商品名称
  13.         'deposit_amount': 5000,  # 押金总额,单位/分
  14.         'rent_unit': 'FEN_1_HOUR',  # 计费方式: 小时/分
  15.         'rent_unit_fee': rent_unit_fee  #传入的单价  
  16.     }
  17.     order_info['sign'] = hmac_sha256(order_info)
  18.     data = xmltodict.unparse({'xml': order_info}, pretty=True, full_document=False).encode('utf-8')
  19.     headers = {'Content-Type': 'application/xml'}
  20.     weixinapiclient_cert = WX_CERT_PATH  # 需要传入证书和密钥
  21.     weixinapiclient_key = WX_KEY_PATH  # 需要传入证书和密钥
  22.     response = requests.post(UNIFIED_ORDER_URL, data=data, headers=headers,
  23.                              cert=(weixinapiclient_cert, weixinapiclient_key), verify=True)
  24.     msg = response.text
  25.     xmlmsg = xmltodict.parse(msg)  # 将xml转成字典
  26.     if xmlmsg['xml']['return_code'] == 'SUCCESS':
  27.         if xmlmsg['xml']['result_code'] == 'SUCCESS':
  28.             order_id = xmlmsg['xml']['order_id']  # 微信支付服务订单号
  29.             package = xmlmsg['xml']['package']  # 跳转微信侧小程序订单数据
  30.             info = {
  31.                 'mch_id': WEIXIN_MCHID,
  32.                 'package': package,
  33.                 'timestamp': str(int(time.time())),  # 时间戳为string类型
  34.                 'nonce_str': nonce_str,
  35.                 'sign_type': 'HMAC-SHA256',
  36.             }
  37.             info['sign'] = hmac_sha256(info)
  38.             return info,order_id   # 返回给微信小程序端的数据,根据自己的业务需要返回
  39.         return False, False
  40.     return False, False

上面需要使用到HMAC-SHA256做签名,参数名还要按ASCII码从小到大排序(字典序):

  1. def hmac_sha256(param):
  2.     '''使用hmac-sha256生成签名'''
  3.     appkey = settings.WEIXIN_APP_KEY
  4.     stringA = ''
  5.     ks = sorted(param.keys())
  6.     # 参数排序
  7.     for k in ks:
  8.         stringA += (str(k) + '=' + str(param[k]) + '&')
  9.     # 拼接商户KEY
  10.     stringSignTemp = stringA + "key=" + appkey
  11.     signature = hmac.new(bytes(appkey, encoding='utf-8'), bytes(stringSignTemp, encoding='utf-8'),
  12.                          digestmod=hashlib.sha256).digest()
  13.     # 二进制转为HEX
  14.     HEX = signature.hex()
  15.     # 转化为大写
  16.     sign = HEX.upper()
  17.     return sign

由于我在定义参数的时候使用的是字典,而发送给微信的时候需要转成xml,所以又写了个方法,后面还会用到:

  1. def trans_dict_to_xml(data_dict):
  2.     '''定义字典转XML的函数'''
  3.     data_xml = []
  4.     for k in sorted(data_dict.keys()):  # 遍历字典排序后的key
  5.         v = data_dict.get(k)  # 取出字典中key对应的value
  6.         if k == 'detail' and not v.startswith('<![CDATA['):  # 添加XML标记
  7.             v = '<![CDATA[{}]]>'.format(v)
  8.         data_xml.append('<{key}>{value}</{key}>'.format(key=k, value=v))
  9.     return '<xml>{}</xml>'.format(''.join(data_xml))  # 返回XML

使用 requests.post给微信端发送消息,post请求:

  1. response = requests.post(UNIFIED_ORDER_URL, data=data, headers=headers,
  2.                              cert=(weixinapiclient_cert, weixinapiclient_key), verify=True)

第一步创建免押租借订单就算是完成了,有些固定值需要自己配置,比如appid,mchid之类的

这里碰到的第一个坑是,租金规则的示例值:

根据文档显示示例值为DAY_FEN,我的需求就应该是HOUR_FEN,然后微信那边一直给返回传参错误,后来根据右边的例如的例子,改为FEN_1_HOUR才对,然后当场给微信那边反应,第二天发现他们已经改回来了,用的还是我在代码里用的,他们这个文档写得确实是乱

2. 创建订单成功后,还要接收微信方发送过来的回调,回调地址必须是HTTPS的,在商户接入支付分的时候,微信的工作人员会让接入的商户填写一个表格,里面就包括要填的回调地址,注意:这个地址在这个时候开始就算是已经被使用了,其他的接口不能再使用这个路由地址

  1. class AuthConfirmView(APIView):
  2.     '''确认订单回调通知'''
  3.     def post(self, request):
  4.         key = WEIXIN_APIV3_KEY   # apiv3的key在商户后台里设置,一定要是32位的,作为验签的key使用
  5.         msg = request.body.decode('utf-8')
  6.         dic = trans_xml_to_dict(msg)   # 微信方发过来的是xml格式的字符串,注意是字符串,所以又写了个方法给它转成字典
  7.         associated_data = dic['event_associated_data']   # 验签需要的数据
  8.         ciphertext = dic['event_ciphertext']  # 验签需要的数据
  9.         nonce = dic['event_nonce']  # 验签需要的数据
  10.         ciphert = decrypt(ciphertext, key, nonce, associated_data)  # 解密验签 ,解出来的还是xml格式的
  11.         res = trans_xml_to_dict(ciphert)    # 因为不会从xml中取值,所有又转成字典
  12.         finish_ticket = res['finish_ticket'# 完结凭证,一定要存起来,后面创建完结订单的时候需要用到
  13.         out_order_no = res['out_order_no'# 商户自己的订单号,微信给返回回来
  14.         try:
  15.             order = Order.objects.get(order_id=out_order_no)  #  自己查库
  16.         except:
  17.             return Response({'message': '订单不存在'})
  18.         order.finish_ticket = finish_ticket  # 将完结凭证村库
  19.         order.save()
  20.         return Response(trans_dict_to_xml({'return_code': '<![CDATA[SUCCESS]]>', 'return_msg': '<![CDATA[OK]]>'}))

将xml转成字典的方法:

  1. def trans_xml_to_dict(xml_data):
  2.     #将xml字符串转换为字典
  3.     soup = BeautifulSoup(xml_data, features='xml')
  4.     xml = soup.find('xml')  # 解析XML
  5.     if not xml:
  6.         return {}
  7.     data_dict = dict([(item.name, item.text) for item in xml.find_all()])
  8.     return data_dict

使用AEAD_AES_256_GCM解密验签,这个方法在网上好难找,后来是微信那边给提供的

  1. def decrypt(ciphertext, key, nonce, associated_data):
  2.     '''使用AEAD_AES_256_GCM解密'''
  3.     key_bytes = str.encode(key)
  4.     aesgcm = AESGCM(key_bytes)
  5.     nonce_bytes = str.encode(nonce)
  6.     ad_bytes = str.encode(associated_data)
  7.     data = base64.b64decode(ciphertext)
  8.     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.先总结完结免押租借订单吧,因为查询,修改,撤销之类的不用回调,而创建和完结需要用到回调

  1. # 完结免压支付订单,还是一堆熟悉的数据
  2. def finishrentbill(out_order_no, total_amount, rent_fee, finish_ticket):
  3.     UNIFIED_ORDER_URL = 'https://api.mch.weixin.qq.com/wxv/finishrentbill'
  4.     nonce_str = ''.join(random.sample(string.ascii_letters + string.digits, 32))
  5.     realtime = datetime.datetime.now().strftime('%Y%m%d%H%M%S')
  6.     order_info = {
  7.         'version': '1.0',
  8.         'appid': WEIXIN_APP_ID,
  9.         'mch_id': WEIXIN_MCHID,
  10.         'nonce_str': nonce_str,
  11.         'sign_type': 'HMAC-SHA256',
  12.         'out_order_no': out_order_no,
  13.         'service_id': WEIXIN_SERVICE_ID,
  14.         'returned': 'TRUE'# 注意这里,这个是string类型,不是布尔类型,如果这里填了TRUE,下面的归还时间就一定要
  15.         'real_end_time': realtime,  # 归还时间,选的是当前时间,也必须是string类型
  16.         'total_amount': int(total_amount),  # 总金额,一定要是int类型的,否则还是会报错
  17.         'rent_fee': int(rent_fee),  # 租金费用,也一定要是int类型的,如果没有赔偿金,其实总金额就是等于租金费用
  18.         'finish_ticket': finish_ticket,  # 从数据库中取出来,然后传过来,一个订单一个完结凭证
  19.     }
  20.     order_info['sign'] = hmac_sha256(order_info)  # 签名,并且加入到参数里面去,签名方法和前面的一样
  21.     data = xmltodict.unparse({'xml': order_info}, pretty=True, full_document=False).encode('utf-8')
  22.     headers = {'Content-Type': 'application/xml'}
  23.     weixinapiclient_cert = WX_CERT_PATH
  24.     weixinapiclient_key = WX_KEY_PATH
  25.     response = requests.post(UNIFIED_ORDER_URL, data=data, headers=headers,
  26.                              cert=(weixinapiclient_cert, weixinapiclient_key), verify=True)
  27.     msg = response.text
  28.     xmlmsg = xmltodict.parse(msg)
  29.     if xmlmsg['xml']['return_code'] == 'SUCCESS':
  30.         if xmlmsg['xml']['result_code'] == 'SUCCESS':
  31.             order_id = xmlmsg['xml']['order_id']  # 微信支付服务订单号
  32.             data = {
  33.                 'order_id': order_id,  # 其实需要什么数据,自己可以看微信文档,然后按照自己的业务逻辑去取
  34.             }
  35.             return data
  36.         return False
  37.     return False

 

注意看右边的描述,如果单纯看是否必填就很容易忽略掉实际归还时间这个参数

这第三步完结免押租借订单就算是大功告成了,这里也遇到一个坑,手动捂脸

微信支付分文档里面,这个分账标识是必须传的,当时我们没有指定服务商分账,我就填了False,然后这个错就找了好久,后面鬼使神差的把这个分账标识去掉,它居然可以跑通了。。。4.既然完结订单也已经写好了,接下来就是处理微信发过来的回调了

  1. class PaySuccessView(APIView):
  2.     '''支付成功回调通知'''
  3.     def post(self, request):
  4.         key = WEIXIN_APIV3_KEY
  5.         msg = request.body.decode('utf-8')
  6.         dic = trans_xml_to_dict(msg)
  7.         associated_data = dic['event_associated_data']
  8.         ciphertext = dic['event_ciphertext']
  9.         nonce = dic['event_nonce']
  10.         ciphert = decrypt(ciphertext, key, nonce, associated_data)  # 解密验签
  11.         res = trans_xml_to_dict(ciphert)
  12.         out_order_no = res['out_order_no']
  13.         finish_transaction_id = res['finish_transaction_id']
  14.         try:
  15.             order = Order.objects.get(order_id=out_order_no)
  16.         except:
  17.             return Response({'message': '订单不存在'})
  18.         order.status = True  # 修改订单状态为已支付
  19.         order.finish_transaction_id   = finish_transaction_id   # 退款时需要这个字段,所以也存起来
  20.         order.save()
  21.         return Response(trans_dict_to_xml({'return_code': '<![CDATA[SUCCESS]]>', 'return_msg': '<![CDATA[OK]]>'}))

写了上面的三个接口,这次就没有遇到什么坑啦啦啦啦啦

微信支付分明确提到,支付分这边是没有独立的退款接口的,如果实在是需要退款,用的是普通支付的退款接口

微信把这个东西写了问题集锦里面去了,当调用普通退款接口给支付分退款的时候,将finish_transaction_id 替换transaction_id   就可以了

5.查询,撤销,修改这三个接口就不做总结了,按照文档去写,不会遇到什么坑了

使用python3的Django框架开发微信支付分后端业务处理就算是大功告成了,也许是因为微信那边的支付分功能刚开通不久吧,文档上的漏洞还是很多,遇到问题就像微信那边的技术人员寻求帮助就可以了

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小小林熬夜学编程/article/detail/176566
推荐阅读
相关标签
  

闽ICP备14008679号