微信小程序+ node koa2框架实现微信小程序支付

导语

小程序支持微信支付,前提条件是小程序账号的主体不能为个人,并且已经开通了商户号,商户在完成签约后,需要确认当前商户号同appid的绑定关系,方可使用。

在前置准备做好了之后,我们先看下小程序是如何调起微信支付的,官方文档

wx.requestPayment(Object object)

发起微信支付。了解更多信息,请查看微信支付接口文档

参数

Object object

属性 类型 默认值 必填 说明
timeStamp string 时间戳,从 1970 年 1 月 1 日 00:00:00 至今的秒数,即当前的时间
nonceStr string 随机字符串,长度为32个字符以下
package string 统一下单接口返回的 prepay_id 参数值,提交格式如:prepay_id=***
signType string MD5 签名算法
paySign string 签名,具体签名方案参见 小程序支付接口文档
success function 接口调用成功的回调函数
fail function 接口调用失败的回调函数
complete function 接口调用结束的回调函数(调用成功、失败都会执行)

我们可以看到调起小程序支付只需要传入五个参数,便可调起支付,而这些参数需要从服务端请求获取,所以支付主要的工作都在服务端。


小程序支付交互图

上图是官方给出的交互图,简单的可以概况为几个步骤

1.小程序触发生成订单事件,携带用户标识(在项目中我使用的是token)到服务端
2.服务端调用微信平台支付统一下单api,微信后台会返回预付单信息(prepay_id)
3.将返回的预付单信息进行组合以及再次签名,将5个参数和sign返回给微信小程序
4.小程序通过服务端返回的参数,即可直接调起微信支付,支付的结果会直接返回给小程序
5.支付成功后,微信后台会像服务器推送支付结果,服务器修改订单状态
以上五个步骤就是微信小程序的完整支付流程,下面开始通过node来实现小程序支付的服务端代码编写


一、调用微信支付统一下单API

我们直接上代码,具体可以去看微信的文档

//生成随机字符串的npm包
const stringRandom = require('string-random')
//node加密相关
const crypto = require('crypto');
//解析和生成xml文件的npm包
const Xml2js = require('xml2js');
/*方法接收2个参数,out_trade_no(服务器生成的订单id,需小于32位)
openid(当支付类型是JSAPI时需要传入,用户的openid)
total_fee(订单金额,单位为分,我们先设置默认值为1,记得改回来!)
*/
 unifiedorder(out_trade_no,openid,total_fee=1) {
        // 先将需要发送的参数创建好,然后根据参数名ASCII码从小到大排序(字典序)
        let order = {
            appid: config.getItem('wx.appId'),
            body: "一乎小程序-发布任务支付赏金",
            mch_id:config.getItem('wx.merchantId'),
            //    生成随机字符串,长度32位以内,我们使用stringRandom库生成16位随机数
            nonce_str: stringRandom(16),
            notify_url: config.getItem('siteDomain')+'/v1/task/pay/result',
            openid,
            out_trade_no:out_trade_no,
            spbill_create_ip: config.getItem('spbill_create_ip'),
            total_fee,
            trade_type: "JSAPI",
        }
        //    将参数对象转为key=value模式的字符串,用&分隔
        let stringA = obj2String(order)
        //    将生成的字符串末尾拼接上API密钥(key设置路径:微信商户平台(pay.weixin.qq.com)-->账户设置-->API安全-->密钥设置)
        let stringSignTemp = stringA + `&key=${config.getItem('wx.merchantKey')}`
        //    通过HMAC-SHA256或者MD5生成sign签名,这里我们使用md5,然后将签名加入参数对象内
        let md5 = crypto.createHash('md5')
        md5.update(stringSignTemp);
        order.sign = md5.digest('hex').toUpperCase()
        //    将参数对象专为xml格式
        const builder = new Xml2js .Builder();
        const xml = builder.buildObject(order);
        //    发送请求
        /axios.post("https://api.mch.weixin.qq.com/pay/unifiedorder",xml)
        //    由于微信服务器返回的data格式是xml,所以这里我们需要转成object
        const parser = new Xml2js.Parser();
        const xmlObj =await parser.parseStringPromise(result.data)
        if(xmlObj.xml.return_code[0]==='FAIL'){
            throw new Failed({
                msg:`支付失败,${xmlObj.xml.return_msg[0]}`,
            })
        }
        if(xmlObj.xml.result_code&&xmlObj.xml.result_code[0]==='SUCCESS'){
            let payData= {
                appId:xmlObj.xml.appid[0],
                nonceStr:xmlObj.xml.nonce_str[0],
                package:`prepay_id=${xmlObj.xml.prepay_id[0]}`,
                signType:"MD5",
                timeStamp:new Date().getTime().toString(),
                key:config.getItem('wx.merchantKey')
            }
            const StringPay = obj2String(payData)
            let payMd5 = crypto.createHash('md5')
            payMd5.update(StringPay);
            let paySign= payMd5.digest('hex').toUpperCase();
            payData.paySign = paySign;
            delete payData.key;
            //前面已经将需要的字段拼接好,将对象从方法返回,服务端可以将对象直接传回给小程序客户端
            return payData
        }else{
            throw new Failed({
                msg:`支付失败,${xmlObj.xml.err_code[0]}:${xmlObj.xml.err_code_des[0]}`,
            })
        }
        
    }

总结下遇到的几点问题

  • 生成签名的时候一定要将object的属性按照ASCII码从小到大排序
  • 返回给小程序的时间戳timeStamp,需为毫秒数,并且要是字符串格式
  • 可能会遇到签名错误的报错,这时候要耐心的去看代码哪里出了问题,商户平台的key是手动填写的32位随机生成字符串

现在统一下单已经调用成功,我们也拿到了小程序调起支付的几个参数,我们先去小程序测试下能不能支付

二、小程序调起支付

//触发生成订单事件
function (){
 //。。。省略部分代码

/*
res.data的格式如下
  res.data={
    appId: "wx14dcf5e8a4179d42"
  nonceStr: "TuXBgZrQ1JNWuLiC"
  package: "prepay_id=wx***********"
  paySign: "***********"
  signType: "MD5"
  timeStamp: "1582709049872"
}
*/  
orderModel.createOrder()
  .then(res=>{
      if(res.error_code==0){
        wx.requestPayment({
          ...res.data,
          success:re=>{
            //成功支付后执行
            console.log(re)
          },
          fail:(err)=>{
            //取消支付或者支付失败后执行
            console.log(err)
          }
        })
      }
    })
}
 

没意外的话我们已经成功调起微信支付了,如果有错误微信的报错还是挺清晰的,我们可以根据稳定一步一步去修改
接下来别忘了去接收支付成功后微信返回的支付结果

三、接收微信推送支付通知

这里接收微信推送的url是第一步调用微信支付统一下单API的notify_url字段,需要能访问
根据文档可知,微信服务器返回的数据是xml格式的,而koa-bodyparser无法解析xml,我们需要引入koa-xml-body中间件来解析xml

这里需要重点说明下,koa-xml-body官方文档上说明和koa-bodyparser是可以兼容使用的,但是引入中间件的时候,得先引用koa-xml-body再引入koa-bodyparser

const KoaBodyParser = require('koa-bodyparser');
const KoaXmlBody = require('koa-xml-body');
//注意先引入koa-xml-body
app.use(KoaXmlBody());
app.use(KoaBodyParser());

引入koa-xml-body就可以在ctx.request.body.xml里获得解析好的xml

  router.post('/pay/result',async (ctx)=>{
      const xml = ctx.request.body.xml;
      const successXml= ‘’;

      // 这里进行签名和校验返回xml数据的真实性,以防恶意调用接口
      //校验过程省略...

      if (returnCode === 'SUCCESS' && xml.result_code[0] === 'SUCCESS') {
        // 根据自己的业务需求支付成功后的操作
        //......
        //返回xml告诉微信已经收到,并且不会再重新调用此接口
           ctx.body = successXml
  }

在收到微信推送支付的成功通知后,便可以根据业务需求去修改自己订单的状态,一个微信支付完整流程也完成了

有什么不对的地方或者疑问,欢迎在评论指出

你可能感兴趣的:(微信小程序+ node koa2框架实现微信小程序支付)