微信小程序+ 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><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>’;

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

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

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

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,941评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,397评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,345评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,851评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,868评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,688评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,414评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,319评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,775评论 1 315
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,945评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,096评论 1 350
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,789评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,437评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,993评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,107评论 1 271
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,308评论 3 372
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,037评论 2 355

推荐阅读更多精彩内容