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

由于微信支付分开发没有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(''.format(v)
        data_xml.append('<{key}>{value}'.format(key=k, value=v))
    return '{}'.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之类的

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

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

根据文档显示示例值为DAY_FEN,我的需求就应该是HOUR_FEN,然后微信那边一直给返回传参错误,后来根据右边的例如的例子,改为FEN_1_HOUR才对,然后当场给微信那边反应,第二天发现他们已经改回来了,用的还是我在代码里用的,他们这个文档写得确实是乱使用python3的Django框架开发微信支付分整体流程和AEAD_AES_256_GCM回调验签、HMAC-SHA256加密以及微信文档的各种坑_第2张图片

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': '', 'return_msg': ''}))

将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': '', 'return_msg': ''}))

这第二步的确认订单成功回调通知的处理就算完成了,这里又遇到的一个坑是:

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

也就是说,开发文档中写着,微信方那边返回来的数据时json格式的,后来怎么试都不对,然后鬼使神差就试了xml格式,居然对了。又跟微信那边的技术人员反映,后来他们又改回来了:使用python3的Django框架开发微信支付分整体流程和AEAD_AES_256_GCM回调验签、HMAC-SHA256加密以及微信文档的各种坑_第4张图片

多加了两句话。。。

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

 

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

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

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

微信支付分文档里面,这个分账标识是必须传的,当时我们没有指定服务商分账,我就填了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': '', 'return_msg': ''}))

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

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

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

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

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

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

 

你可能感兴趣的:(第三方开发)