前言
本文将介绍在nodejs开发环境下的微信h5支付流程,由于笔者被微信文档逼疯,又被网上的多种不完全的博客坑害,所以总结自己的经验,给出完整流程。注意!笔者的开发环境是uniapp+unicloud,运行环境是在微信浏览器中的公众号h5网页,依托于微信公众号。后端是nodejs,如果是使用java和php的朋友可以完全参考微信开放文档,因为有完整的sdk示例流程。只需要了解前端换取code的方式即可。
环境准备
- h5的运行环境需要有微信浏览器的独特api,开发调试必须在微信浏览器中进行,所以我们需要下载微信开发者工具,点击微信公众号项目就可以打开微信浏览器,然后当做正常浏览器使用即可
- 微信换取code的时候需要跳转外网,也就是说本地调试是无法正常进行的,需要使用内网穿透,在下面会具体介绍
- 需要准备各种api和密匙,下文使用到时,会具体说明位置和配置方法
使用流程
设置安全域名
在使用微信h5的时候,为了保证在这里的监管,微信需要你跳转到它的指定页面,在链接后面带上你的各种参数,如APPID等,然后加上你的实际地址,然后微信从那个页面跳回你的页面,链接上就会给你放上它的code,你可以用这个code去换取openid,这也就是当前项目下,对该用户的唯一标识,后面很多操作都是基于这一点的。
完成这一步,需要配置授权域名,这里配置。
配置完成后就可以进行域名跳转了
设置开发环境
这里会遇到一个问题,就是在开发初期,我们可能并没有服务器,再者,如果传上服务器,那么调试将变的无比困难,这里笔者的解决办法是使用内网穿透软件。在我们开发的时候,一般前端会有一个本地的开发服务器,只需要将内网穿透的外网地址解析到我们本地开发服务器的地址,然后在安全域名配置的时候,将内网穿透软件提供的外网域名放上去就可以了,这个时候,我们就可以进行正常调试了。但是要注意,免费的内网穿透带宽特别低,而开发服务器又会占用较大的空间,可能会导致调试很慢,这个就需要各位自行解决了。例如笔者使用的花生壳软件。
设置初始页面
前面提到了,我们需要进入一个微信指定的页面,然后让微信跳转回来,以便获取code。只需要对页面url进行判断,没有code的情况下,跳转,有了code,就可以进入我们的主页面了
<template>
<view>
</view>
</template>
<script>
import {
utilWxTogo
} from '@/utils/utils.js'
export default {
data() {
return {
APPID: "你的appID", // 应用ID
mainPage: "https://314g552a52.vicp.fun/static"// 主页面域名
}
},
onLoad() {
const code = this.getCode() // 解析url上的code
const mainP= encodeURIComponent(this.mainPage) // js自带函数,编译http格式
const url_ =
`https://open.weixin.qq.com/connect/oauth2/authorize?appid=${this.APPID}&redirect_uri=${mainP}&response_type=code&scope=snsapi_base&state=123#wechat_redirect`
if (!code) // 如果没有code则跳转
window.location.href = url_
else// 有了code,则直接跳转进主页面,code一直在url上,所以不需要保存
utilWxTogo('/pages/login/login', {})() //自己写的跳转函数
},
methods: {
getCode() {
var url = new URL(window.location.href);
// 获取参数对象
var params = new URLSearchParams(url.search);
// 获取特定参数的值
return params.get('code');
}
}
}
</script>
<style>
</style>
APPID可以公众号平台的这里找到
换取openid
得到code后,就可以发到后端,换取openid了,这里其实也是向微信接口发送请求,操作如下
// 获取openId云函数入口函数
exports.main = async (event, context) => {
// 获取基础信息
const {data} = await axois.get('https://api.weixin.qq.com/sns/oauth2/access_token',{
params: {
appid: '你的APPID',
secret: '你的密匙', // 开发者密匙的位置,就在appid下面
code: event.code, // 前端传过来的code
grant_type: 'authorization_code'
}
})
return data
}
微信统一下单
准备工作
在进行支付前,需要先进行统一下单操作。在此之前,我们需要配置支付安全目录,把我们h5的域名加入。在这里配置
准备一下以下几个参数
const Payment = require('wxpay-v3'); // 下单工具,直接npm下载
const appid = "你的appid"; //appid
const mchid = '商户号'; //商户号
const serial_no = '商户证书序列号'; //商户证书序列号
const notify_url = 'https://127.0.0.1' // 支付结果回调地址,需要https,如果不需要回调,就随便写
const filePath = path.join(__dirname, 'apiclient_key.pem'); // 准备证书
const private_key = require('fs').readFileSync(filePath).toString() // 读取证书
准备apiv3密匙,位置如下
统一下单
初始化下单工具
const paymnet = new Payment({
appid: appid,
mchid: mchid,
private_key: private_key, //或者直接复制证书文件内容
serial_no: serial_no,
apiv3_private_key: '你的apiv3密匙',
notify_url: notify_url,
})
生成订单号,这里就随便用时间戳生成,只要保证不重复即可
function orderCode() {
let timestamp = new Date().getTime().toString();
const chars = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz'; //默认去掉了容易混淆的字符oOLl,9gq,Vv,Uu,I1
const len = 31;
const maxPos = chars.length;
let pwd = 'D'+timestamp;
for (let i = 0; i < len-timestamp.length; i++) {
pwd += chars.charAt(Math.floor(Math.random() * maxPos));
}
return pwd;
}
const order = orderCode()
使用jsapi下单,传入订单参数
let result = await paymnet.jsapi({
"mchid": mchid, //直连商户号
"out_trade_no": order, //商户系统内部订单号,只能是数字、大小写字母_-*且在同一个商户号下唯一
"appid": appid, //应用ID
"description": '', //商品描述
"attach": JSON.stringify(data.attach), //附加数据,可空
"notify_url": notify_url, //通知地址
"amount": {
"total": +data.money, //总金额,单位为分
"currency": "CNY"
},
"payer": {
"openid": data.openid //用户标识
}
})
生成签名,这个流程很奇怪,不复杂,但是跟着官网来总是失败,我去找了个别人写好的工具类,成功生成
const prepay_id = JSON.parse(result.data)['prepay_id'] // 注意返回的data是字符串json
const wxPay = new WxPayUtils(private_key, appid);
const sign_params = wxPay.paysign({
prepay_id,
order
})
return {
appId: appid,
...sign_params,
}
工具类内容如下
const RSA = require('./wx_rsa');
class WxPayUtils {
constructor(privateKey, appId) {
this.privateKey = privateKey;
this.appId = appId;
}
signLong(data) {
// let privateKey =private_key
let sign_rsa = new RSA.RSAKey();
sign_rsa = RSA.KEYUTIL.getKey(this.privateKey);
let hashAlg = 'sha256'; //sha256//此处换成腾讯要求的方式
let Sig = sign_rsa.signString(data, hashAlg);
Sig = RSA.hex2b64(Sig); // hex 转 b64
return Sig;
}
paysign(options) { //发起支付签的字段准备
let timeStamp = this.createTimeStamp(), //时间戳
nonceStr = options.order, //32位随机数
Ppackage = `prepay_id=${options.prepay_id}`, //prepay_id
signType = 'RSA'; //加签方式
//appId、timeStamp、nonceStr、package
let PpaySign = `${this.appId}\n${timeStamp}\n${nonceStr}\n${Ppackage}\n`; //需要加签的字段拼接
let cryptStr = this.signLong(PpaySign); //生成签名
let paySign = cryptStr;
return {
timeStamp,
nonceStr,
package: Ppackage,
signType,
paySign
};
}
createTimeStamp() {
return parseInt(new Date().getTime() / 1000) + ''
}
randomString() {
const chars = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678'; //默认去掉了容易混淆的字符oOLl,9gq,Vv,Uu,I1
const len = 32;
const maxPos = chars.length;
let pwd = '';
for (let i = 0; i < len; i++) {
pwd += chars.charAt(Math.floor(Math.random() * maxPos));
}
return pwd;
}
}
module.exports = WxPayUtils;
其中rsa文件太大,放到了百度网盘上,自取
链接:https://pan.baidu.com/s/1bxTygUSDrGuvKGKtZYplDQ?pwd=ac4p
提取码:ac4p
发起支付
统一下单接口返回的数据可以在微信浏览器中使用独有api-WeixinJSBridge直接发起支付,注意该api只存在于微信浏览器。
const {result} = await payBill({ // 向后端发起请求的方法
money: this.price,
openid: uni.getStorageSync('openId'),
});
weiPay(result).then((res) => {
//支付成功
})
weiPay函数如下
export function weiPay(params) {
return new Promise((resolve, reject) => {
WeixinJSBridge.invoke('getBrandWCPayRequest', params,
function(res) {
if (res.err_msg == "get_brand_wcpay_request:ok") {
// 使用以上方式判断前端返回,微信团队郑重提示:
//res.err_msg将在用户支付成功后返回ok,但并不保证它绝对可靠。
resolve(res)
} else {
reject(res)
}
});
})
}
注意事项
注意在第一步跳转微信制定页面的时候,给微信的返回页面必须是https,如果是http,那么支付就会出现问题,比如说提示你某个参数不能为空,实际上根本没有这个参数,但是用https就不会有这个问题