nodejs 微信支付 (koa 框架)

nodejs实现微信支付,koa框架的接入流程,此文档通过测试,在小程序/微信公众号的后端代码中使用, 已经用于生产环境, 如有需要,请放心复制.

根据官方微信支付的文档
和流程图

nodejs 微信支付 (koa 框架)_第1张图片
image

支付过程可以分为后端流程和前端流程

后端流程

后端分为2步

  1. 根据用户的下单请求调用微信统一下单api拿到返回的关键数据prepay_id

export const prepay = async ({openid,orderId,desc,totalPrice,spbill_create_ip})=> {
    // 通过查阅文档,调用统一下单有10个参数是必须的
    let obj = {
        appid,
        mch_id,
        nonce_str: get_nonce_str(32),
        body: desc,
        out_trade_no: orderId,
        total_fee: parseInt(totalPrice * 100),
        spbill_create_ip,
        notify_url,
        trade_type:'JSAPI',
        openid
    }
    // js的默认排序即为ASCII的从小到大进行排序(字典排序)
    let arr = Object.keys(obj).sort().map(item => {
        return `${item}=${obj[item]}`;
    });
       // 这里拼接签名字符串的时候一定要注意: 商户的key是要单独拿出来拼在最后面的
    let str = arr.join('&') + '&key=' + key;
    // appid=wxf8600b***b5dfb&body=德胜村&mch_id=1490909372&nonce_str=plfbp2bhr0id1z6aktmndfot94hkewcv¬ify_url=https://server.***.cn/wechat/pay_notify&openid=oFm4h0WvnQWB4ocFmdPzsWywlE8c&out_trade_no=20150806125346&spbill_create_ip=127.0.0.1&total_fee=56600&trade_type=JSAPI&key=Lzy1234567890111***5161718192
    
    obj.sign = getSign(str);
    let res;
    try{
        // 调用微信统一下单接口拿到 prepay_id
        res = await wechatPay(obj);
        let {prepay_id} = res;
        if(prepay_id){
            res = getClientPayConfig(prepay_id)
        }
        // console.log(res);
    }catch(e){
        res = e;
        console.log(e);
    }
    return res;
}


/**
 * 统一下单 prepay_url = 'https://api.mch.weixin.qq.com/pay/unifiedorder';
 * @param {Object} obj 调用统一下单的必须参数
 */ 
const wechatPay = (obj)=>{
    let xml = json2xml(obj);
    console.log(xml)
    return new Promise((resolve,reject)=>{
        // 这里用了reques库,不熟悉的同学可以看看相关文档 https://github.com/request/request
        // 总之就是向微信的统一下单接口提交一个xml
        request({method:'POST',url: prepay_url,body: xml},(err,res, body)=>{
            if(err){
                reject(err);
            }else{
                //如果成功即可得到微信返回参数
                console.log(body);
                let obj = parseXml(body).xml;
                resolve(obj);
            }
        });
    });
}


/**
 * 对指定字符串进行md5加密
 * @param {String} str 
 */
const getSign = (str)=>{
    console.log(str)
    let hash = crypto.createHash('md5').update(str,'utf8');
    return hash.digest('hex').toUpperCase();
}

/**
 * 转化xml用了xml2js库  
    https://github.com/Leonidas-from-XIV/node-xml2js
 * @param {Object} obj 
 */
const json2xml = (obj)=>{
    let builder = new xml2js.Builder({
        headless:true,
        allowSurrogateChars: true,
        rootName:'xml',
        cdata:true
    });
    var xml = builder.buildObject(obj);
    return xml;
}

const parseXml = (xml)=>{
    let {parseString} = xml2js;
    let res;
    parseString(xml,  {
        trim: true,
        explicitArray: false
    }, function (err, result) {
        res = result;
    });
    return res;
} 

/**
 * 生成指定长度的随机数
 * @param {*int} len 
 */
const get_nonce_str = (len)=>{
    let str = '';
    while(str.length < len){
        str +=  Math.random().toString(36).slice(2);
    }
    return str.slice(-len);
}

  1. 通过prepay_id生成前端调启微信支付界面的必要参数
    官方文档
/**
 * 生成前端调启支付界面的必要参数
 * @param {String} prepay_id 
 */
const getClientPayConfig = (prepay_id)=>{
    let obj = {
        appId: appid,
        timeStamp: String(Math.floor(Date.now()/1000)),,
        nonceStr: get_nonce_str(32),
        package: 'prepay_id=' + prepay_id,
        signType: 'MD5'
    }
    let arr = Object.keys(obj).sort().map(item => {
        return `${item}=${obj[item]}`;
    });
    let str = arr.join('&') + '&key=' + key;
    obj.paySign = getSign(str);
    return obj;
}

前端流程

前端分为2步 官方文档

  1. 向后台提交支付订单的请求
  2. 拿到后台返回参数, 调启支付页面
 $api({
    method:'POST',
    url:'/order',
    data: obj,
    success:(data)=>{
        console.log(data);
        /**
         *  {
                appId:"wxf860****03b5dfb"
                nonceStr:"o5any3uj14tyfjr8wa249n5s0nnp9rkl"
                package:"prepay_id=wx20171104151803201b17a3100900948881"
                paySign:"D2632F71E4CB7E9E18D329460FDF5EB0"
                signType:"MD5"
                timeStamp:"1509779883"
            }
        */
        let obj = Object.assign({
            'success':function(res){
                wx.showModal({
                    title: '提示',
                    content: '支付成功',
                    showCancel: false
                });
            },
            'fail':function(res){
                wx.showModal({
                    title: '提示',
                    content: '取消支付',
                    showCancel: false
                });
            }
        },data)
        //调用小程序支付api,若为网页支付,查看相应文档即可. (若是spa网页支付,有一个支付目录的坑.最粗暴的方式:刷新进入支付页面)
        wx.requestPayment(obj)
    }
})

签名错误

根据经验 签名错误是xml的加密出错了
这里贴出一个提交统一下单的原始xml
这里说明一下: 经过亲测 spbill_create_ip, notify_url这两个参数即使是写死的也不是导致签名错误的原因



    wxf8600b48303b5dfb
    德胜村
    1490909372
    t7z9yb0wa8e5zhcwaw4ovjlrzj39t2xh
    https://server.**.cn/wechat/pay_notify
    oFm4h0WvnQWB4ocFmdPzsWywlE8c
    20150806125346
    127.0.0.1
    56600
    JSAPI
    39FD69074F0B184D10CC5E826914785A






遇到签名错误,不要着急,进行以下2步排查,定能解决问题

  1. 到官方调试界面,输入自己的参数,看看最终的签名是否和自己生成的一致
  1. 如果签名没错,那肯定是商户信息的问题了(本人此处被坑了很久,老板给了我mch_idkey都是正确的,结果未安装操作证书,导致我调试了很久找不到原因,心中一万匹草泥马)

检查商户信息,也就是商户号mch_id和商户的key(这里需要注意key,是申请微信支付成功后,腾讯发给申请者邮件里面的秘钥,要想此秘钥生效还需要安装操作证书)

你可能感兴趣的:(nodejs 微信支付 (koa 框架))