微信小程序 + nodeJs,loopback 实现支付

实现小程序的支付,首先需要去微信官网先了解一下微信小程序支付相关接口文档:https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=7_3&index=1

微信支付首先需要调用微信的统一下单接口,返回微信支付接口需要数据。具体参数参考:https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=9_1

在后端服务中,提供一个小程序前端调用的订单接口,生成预定单并访问微信统一下单接口返回微信接口数据,具体服务端代码如下:

module.exports = (req, res) => {
  // TODO replace the codes bellow and add your own codes here
  const wx_app_id = server.get('wx-app-id');
    let pay_info = {
        [wx_app_id]: { // 定义能支付的商户,可能存在多个
            mch_id: server.get('wx-mch-id'),
            pay_key: server.get('wx-pay-key') 
        }
    }if(!(req.body.appid in pay_info)) {
        res.send({
            successed: false,
            message: '商户不支持支付',
            datalist: '暂不支持支付的appid'
        });
        return
    }
    let sendtowx = {
        appid: req.body.appid,
        mch_id: pay_info[req.body.appid].mch_id,
        nonce_str: moment().valueOf(),
        body: req.body.body,
        // detail: req.body.detail,    
        // attach: req.body.attach,
        out_trade_no: getOrderNo(),   //订单在同一商家不能重复
        total_fee: req.body.total_fee,
        spbill_create_ip: req.headers['x-real-ip'] || req.headers['x-forwarded-for'] || req.connection.remoteAddress,
        notify_url: req.body.notify_url,  // 订单支付反馈通知接口,一般做修改订单完成状态用,微信支付成功后会在后台调用
        trade_type: 'JSAPI',
        openid: req.body.openid
    }
    sendtowx.sign = wx_sign(sendtowx, pay_info[req.body.appid].pay_key);  //生成MD5加密字符,传入字段不能有undefined。否则会报签名错误

    let request_str = '<xml>'
    for (let item in sendtowx) {
        request_str = request_str + '<' + item + '>'
        request_str = request_str + sendtowx[item]
        request_str = request_str + '</' + item + '>'
    }
    request_str = request_str + '</xml>'
    console.log('request_str:', request_str);
    const AppOrder = server.models.AppOrder;
    AppOrder.findOrCreate({where:req.body.where},{  // 首先创建系统订单,此时状态为待支付
        status:"0",
        pay_price: sendtowx.total_fee/100,
        order_no: sendtowx.out_trade_no
    }, function(err, data, created) {
        console.log(data,created,'s;s/');
        if (err) {
          console.log(err);
        } else if(created){
            console.log("创建预付订单成功",data);
          request.post(                    // 调用微信支付统一下单接口,返回微信预订单数据
            {
              url : 'https://api.mch.weixin.qq.com/pay/unifiedorder',
              // headers: {
              // 'Content-Type':'text/xml; charset=utf-8',
              // 'Content-Length': data.length
              // },
              body: request_str,
              rejectUnauthorized: false
            }, function(err, httpResponse, body){
              // 请求完成之后的回调函数    
                console.log(body)
                parseString(body, {explicitArray:false}, function (err, result) {
                    let res_body = { }
                    if(result.xml.return_code == 'SUCCESS') {
                        res_body.successed = true
                        res_body.message = 'ok'
                        let temp = {
                            appId: result.xml.appid,
                            timeStamp: moment().format('X'),
                            nonceStr: moment().format('x'),
                            package: 'prepay_id='+result.xml.prepay_id,
                            signType: "MD5"
                        }
                        temp.paySign = wx_sign(temp, pay_info[result.xml.appid].pay_key)
                        result.xml = temp
                    }
                    else {
                        res_body.successed = false
                        res_body.message = '支付失败'
                    }
                    if(res_body.message) {
                        res_body.datalist = result.xml
                        res.send(res_body);
                    }
                });
            })
                }
                else {
                    res.send({
                        code: "applied",
                        successed: false,
                        message: '不能重复提交订单!'
                    });
                }
    });
    
}

经常遇到的问题是会报签名错误问题,排查方法:

1.排查微信支付申请的pay-key是否正确,或写成了appid、app-key;

2.排查在上传MD5签名时是否传入了undefined的字段;

3.如果都没问题,建议在微信商户管理后面中重新设置pay-key。

统一下单结束后,小程序前端调用统一下单接口,开始微信支付:

payNow: function (e) {
    let that = this;
    let data = {
      openid: app.globalData.openId,
      appid: app.globalData.app_id,
      body: this.data.applyGame.name + "测试",
      // attach: this.data.applyGame.id,//附加信息,暂时不添加
      total_fee: 1,
      notify_url: app.globalData.appUrl + 'sdk/wxpayvnotify', // 微信支付成功反馈通知接口
      where:{ // 不能重复提交订单条件
        status: orderStatus.pay,
      }
    };
    pay(data, (res) => {
      if (res.statusCode == 200) {
        if (res.data.successed == true) {
          let backobj = res.data.datalist;

          let timestamp = backobj.timeStamp;
          let nonceStr = backobj.nonceStr;
          let prepay_id = backobj.package;
          let signType = backobj.signType;
          let paySign = backobj.paySign;
          wx.requestPayment({
            'timeStamp': timestamp,
            'nonceStr': nonceStr,
            'package': prepay_id,
            'signType': signType,
            'paySign': paySign,
            'notify_url': app.globalData.appUrl + 'sdk/wxpayverify',
            success: function (res) {
              wx.navigateTo({
                url: "/pages/my-game/index",
              })
              wx.switchTab({
                url: '/pages/my-game/index',
              })
            },
            fail: function (res) {
              console.log(res,'支付失败');
              wx.showToast({
                title: '支付失败!',
                icon: 'none',
              })
              // wx.navigateTo({
              //   url: "../pay/payfail/payfail",
              // })
            },
          })
        } else {
          if (res.data.code === 'applied'){
            wx.showToast({
              title: '已经报名',
              icon: 'none',
            })
          }else{
            wx.showToast({
              title: res.data.message,
              icon: 'none',
            })
          }
          
        }
      }else{
        wx.showToast({
          title: '支付失败!',
          icon: 'none',
        })
      }
    })
  }

支付完成。

console.log(e,'s//s/');

let that = this;

// if (!this.data.payNowBottonUse) {//防止按钮重复点击

// return;

// }

// this.setData({

// payNowBottonUse: false

// })

let data = {

openid: app.globalData.openId,

appid: app.globalData.app_id,

body: this.data.applyGame.name + "报名测试",

// body: "pppddpdp",

// detail: JSON.stringify(that.data.payOrdrInfoData.specific_project),//服务id

// attach: this.data.applyGame.id,//附加信息,暂时不添加

// //product_id: "002",//trade_type=NATIVE时(即扫码支付),此参数必传。

total_fee: 1,

notify_url: app.globalData.appUrl + 'sdk/wxpayvnotify',

member_id: this.data.memberInfo.id,

user_id: app.globalData.userId,

game_id: this.data.applyGame.id,

where:{ // 不能重复提交订单条件

status: orderStatus.pay,

gameId: this.data.applyGame.id,

memberId: this.data.memberInfo.id

}

};

pay(data, (res) => {

if (res.statusCode == 200) {

if (res.data.successed == true) {

let backobj = res.data.datalist;

console.log(backobj,'slslslslls');

let timestamp = backobj.timeStamp;

let nonceStr = backobj.nonceStr;

let prepay_id = backobj.package;

let signType = backobj.signType;

let paySign = backobj.paySign;

wx.requestPayment({

'timeStamp': timestamp,

'nonceStr': nonceStr,

'package': prepay_id,

'signType': signType,

'paySign': paySign,

'notify_url': app.globalData.appUrl + 'sdk/wxpayverify',

success: function (res) {

wx.navigateTo({

url: "/pages/my-game/index",

})

wx.switchTab({

url: '/pages/my-game/index',

})

},

fail: function (res) {

console.log(res,'支付失败');

wx.showToast({

title: '支付失败!',

icon: 'none',

})

// wx.navigateTo({

// url: "../pay/payfail/payfail",

// })

},

})

} else {

if (res.data.code === 'applied'){

wx.showToast({

title: '该棋手已经报名',

icon: 'none',

})

}else{

wx.showToast({

title: res.data.message,

icon: 'none',

})

}

}

}else{

wx.showToast({

title: '支付失败!',

icon: 'none',

})

}

})