JSAPI微信支付
JsApi微信支付
最近项目涉及到微信支付的功能,在这里简单分享下整体的开发流程,这里要介绍的是JSAPI支付。 上述介绍可以简单理解为,jsapi支付必须在微信浏览器中进行。 微信支付支持多种接入模式,常用的就是直连模式及服务商模式。
申请商户号(需要有营业执照、个人不太行)
注册微信公众号(同样也是要有营业执照 公司主体)
接入微信jsApi js文件
<script src="http://res.wx.qq.com/open/js/jweixin-1.6.0.js"></script>
Node后端代码
WXCONFIG.js
module.exports = {
appId:'wx8*******31', // 公众号id(Appid)应用id
mchId:'16*****99', // 商户号
openId:'omn-----------alVD91UU', // 微信支付获取到的openid(用户的标识)
serial_no:'4C3432FF8***************ECE8049DEA3', // 证书的序列号
authorization_type:'WECHATPAY2-SHA256-RSA2048', // 认证类型 暂时就这一个固定值
apiclient_key:`
-----BEGIN PRIVATE KEY-----
MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQC+cfLSnMUYA4dk
tC/p---------------------------------------------Zd3/lQ3SQKBgQCx
JNYGxkiELsQXD9aookoFb-------------------------------------------
gAVz5zodCDKW9c66m0AbALaaBnpfZdr0Iv1bp/v4aw0*********************
EEWPggdxbTnKX6NZHoJQoqiLzHmN18HoyxD4nTUgaIzCb3hT08pBFWyDsej9Ud/4
zNWX+2bzFPlNIY5lqbH02ESXp1BK1Bf8S6lONaPksQKBgQCAfRn0kl1xlpfY1Exo
k0gAth3u7M97dVU+S+9PpKE/KVBChxnuTmOao0cwDwEsKTuy1q80qlr6R3lNkjHn
YH3YXjBtzSNpn3cRuosJ05FUdV8q1nZvNMk35BbyIwip+NeC5R7ZkrlaYGOjLoWx
xfEal/jdw/4asGsoYQqXJhhziA==
-----END PRIVATE KEY-----
`, // api认证之后的pem文件 私钥
data:{
"mchid": "1*******7399",
"out_trade_no": "93BEC0C930BF1AFEB40B4A08C8FB24",
"appid": "wx89d********d7b6c431",
"description": "Image形象店-深圳腾大-QQ公仔",
"notify_url": "https://www.weixin.qq.com/wxpay/pay.php",
"amount": {
"total": 1,
"currency": "CNY"
},
"payer": {
"openid": "omn7h6t******alVD91UU"
}
}
}
app.js
const express = require('express')
const jsrsasign = require('jsrsasign')
const app = express()
const WXCONFIG = require('./WXCONFIG')
const bodyParser = require('body-parser')
const http = require('axios')
const cors = require('cors')
app.use(cors())
app.use(bodyParser({extended:true}))
class Pay {
constructor() {
this.appId = WXCONFIG.appId
this.mchid = WXCONFIG.mchId
this.apiKey = WXCONFIG.apiKey
this.timestamp = '' // 时间戳
this.nonce_str = '' // 随机串
this.signature = '' // 签名值
this.authorization = ''
// 生成Authorization需要数据
this.paySign = '' // 调起支付的paySign
this.signType = 'RSA' // 签名方式
this.package = '' // 与订单号
}
// 生成请求头前面的签名值Authorization
genterateAuthorization(req,res) {
this.signature = this.signalStr(req)
// 生成HTTP头
let authorization = `${WXCONFIG.authorization_type} mchid="${this.mchid}",nonce_str="${this.nonce_str}",signature="${this.signature}",timestamp="${this.timestamp}",serial_no="${WXCONFIG.serial_no}"`
console.log('authorization',authorization);
this.authorization = authorization
return authorization
}
signalStr(req){
let requestMethod = req.method
this.nonce_str = this.generateNonceStr(18,30)
this.timestamp = this.generateCurrentTimeStamp()
let url = '/v3/pay/transactions/jsapi'
let body = JSON.stringify(req.body)
let signStr = `${requestMethod}\n${this.nonce_str}\n${this.timestamp}\n${url}\n${body}\n`
// console.log('参与签名的字符串\n',signStr);
let sign_message = 'POST' +'\n' + '/v3/pay/transactions/jsapi\n' + this.timestamp + '\n' + this.nonce_str + '\n'+ JSON.stringify(req.body) + '\n'
// console.log('999999',sign_message);
console.log('生成的签名',this.encrtySigin256(sign_message))
return this.encrtySigin256(sign_message)
}
// 将数据生成sha256Rsa
encrtySigin256(data){
let RSA = new jsrsasign.RSAKey()
const appclient_pem = WXCONFIG.apiclient_key
RSA = jsrsasign.KEYUTIL.getKey(appclient_pem) // 将私钥转化为16进制
const sign256Str = new jsrsasign.KJUR.crypto.Signature({alg:'SHA256withRSA'})
sign256Str.init(RSA)
sign256Str.updateString(data)
// 转化成base64
const sign = jsrsasign.hextob64(sign256Str.sign())
return sign
}
// 生成随机字符串
generateNonceStr(min,max){
var returnStr = "",
range = (max ? Math.round(Math.random() * (max-min)) + min : min),
charStr = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
for(var i=0; i<range; i++){
var index = Math.round(Math.random() * (charStr.length-1));
returnStr += charStr.substring(index,index+1);
}
return returnStr;
}
// 生成当前时间戳
generateCurrentTimeStamp(){
return Math.round(new Date().getTime()/1000).toString()
}
// 下单
placeOrder(){
return new Promise(async (resolve,reject)=>{
try {
let that = this
let response = await http({
method:"POST",
url:"https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi",
headers:{
"Authorization":that.authorization,
"Accept":"application/json",
"Content-Type":"application/json"
},
data:WXCONFIG.data // 应该是获取到接口请求到的数据 因为接口请求数据和配置里面一样所以就用了WXCONFIG.data 应该用(req.body)
})
// console.log('res',response)
if(response.status == 200) {
console.log('生成预订单成功',response.data)
this.package = 'prepay_id=' + response.data.prepay_id
return resolve(response.data)
}
}catch (e) {
console.log('error错误',e)
return reject(e)
}
})
}
// 调起支付
pay(req,res) {
let signStr = `${this.appId}\n${this.timestamp}\n${this.nonce_str}\n${this.package}\n`
console.log('参与调起支付的签名字段格式',signStr)
console.log('计算调起支付的签名值',this.encrtySigin256(signStr))
res.send({
appId:this.appId,
timeStamp:this.timestamp,
nonceStr:this.nonce_str,
package:this.package,
signType:this.signType,
paySign:this.encrtySigin256(signStr)
})
}
}
app.get('/',(req,res) => {
res.send("hello world")
})
app.post('/pay',async (req,res) => {
let pay = new Pay()
let authorization = pay.genterateAuthorization(req,res)
let response = await pay.placeOrder()
console.log('responsePrepay_id',response)
pay.pay(req,res)
// res.send(response)
// console.log('Authorization',authorization);
})
app.listen(8084,(() => {
console.log("server is running on port 8084")
}))
详解
通过函数genterateAuthorization 获取请求时候的携带的Authorization
字段
// 生成请求头前面的签名值Authorization
genterateAuthorization(req,res) {
this.signature = this.signalStr(req)
// 生成HTTP头
let authorization = `${WXCONFIG.authorization_type} mchid="${this.mchid}",nonce_str="${this.nonce_str}",signature="${this.signature}",timestamp="${this.timestamp}",serial_no="${WXCONFIG.serial_no}"`
console.log('authorization',authorization);
this.authorization = authorization
return authorization
}
生成的头部认证字段示例
WECHATPAY2-SHA256-RSA2048 mchid=“1668797399”,nonce_str=“RweCQHiE59ZN8gnL4mFtEtq”,signature=“QVdtEOxEZkvGmQIhPCVFcj42K6ckWfyaoh1OUQRyJdgV7EJeI6Q8FBpgTDsM+CGohnTr6FhMIHsidLNq9A70BTYEEnvBnz1TcP8KPVmQtowB4dWiJa1NACJYHJ
nIS43b6TUk/fVEgyYF3gKcOWG/SGqPWuayBlpwnO+lKfwwVygzyMe33/jUi6WeCv+FfB5WBjXfzkwt4piOUvMNj7DdfF1aOMSe1xgmCRrTgBNcNSRLANZfNUorMvnxkJVeW8wM55K73nPgBlgjg/1JHR/NG1AgUgoxMjiXkcy7nVsu8zVZdDH3xSZZc6mJwUzwA3U36zV+a6IDHsOR3rMIOkYHOoGw==”,timestamp=“1707020461”,serial_no=“4C3432FF864E9B3DBA134D90909ECE8049DEA3”
获取到签名串之后需要开始对接下单接口
下单接口文档
根据下单接口生成微信内部生成的预订单 返回{ prepay_id: 'wx0412210121sd17475d0d67740b3ab59c0000' }
返回此数据之后将此数据返回给前端进行调用jsApi调起支付
后端返回这类格式数据最好
jsApi调起支付
前端(api.js)
// 获取微信支付预订单
export const getWXOrder = () => {
return request({
method:"POST",
url:"https://98989889.ngrok.xiaomiqiu123.top/pay", // 后端接口
data:{
"mchid": "1677678678399",
"out_trade_no": "93BEC0C930BF1AFEB40B4A08C8FB24", // 内部订单号
"appid": "wx768678678686787657",
"description": "Image形象店-深圳腾大-QQ公仔",
"notify_url": "https://www.weixin.qq.com/wxpay/pay.php",
"amount": {
"total": 1,
"currency": "CNY"
},
"payer": {
"openid": "omn7678678h6tYdsfsdfsdfsdfaJRsKBABnalUU"
}
}
})
}
调用微信支付注意paySign字段的生成方式[文档地址](https://pay.weixin.qq.com/docs/merchant/apis/jsapi-payment/jsapi-transfer-payment.html)
签名时候的时间戳数据随机串都要是之前下单时候的数据不能变动 不然会支付签名认证失败
wxPay(){
function onBridgeReady() {
getWXOrder().then((res) => {
WeixinJSBridge.invoke('getBrandWCPayRequest', {
"appId": res.data.appId, //公众号ID,由商户传入
"timeStamp": res.data.timeStamp, //时间戳,自1970年以来的秒数
"nonceStr": res.data.nonceStr, //随机串
"package": res.data.package,
"signType": "RSA", //微信签名方式:
"paySign": res.data.paySign
},
function(res) {
if (res.err_msg == "get_brand_wcpay_request:ok") {
// 使用以上方式判断前端返回,微信团队郑重提示:
//res.err_msg将在用户支付成功后返回ok,但并不保证它绝对可靠。
}
});
})
}
if (typeof WeixinJSBridge == "undefined") {
if (document.addEventListener) {
document.addEventListener('WeixinJSBridgeReady', onBridgeReady, false);
} else if (document.attachEvent) {
document.attachEvent('WeixinJSBridgeReady', onBridgeReady);
document.attachEvent('onWeixinJSBridgeReady', onBridgeReady);
}
} else {
onBridgeReady();
}
},
验签成功之后就可以在微信环境调起支付了
失败的话出现验签失败
验签失败可能问题
1、调起支付时候传递的时间戳和随机数和下的那时候的不一样
2、与下单时候生成签名的算法不一样,例如一个md5
一个用了RSA
3、听天由命…多看看文档
版权声明
本文系作者 @黄粱一梦 转载请注明出处,文中若有转载的以及参考文章地址也需注明。\(^o^)/~