JSAPI微信支付

黄粱一梦2024-02-0650

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调起支付

后端返回这类格式数据最好

image.png

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();
      }
    },

验签成功之后就可以在微信环境调起支付了

image.png

失败的话出现验签失败

image.png

验签失败可能问题

1、调起支付时候传递的时间戳和随机数和下的那时候的不一样
2、与下单时候生成签名的算法不一样,例如一个md5一个用了RSA
3、听天由命…多看看文档

分类:前端Vue

标签:前端后端vue

上一篇uniapp uni-search-bar组件在微信小程序输入点击无效下一篇嗨!好久不见!

版权声明

本文系作者 @黄粱一梦 转载请注明出处,文中若有转载的以及参考文章地址也需注明。\(^o^)/~

Preview