微信小程序支付功能详细分析(一、预支付)
发表时间:2020-10-19
发布人:葵宇科技
浏览次数:93
微信小程序支付功能详细分析
小记:最近在做微信小程序的支付,中间也遇到了很多问题,总结一下,给大家避个坑,也给自己记录一下后面可以作为参考
概要
本文中只写执行逻辑,跟一些算法,不包含具体代码内容,目前开发采用的是微信小程序、nginx+uwsgi+python+flask
微信小程序支付
官方文档:https://developers.weixin.qq.com/miniprogram/dev/framework/
整体逻辑
首先先看下整体逻辑
整体逻辑
https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=7_4&index=3
这个是官方给出的 小程序支付的流程图,我们来分布讲解一下
首先先讲解下逻辑,用户点击下单,并不是直接小程序发送给微信后台服务器,而是小程序把数据传输给我们自己的服务器,通过我们自己的服务器跟微信后台服务器交互后获取支付数据传输给微信小程序客户端,再由微信小程序通过统一的支付函数,直接发送给微信后台服务器,支付成功,成功后微信后台服务器会给我们的服务器和微信小程序发支付成功信息。
为了方便理解,我们把我微信小程序简称(客户端),我们自己的服务器简称(A服务器),微信后台的服务器简称(B服务器)
可以看到我们在整个支付流程中, 向B服务器发送过两次的支付请求,一次是A服务器发出,一次是微信小程序发出。 第一次称为预支付,第二次才是下单
预支付
我们先讲一下预支付
预支付就是A服务器使用统一下单命令向B服务器发送数据并返回一个json数据,这个json数据到时候是要发送给客户端,完成第二次下单操作的参数
预支付步骤:
关于 统一下单 需要几个参数我们需要先获取一下:
1、openid (用户标识)
2、nonce_str (随机字符串)
3、out_trade_no (商户订单号)
4、sign (签名)
1、openid(用户标识)
首先客户端提起支付请求,我们需要先把微信客户端的用户登陆凭证获取到,然后发送给A服务器
//微信小程序 js代码
//微信登陆,获取代码,发送A服务器 并获取结果(即openid)
wx.login({
success: function (res) { //登陆成功,获取用户登陆凭证 res.code
if (res.code) {
wx.request({ //发送数据给A服务器
url: 'https://你自己的服务器',
data: {
code: res.code, //用户登陆凭证,这个后面用户换取openid的东西 主要作用参考图 2
ordercode: ordercode, //支付金额
},
method: 'post',
success: function (res) {
//返回的数据我们待会在做详细讲解///
}
})
} else {
console.log('获取用户登录态失败!' + res.errMsg)
}
}
});
图2
wx.login
官方文档:https://developers.weixin.qq.com/miniprogram/dev/api/open-api/login/wx.login.html
接下来的部分就全部是 A B两个服务器之间的数据互换了
把A服务器刚才获取到的数据,发送给B服务器,获取openid(这个相当于客户的id,在我们自己的小程序中是唯一的,用于辨识是哪个用户)
下面是openid获取的演示
# A服务器 python
#后台我使用的是python 只讲下逻辑
#比如下面这个data就是前端发过来的数据
data:{
code: "aaabbbcccc",
ordercode: 100,
}
#定义一个获取openid 的函数,把整个data都传进去
def getOpenID(kwargs):
param = {
'code': kwargs['code'], #前端获取到的用户登陆凭证
'appid': "wxd00000000000000", #小程序的appid
'secret': "000000000000000000000000",#商户的密钥
'grant_type': 'authorization_code', #这个直接固定死值就可以了
}
# 通过code获取openid
openIdUrl = 'https://api.weixin.qq.com/sns/jscode2session?appid={}&secret={}&js_code={}&grant_type=authorization_code'.format(
param['appid'], param['secret'], param['code'])
resp = requests.get(openIdUrl)
#一定要使用get获取,不用使用post
#成功后的值
# {
# "openid":"xxxxxxxxxxxxxxxx",
# "session_key":"xxxxxxxxxxxxx"
# }
openId = json.loads(resp.text)
return openId['openid']
getOpenID(data) #执行函数
openid获取
参考官方文档:https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/login/auth.code2Session.html
目前我们获取到了openid
2、nonce_str (随机字符串)
这个作用主要是为了防止别人模仿发送假的数据给微信,不用太纠结作用,写入就可以了,下面讲下他的生成方法
官方解释
#A服务器 python
def get_nonce_str():
char = "abcdefghijklmnopqrstuvwxyz0123456789"
nonce_str = ""
for i in range(0, 32):
rad_num = random.randint(0, len(char) - 1)
nonce_str += char[rad_num]
nonce_str = nonce_str .upper() #注意:最后要全部处理成大写
return nonce_str
随机字符串
参考官方文档:https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=4_3
3、out_trade_no (商户订单号)
这个就是你自己定义一个订单号,保证唯一性,可随意定制
4、sign (签名)
签名,主要是防止被伪造,官方给出了签名算法
签名生成的通用步骤如下:
第一步,设所有发送或者接收到的数据为集合M,将集合M内非空参数值的参数按照参数名ASCII码从小到大排序(字典序),使用URL键值对的格式(即key1=value1&key2=value2…)拼接成字符串stringA。
特别注意以下重要规则:
◆ 参数名ASCII码从小到大排序(字典序); ◆ 如果参数的值为空不参与签名; ◆ 参数名区分大小写; ◆
验证调用返回或微信主动通知签名时,传送的sign参数不参与签名,将生成的签名与该sign值作校验。 ◆
微信接口可能增加字段,验证签名时必须支持增加的扩展字段
第二步,在stringA最后拼接上key得到stringSignTemp字符串,并对stringSignTemp进行MD5运算,再将得到的字符串所有字符转换为大写,得到sign值signValue。◆ key设置路径:微信商户平台(pay.weixin.qq.com)–>账户设置–>API安全–>密钥设置
我们这边来实现一下
#A服务器 python
# MD5加密
def MD5(str):
md5 = hashlib.md5()
md5.update(str.encode('utf-8'))
return md5.hexdigest()
#签名
def getSign(kwargs):
keys, paras = sorted(kwargs), [] #先排序
paras = ['{}={}'.format(key, kwargs[key]) for key in keys if key != ''] #去除空值,把格式转换成key=value
stringA = '&'.join(paras) #连接在一起
stringSignTemp = stringA + '&key=' + "xxxxxxxxx" #添加密钥
sign = MD5(stringSignTemp).upper() #MD5加密
return sign
#1、根据key排序
#2、去除key为空的值
#3、json格式转程字符串 key = value
#4、字符串添加密钥
#5、MD5加密 并转大写
签名
参考官方文档:https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=4_3
函数大家都会写,那里面提到的参数到底是什么数据呢,其实就是统一下单你要提交的所有数据
图片
图片文献官方文档:https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=9_1#
就是把里面所有的必填项写成json,其中的sign 不要写进去,虽然他是必填,但不要写进去,这点官方文档没有说明
#A服务器 python
pay_info = {
"appid": "xxxxxxxxxxxxxxxxx", #小程序的appid
"mch_id": "xxxxxxxxxxxxxx", #微信支付分配的商户号
"nonce_str": get_nonce_str(), #随机字符串 参考2
"body": "产品名称",
"out_trade_no": out_trade_no, #商户订单号 参考3
"total_fee": 1, #费用
"spbill_create_ip": "000.000.000.000",#就是你A服务器的ip
"notify_url": "https://xxxxxx", #这个是支付成功后会返回支付详情,就返回到你写的这个网址
"trade_type": "JSAPI", #不用改
"openid": openID, #你刚才获取的openid
}
getSign(pay_info) #把这个json去生成加密得到 sign
签名虽然获取完了,最后还要把刚才的要传输的数据xml格式化
pay_info['sign'] = getSign(pay_info)
#注意:要先把生成的sign 放入到刚才的 json里面去,然后再去转xml
#转换成xml
def getxml(kwargs):
# 生成xml
xml = ''
for key, value in kwargs.items():
xml += '<{0}><{1}></{0}>\n'.format(key, value)
xml = '<xml>{0}</xml>'.format(xml)
xml = xml.encode('utf-8')
# print(xml)wx
return xml
此时所有数据都获取完了,也xml格式化成 微信支付中统一下单的格式了,我们下面发送给B服务器,获取后面第二步下单的数据就可以了
最后一步,统一下单
讲几个着重点:
发送的一定要是最后格式化的xml
post发送
发送的网址:https://api.mch.weixin.qq.com/pay/unifiedorder
def wxpay(xml):
oUrl = 'https://api.mch.weixin.qq.com/pay/unifiedorder'
resp = requests.post(Url, data=xml)
return resp.content
统一下单
参考官方文档:https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=9_1#
以为结束了,不,这里有个小坑,一定要注意下
获取到的数据 是不能直接传给 客户端的 传给客户端的需要是(第2步 下单)所需要的数据
这个我们下一张再讲吧
蓝框部分咱们已经全部完成了