微信公众号网页用uniapp,后台用的python,近期对接微信支付-apiv3版-jsapi支付,特此整理记录方便日后查找使用
apiv3升级后,请求体使用不用xml使用json,每次请求需要在header中添加签名,而签名需要用微信支付的商户证书私钥进行RSA加密
使用公众号进行对接流程如下
1、自行保存公众号access_token与jsapi_ticket
公众号access_token获取文档
公众号网页开发jsapi_ticket获取文档
uniapp-支付页面先引入注入微信网页开发所用的JS-SDK
微信公众号网页开发文档地址
// 支付页面代码
// 在页面onload中引入微信sdk,在APP.vue引入好像有点问题,不过为了保险起见,在App.vue页面再引入了一次
onLoad(e) {
var script =document.createElement('script');
script.src = "/static/js/jweixin-1.6.0.js";
script.type = 'text/javascript';
document.body.appendChild(script)
}
// App.vue页面
onShow: function() {
console.log('App Show')
// 引入微信sdk
const script = document.createElement('script');
script.src = "https://res.wx.qq.com/open/js/jweixin-1.6.0.js";
script.type = 'text/javascript';
document.body.appendChild(script)
}
2、在微信公众号 头像>功能设置页中进行配置域名白名单,把业务域名、JS接口安全域名、网页授权域名
全部添加。
公众号设置超链接
3、请求后台,返回注入js所需要的参数
注入时遇到的问题
3.1、签名出错,查看参数,查看加密方式,查看url参数(出错较多)
3.2、jsapi_list为空列表[]也不可以,提示fail,添加一个权限就可以了
注入js-sdk微信文档
//uniapp支付页关键代码
//注入js有一个当前页面的url,所以这里把当前的url保存传到后台,之前后台写死会出现不一致的问题,保险起见,这里由前端把url传入
var currentUrl = window.location.href;
var token = uni.getStorageSync('token')
var data = {
token: token,
appid: 'xxxx',
url: currentUrl
}
# python端关键代码-js注入参数生成
def jszhuru(appid, url, debug):
"""jsapi注入"""
#生成随机数
noncestr = str(uuid.uuid4()).replace('-', '')
# 这里请求数据库,拿到jsapi_ticket
# jsapi_ticket=xxxxxxxxxx
timestamp = int(time.time())
sing_str = "jsapi_ticket={}&noncestr={}×tamp={}&url={}".format(jsapi_ticket, noncestr, timestamp, url)
sha1_hash = hashlib.sha1()
sha1_hash.update(sing_str.encode('utf-8'))
sha1_sign = sha1_hash.hexdigest()
result = {
'debug': debug,
"appid": appid,
'noncestr': noncestr,
"timestamp": timestamp,
"signature": sha1_sign,
"url": url,
"jsapi_list": ['chooseImage']
}
return result
4、h5端注入js-sdk
// uniapp支付页关键代码
wx.config({
debug: e.data.data.result.debug, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。
appId: String(e.data.data.result.appid), // 必填,公众号的唯一标识
timestamp: String(e.data.data.result.timestamp), // 必填,生成签名的时间戳
nonceStr: String(e.data.data.result.noncestr), // 必填,生成签名的随机串
signature: String(e.data.data.result.signature), // 必填,签名
jsApiList: e.data.data.result.jsapi_list // 必填,需要使用的JS接口列表
});
wx.ready(function() {
that.zhuru_status = true
});
wx.error(function(res) {
that.zhuru_status = false
uni.showToast({
title:'微信支付加载失败',
icon:'none'
})
});
5、使用前端传来的code获取openid,之后调用下单接口,获取prepay_id,然后返回下单所需要的参数,前端调用支付功能
code获取openid微信文档
# 获取openid关键代码
url = 'https://api.weixin.qq.com/sns/oauth2/access_token?appid=xxx&secret=xxx&code=' + code + '&grant_type=authorization_code'
resp = requests.get(url)
resp_dict = json.loads(resp.text)
print(resp.text)
openid = resp_dict.get('openid')
# 后台请求jsapi下单接口关键代码
def xiadan(out_trade_no,description, amount_total, openid):
"""jsapi下单"""
# 订单过期时间
now = datetime.datetime.now()
# 计算1小时后的时间
future_time = now + relativedelta(hours=1)
# 将时间格式化为RFC 3339标准格式
time_expire = future_time.strftime('%Y-%m-%dT%H:%M:%S.%f') + 'Z'
appid = 'xxxx'
body_dict_list = [
{'key': 'appid', 'doc': '应用id', 'value': appid},
{'key': 'mchid', 'doc': '商户id', 'value': MCHID},
{'key': 'description', 'doc': '商品描述', 'value': description},
{'key': 'out_trade_no', 'doc': '内部订单号', 'value': out_trade_no},
{'key': 'time_expire', 'doc': '订单过期时间', 'value': time_expire},
{'key': 'notify_url', 'doc': '回调url', 'value': NOTIFY_URL},
{'key': 'amount', 'doc': '金额', 'value': {'total': amount_total}},
{'key': 'payer', 'doc': 'openid', 'value': {'openid': openid}},
]
# 2、字典排序
body_sort_list = sorted(body_dict_list, key=lambda x: x['key'])
# 生成请求体
body_dict = {}
for item in body_sort_list:
key = item.get('key')
value = item.get('value')
body_dict[key] = value
# 拼接证书的路径
current_file_path = os.path.abspath(__file__)
current_directory = os.path.dirname(current_file_path)
current_cwd = current_directory.replace('\\', '/')
current_cwd_end_index = current_cwd.rfind('/')
dir_path = current_cwd[:current_cwd_end_index]
private_path = os.path.join(dir_path, 'cert', 'pkcs1_private.pem')
cert_path = os.path.join(dir_path, 'cert', 'apiclient_cert.pem')
print('私钥路径', private_path)
print('证书路径', cert_path)
# 8、发起请求
result = wxfunc.wxpay_http(method='POST', path='/v3/pay/transactions/jsapi', body=body_dict,
private_key_pkcs1_path=private_path, cert_path=cert_path,
)
return json.loads(result)
# 后台生成调用支付参数部分-生成签名的关键代码
nonce_str = str(uuid.uuid4()).replace('-', '')
time_stamp = int(time.time())
appid = 'wxxxxxxx'
package = 'prepay_id=' + prepay_id
# 生成签名
sign_str = '{}\n{}\n{}\n{}\n'.format(appid,
time_stamp,
nonce_str,
package
)
sign = sha256_with_rsa(sign_str, private_path)
amount_total = order_info.amount_total
amount_str = str(Decimal(str(amount_total)) / 100)
body_dict_list = [
{'key': 'appId', 'doc': '应用id', 'value': str(appid)},
{'key': 'timeStamp', 'doc': '时间戳', 'value': str(time_stamp)},
{'key': 'nonceStr', 'doc': '随机数', 'value': str(nonce_str)},
{'key': 'package', 'doc': '下单id', 'value': str(package)},
{'key': 'signType', 'doc': '签名类型', 'value': 'RSA'},
{'key': 'paySign', 'doc': '签名值', 'value': sign},
{'key': 'description', 'doc': '商品描述', 'value': order_info.description},
{'key': 'amount', 'doc': '金额', 'value': amount_str},
{'key': 'order_id', 'doc': '订单号', 'value': order_id},
]
"""...返回调用支付所需要的参数"""
6、前端获取调用所需要的参数,发起支付
微信调起jsapi支付方式文档
// js调起支付
WeixinJSBridge.invoke('getBrandWCPayRequest', {
"appId": that.zhifu_dict.appId, //公众号ID,由商户传入
"timeStamp": that.zhifu_dict.timeStamp, //时间戳,自1970年以来的秒数
"nonceStr": that.zhifu_dict.nonceStr, //随机串
"package": that.zhifu_dict.package,
"signType": that.zhifu_dict.signType, //微信签名方式:
"paySign": that.zhifu_dict.paySign
},
function(res) {
if (res.err_msg == "get_brand_wcpay_request:ok") {
uni.showToast({
title:'支付成功',
icon:'success'
})
// 使用以上方式判断前端返回,微信团队郑重提示:
//res.err_msg将在用户支付成功后返回ok,但并不保证它绝对可靠。
}
});