本篇主要讲解pyhon服务端的接入流程,如需了解android的接入流程可以看我的另一篇《 手把手教你接入微信app支付android篇》

由于服务端需要向外提供web api访问及获取微信的异步通知服务,所以这里采用python+flask的方式来创建服务端的web api服务

阅读本文需要有pythonflask的基础哦,不然阅读起来会比较费劲哦

首先先安装我们需要的库,这里使用pip安装

pip install flask,requests,bs4
  • flask web框架核心
  • requests 网络请求
  • bs4 用于解析微信的xml数据

先来实现一个flask最小的web服务

# pay.py
from flask import Flask
app = Flask(__name__)

'''
获取微信支付订单
该url的HTTP动词采用POST方式
'''
@app.route('/get_weixin_pay/', methods=["POST"])
def weixin_pay():
    return "success"

if __name__ == '__main__':
    app.run(host='127.0.0.1', port=5000, threaded=True)

这样我们就实现了一个最小的flask web服务,是不是很简单呢?

我们首先来获取微信的支付订单,该订单是提供给app支付所使用的,我们主要围绕weixin_pay方法来编写

weixin_pay方法需要接收两个参数,titletotal_amount

title是商品的标题

total_amount是商品的金额

实际上titletotal_amount也可以由服务端生成,这里我们让客户端传递的原因是因为我们的支付金额是不固定的,所以需要客户端进行传递

首先判断传入的参数是否为空,记得导入request用于获取传入的参数

from flask import Flask, request,json

'''
获取微信支付订单
该url的HTTP动词采用POST方式
title 商品标题
total_amount  充值的金额
'''
def weixin_pay():
    title = request.form.get("title")
    total_amount = request.form.get("total_amount")
    '''
    如果传入的参数为空,那我们就给客户端返回一个提示信息
    这个提示信息是json格式的,所以不要忘了导入json
    返回的josn有两个参数
    code  返回的状态码,大家可以可以根据不同的错误返回不同的状态码,我这里就全部返回400
    msg  返回的描述信息
    最后使用json.jsonify()将字典对象序列化为json对象返回给客户端
    '''
    if not title:
        data = {}
        data.update({"code": 400})
        data.update({"msg": "title不能为空"})
        return json.jsonify(data)
    if not total_amount:
        data = {}
        data.update({"code": 400})
        data.update({"msg": "total_amount不能为空"})
        return json.jsonify(data)

    # 这三个参数后面会用到,现在先不用管它
    ppid = "xxxxx"
    mch_id = "xxxxx"
    key = "xxxxxx"

接下来我们看看调用微信支付生成订单需要传入哪些参数

注意:这里并不是全部的参数,只是传入了所有的必填参数,如需实现其他业务逻辑可以查看微信支付的官方api文档传入所需参数

参数 说明
appid 接入微信支付程序的appid
mch_id 商户号
nonce_str 随机字符串
out_trade_no 商户唯一订单号
body 商品标题
total_fee 订单总金额
spbill_create_ip 调用者的ip
notify_url 回调地址
trade_type 支付类型

首先来实现商户唯一订单号的逻辑,这里使用一个小算法实现

这里使用到了time模块,记得导入

import time

'''
 将结果返回给order_no
为了区分其他的支付方式的唯一订单,所以在最前面加入了`wx`字符
'''
order_no = "wx" + str(time.strftime('%Y%m%d%H%M%S', time.localtime(time.time()))) + str(time.time()).replace('.', '')[-7:]

下面我们把这些需要传给微信服务器的参数放入一个字典中

d = {
        "appid": appid,  # appid
        "mch_id": mch_id,  # 商户号
        "nonce_str": order_no,  # 随机字符串,这里我们也使用唯一订单,因为它是不重复的
        "out_trade_no": order_no,  # 商户唯一订单号
        "body": title,  # 商品标题
        "total_fee": total_amount,  # 订单总金额
        "spbill_create_ip": "183.2.168.56",  # 调用ip
        "notify_url": "https://pay.xcstudio.cn/wx_notify_url/",  # 回调地址
        "trade_type": "APP"  # 支付类型
    }

接下来是比较重要的一步,我们需要生成一个sign签名

按照微信支付的规定,我们需要把这些参数按照参数名ASCII码从小到大排序key1=value1&key2=value2…的方式进行排序,然后需要将得到的字符串最后拼接上key,并对获得的字符串进行MD5运算,再将得到的字符串所有字符转换为大写,就得到了sign值,最后将sign加入到需要传入的参数字典d

注意key需要提前设置
key设置路径:微信商户平台(pay.weixin.qq.com)–>账户设置–>API安全–>密钥设置

下面看一下具体的代码

import hashlib

'''
由于使用了md5加密,所以记得导入hashlib
'''

list = sorted(d.items(), key=lambda x: x[0])  # 对字符串排序
    stringA = ""
    for i in list:  # 拼接key1=value参数
        stringA = stringA + str(i[0]) + "=" + str(i[1]) + "&"
    stringA = stringA[0:len(stringA) - 1]
    stringSignTemp = stringA + "&key=" + key
    m = hashlib.md5()
    m.update(stringSignTemp.encode("utf8"))
    str_md5 = m.hexdigest()
    sign = str_md5.upper()
    d.update({"sign": sign})  # 将sign加入到需要传入的参数字典`d`中

微信要求传给微信服务端的参数必须为xml格式,我们这里是字典显然是不行的,所以我们需要将字典转换为微信要求的xml,为此我们编写一个小函数来实现

# dict 转xml
def trans_dict_to_xml(data):
    """
    将 dict 对象转换成微信支付交互所需的 XML 格式数据
    :param data: dict 对象
    :return: xml 格式数据
    """
    xml = []
    for k in sorted(data.keys()):
        v = data.get(k)
        if k == 'detail' and not v.startswith('<![CDATA['):
            v = '<![CDATA[{}]]>'.format(v)
        xml.append('<{key}>{value}</{key}>'.format(key=k, value=v))
    return '<xml>{}</xml>'.format(''.join(xml))

然后我们就可以在代码中使用了,发送给微信请求后微信会返回给我们一个xml的数据。所以这里我们需要借助bs4来解析微信返回给我们的数据,解析完成后我们需要将xml转换为python可操作的字典,然后我们还需要生成一个sign,和上面是一模一样的,最后将生成的数据返回客户端即可

具体的返回数据由于比较多篇幅有限具体描述这里就不贴出来了,我这里根据业务需要获取了我所需要的数据,如果需要更多的返回参数数据可以看微信支付官方文档的返回数据

下面来看下具体的代码

from bs4 import BeautifulSoup

data = trans_dict_to_xml(d)  # 字典转xml
    xml = requests.post("https://api.mch.weixin.qq.com/pay/unifiedorder", data=data.encode("utf8"))
    xml.encoding = "utf8"
    soup = BeautifulSoup(xml.text, features='xml')
    xml = soup.find('xml')
    data = dict([(item.name, item.text) for item in xml.find_all()])  # xml转字典
    data1 = {}  # 用于构建返回给客户端的jsom
    if len(data) != 0:
        if data["return_code"] == "SUCCESS" and data["result_code"] == "SUCCESS":
            data1.update({"code": 200})
            data1.update({"msg": "请求数据成功"})
            data2 = {}
            data2.update({"appid": data["appid"]})  # appid
            data2.update({"mch_id": data["mch_id"]})  # 商户id
            data2.update({"nonce_str": data["nonce_str"]})  # 随机字符
            data2.update({"prepay_id": data["prepay_id"]})  # 请求的支付串
            data2.update({"packageValue": "Sign=WXPay"})  # 扩展参数
            t = time.time()
            data2.update({"timeStamp": str(int(t))})  # 时间戳
            # sign签名
            list = sorted(data2.items(), key=lambda x: x[0])
            stringA = ""
            for i in list:
                stringA = stringA + str(i[0]) + "=" + str(i[1]) + "&"
            stringA = stringA[0:len(stringA) - 1]
            stringSignTemp = stringA + "&key=" + key
            m = hashlib.md5()
            m.update(stringSignTemp.encode("utf8"))
            str_md5 = m.hexdigest()
            sign = str_md5.upper()
            data2.update({"sign": sign})
            data2.update({"order_no": order_no})
            data1["data"] = data2
        else:
            data1.update({"code": 400})
            data1.update({"msg": "可能签名失败或appid无效"})
    else:
        data1.update({"code": 400})
        data1.update({"msg": "请求数据失败"})
    return json.jsonify(data1)

到这里获取微信支付订单就完成了,接下来我们来看看如何获取微信支付的异步通知结果

还记得我们在上面的发送给微信的请求参数d中传入了一个notify_url参数吗?这就是微信的异步通知回调地址了,当我们在微信完成支付后微信会以POST的方法向该地址回传支付结果及用户信息,我们通过这些信息就可以得到支付结果了,拿到结果后我们可以把结果保存到数据库或者其他的地方,之后就可以方便的查询了,微信会返回很多参数给我们,由于我的业务需求,我只获取了一部分,需要注意的是返回的参数也需要进行sign的参数效验,下面是具体的代码

# 微信支付结果异步回调
@app.route("/wx_notify_url/", methods=["POST"])
def wx_notify_url():
    key = "xxxxxx"
    xmls = "<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>"
    xml = request.data
    soup = BeautifulSoup(xml, features='xml')
    xml = soup.find('xml')
    if not xml:
        return ""
    else:
        # 将 XML 数据转化为 Dict
        data = dict([(item.name, item.text) for item in xml.find_all()])
        # 当获取到的数据返回SUCCESS则表示支付成功,支付成功才处理
        if data["return_code"] == "SUCCESS":
            # 接下来继续做sign计算
            list = sorted(data.items(), key=lambda x: x[0])
            stringA = ""
            for i in list:
                stringA = stringA + str(i[0]) + "=" + str(i[1]) + "&"
            stringA = stringA[0:len(stringA) - 1]
            stringSignTemp = stringA + "&key=" + key
            m = hashlib.md5()
            m.update(stringSignTemp.encode("utf8"))
            str_md5 = m.hexdigest()
            sign = str_md5.upper()
            # 将获取到的签名和我们自己计算的签名做比较,如果相同则效验成功
            if data["sign"] == sign:
                notify_time = data["time_end"]  # 创建时间
                buyer_logon_id = data["openid"]  # 买家标识
                total_amount = data["total_fee"]  # 交易金额
                trade_status = data["result_code"]  # 交易状态
                trade_no = data["transaction_id"]  # 微信交易流水号
                out_trade_no = data["out_trade_no"]  # 商户订单号
                # 后面就可以做具体自己的业务操作了,如保存数据库等

到这里微信支付服务端的接入流程也完成了,整体流程还是比较简单的,只是微信的官方文档写的比较坑,新手很容易踩坑,大家可以结合我的文章去看官方文档就没有障碍了

如果文章对你有帮助,可以扫描下面的打赏二维码打赏一杯咖啡哦

这次的教程就到这里了,我们下一篇博文再见

暂无留言,赶快评论吧

欢迎留言