-

微信支付云调用(云支付),可以免鉴权快速调用微信支付的开放能力,开发者无需关心证书、签名、也无需依赖第三方模块,免去了泄漏证书,支付等敏感信息的风险;还支持云函数作为微信支付进行支付和退款的回调地址,不再需要定时轮询,更加高效。只需在开发者工具1.02.2005111 (2020年5月11日版)的云开发控制台绑定微信支付商户号,在绑定完成后可在云开发中原生接入微信支付。

一、云支付快速入门

1、开通微信支付云调用

要开通微信支付云调用,首先需要小程序已经开通了微信支付,而微信支付是不支持个人小程序的,需要企业账户才行,其次需要小程序已经绑定了商户号。满足这两个条件之后,我们可以在云开发控制台(注意开发者工具的版本)- 设置- 全局设置中开通。

点击添加商户号后进行账号绑定,这时候绑定了微信支付的商户号管理员的微信会收到一条授权确认的模板消息,点击模板消息会弹出服务商助手小程序,确认授权之后就可以在云开发控制台看到绑定状态为“已绑定”,而JS API权限也会显示“已授权”。

jsapi和api退款权限授权,需要前往微信支付商户平台-产品中心-我的授权产品中进行确认授权完成授权后才可以调用微信支付相关接口能力。如果你在你的产品中心看不到我的授权产品,可以点击链接:授权产品

2、微信支付流程说明

用微信支付云调用来实现完整的支付功能,大体上会经过以下4个步骤(后面在代码的写法上有些步骤会整合到一起):

  • 1、用户在小程序端点击支付时使用wx.cloud.callFunction调用云函数(比如云函数名为pay),并将商品名称、商品价格等信息传递给pay云函数;

  • 2、在pay云函数中调用统一下单接口CloudPay.unifiedOrder(),参数包括接收的商品信息、云函数环境id,以及需要填写结果通知回调函数(比如函数名为paynotice)用来接收异步支付结果;pay云函数会返回的成功结果对象中会包含payment字段;

  • 3、在小程序端wx.cloud.callFunction的success回调函数(也就是拿到云函数返回的对象)里调用wx.requestPayment接口发起支付,而从pay云函数返回的payment对象(字段)就包含这个接口所需要的所有信息(参数);这时会弹出微信支付的界面;

  • 4、用户在小程序端支付成功,paynotice就会接受到异步的支付结果,我们可以在paynotice云函数里进行发送订阅消息以及将支付成功的信息更新到数据库等操作

3、微信支付的简单案例

我们可以在小程序的wxml页面比如pay.wxml页面,点击某个button组件时,通过事件处理函数比如callPay,来调用pay云函数,代码如下:

<button bindtap ="callPay">发起支付</button>

然后再在pay.js里输入事件处理函数callPay,调用的支付云函数名称为pay(名称任意),注意成功的回调函数的写法如下,这里把支付流程的第1步和第3步整到了一起:

>尤其是const payment = res.result.paymentwx.requestPayment({...payment})不要改(仅对小白用户而言)。因为有不少小白用户啥基础也没有,但是对微信支付比较感兴趣,所以本节内容,会介绍的比较琐碎一些。

callPay(){
  wx.cloud.callFunction({
    name: 'pay',  //云函数的名称,在后面我们会教大家怎么建
    success: res => {
      console.log(res)
      const payment = res.result.payment
      wx.requestPayment({
        ...payment,
        success (res) {
          console.log('支付成功', res) //为方便,只打印结果,如果要写支付成功之后的处理函数,写在这后面
        },
        fail (err) {
          console.error('支付失败', err) 
          //支付失败之后的处理函数,写在这后面
        }
      })
    },
    fail: console.error,
  })
},

然后再在云函数根目录文件夹cloudfunctions右键,选择“新建Nodejs云函数”,新建一个云函数pay,然后再在index.js里输入以下代码,然后进行一些修改(注意参数名称不要改,大小写也要原样写,不懂你就复制):

  • body为你的商家名(店名)-销售商品的类名,代码里有参考;

  • outTradeNo是商户订单号,32个字符内,只能是数字、大小写字母_-,如果你是在调试学习,注意每次都改一下这个,免得重复;

  • subMchId你的商户ID或子商户ID,填写云开发控制台- 设置- 全局设置- 微信支付配置里的商户号也可以;

  • totalFee是支付的金额,单位是分,填写100,就是一块钱,注意这个是数值格式,不要写成了字符串格式(不要加单引号或者双引号);

  • envId是你的结果通知回调云函数所在的环境ID,functionName结果通知云函数的名称(可以自定义);可以在云开发控制台- 设置- 环境设置里看到,注意是环境ID,不是环境名称,最好直接复制过来;

  • 其他地方,不懂的话,不要改,直接copy完事

修改完之后,点击pay云函数目录下的index.js,然后右键选择“云函数增量上传:更新文件”或者右键云函数根目录文件夹cloudfunctions,选择“上传并部署:云端安装依赖(不上传Node_modules)

const cloud = require('wx-server-sdk')
cloud.init({
  env: cloud.DYNAMIC_CURRENT_ENV
})


exports.main = async (event, context) => {
  const res = await cloud.cloudPay.unifiedOrder({
    "body": "HackWeek案例-教学费用",
    "outTradeNo" : "122775224070323234368128", //不能重复,否则报错
    "spbillCreateIp" : "127.0.0.1", //就是这个值,不要改
    "subMchId" : "1520057521",  //你的商户ID或子商户ID
    "totalFee" : 100,  //单位为分
    "envId": "xly-xrlur",  //你的云开发环境ID
    "functionName": "paysuc",  //支付成功的回调云函数,先可以随便写,比如写paysuc,后面会教你怎么建
    "nonceStr":"F8B31E62AD42045DFB4F2",  //随便弄的32位字符串,建议自己生成
    "tradeType":"JSAPI"   //默认是JSAPI
  })
  return res
}

然后就可以在开发者工具的模拟器里点击"发起支付"的按钮了,这时会弹出支付的二维码,扫码支付就可以了;也可以使用预览或真机调试。

>这里的outTradeNo是自己生成的,我们可以使用时间戳Date.now().toString(),或者加随机数Date.now().toString()+Math.floor(Math.random()*1000).toString()等来处理,而nonceStr是32位以内的字符串,我们可以使用用户的openid和时间戳拼接而成(你也可以使用其他方法),比如下面是用户的openid先替换掉-字符,然后将字母都大写,最后加上时间戳的字符串"oUL-m5FuRmuVmxvbYOGuXbuEDsn8".replace('-','').toUpperCase()+Date.now().toString()

二、查询订单与申请退款

我们可以在云函数里调用cloudPay.queryOrder()来查询订单的支付状态,以及调用cloudPay.refund()来对已经支付成功的订单发起退款。下面的代码只是查询订单与申请退款简单的demo,真正要在实际开发中使用这些接口,都是需要结合云开发数据库的,尤其是申请退款开发时一定要慎重对待。

使用开发者工具新建一个queryorder的云函数,然后在index.js里输入以下代码,将云函数部署到云端之后,调用该云函数就能查询订单信息了:

const cloud = require('wx-server-sdk')
cloud.init({
  env: cloud.DYNAMIC_CURRENT_ENV,
})
exports.main = async(event, context) => {
  const res = await cloud.cloudPay.queryOrder({
    "sub_mch_id":"1520057521",
    //商户订单号,需是云支付成功交易的订单号
    "out_trade_no":"122775224070323234368128",
    //微信订单号可以不必写
    // "transaction_id":"4200000530202005179572346100",
    //任意的32位字符
    "nonce_str":"C380BEC2BFD727A4B6845133519F3AD6"
  })
  return res
}

使用开发者工具新建一个refundorder的云函数,然后在index.js里输入以下代码,退款的金额少于交易的金额时,可以实现部分退款;注意调用该云函数,退款会直接原路返回给用户,因此一定要有管理员审核或只能管理员来调用该接口:

const cloud = require('wx-server-sdk')
cloud.init({
  env: cloud.DYNAMIC_CURRENT_ENV,
})
exports.main = async(event, context) => {
  const res = await cloud.cloudPay.refund({
    "sub_mch_id":"1520057521",
    "nonce_str":"5K8264ILTKCH16CQ2502SI8ZNMTM67VS",
    "out_trade_no":"122775224070323234368128",//商户订单号,需是云支付成功交易的订单号
    "out_refund_no":"122775224070323234368128001",//退款单号,可以自定义,建议与订单号相关联
    "total_fee":100,
    "refund_fee":20,
  })
  return res
}

三、支付成功的回调函数

在前面发起支付的云函数里我们写过一个参数functionName结果通知云函数paysuc,paysuc云函数在订单支付成功之后才会被调用。我们可以在支付成功的回调函数里处理一些任务,比如把订单支付的重要信息存储到数据库、给用户发送支付成功的订阅消息、以及获取用户的UnionID等等。要处理这些任务,首先需要了解订单支付成功之后,paysuc云函数会接收到哪些数据。我们可以打印paysuc云函数的event对象,可以了解到event对象里包含类似于如下结构的信息,这些都是我们在paysuc云函数处理任务的关键:

"appid": "wxd2********65e", 
"bankType": "OTHERS",
"cashFee": 200,
"feeType": "CNY",
"isSubscribe": "N",
"mchId": "1800008281",
"nonceStr": "F8B31E62AD42045DFB4F2",
"openid": "oPoo44....t8gCOUKSncFI",
"outTradeNo": "1589720989221",
"resultCode": "SUCCESS",
"returnCode": "SUCCESS",
"subAppid": "wxda99a********57046",
"subIsSubscribe": "N",
"subMchId": "1520057521",
"subOpenid": "oUL********GuXbuEDsn8",
"timeEnd": "20200517211001",
"totalFee": 2,
"tradeType": "JSAPI",
"transactionId": "42000********178943055343",
"userInfo": {
    "appId": "wxd********046",
    "openId": "oUL-m5F********GuXbuEDsn8"
}

要发送订阅消息,首先我们需要去申请订单支付成功的订阅消息模板,比如模板如下,我们需要注意订阅消息里每一个属性对应的具体的格式,以及格式的具体要求,比如支付金额以及支付时间的格式:

商品名称{{thing6.DATA}}
支付金额{{amount7.DATA}}
订单号{{character_string9.DATA}}
支付时间{{date10.DATA}}
温馨提示{{thing5.DATA}}

要发订阅消息,需要调用接口wx.requestSubscribeMessage来获取用户授权以及要有相应的授权次数,在前面我们已经了解到只有用户发生点击行为或者发起支付回调后,才可以调起订阅消息界面,因此我们可以在上面的发起支付的回调函数里直接调用这个接口:

callPay(){
  wx.cloud.callFunction({
    name: 'pay',  //云函数的名称,在后面我们会教大家怎么建
    success: res => {
      console.log(res)
      const payment = res.result.payment
      wx.requestPayment({
        ...payment,
        success (res) {
          console.log('支付成功', res) //为方便,只打印结果,如果要写支付成功之后的处理函数,写在这后面
          this.subscribeMessage() //调用subscribeMessage()函数,如果你不是箭头函数,注意this指代的对象
        },
      })
    },
  })
},
subscribeMessage() {
  wx.requestSubscribeMessage({
    tmplIds: [
      //订阅消息模板ID,一次可以写三个,可以是同款通知、到货通知、新品上新通知等,通常用户不会拒绝,多写几个就能获取更多授权
      "p5ypZiN4TcZrzke4Q_MBB1qri33rb80z-tb16Sg-Kpg",
    ],
    success(res) {
      console.log("订阅消息API调用成功:",res)
    },
    fail(res) {
      console.log("订阅消息API调用失败:",res)
    }
  })
},

然后在paysuc云函数的index.js里写如下代码,订阅消息所需的全部参数都是来自于event对象,我们只需要稍加修改格式即可。

const cloud = require('wx-server-sdk')
cloud.init({
  env: cloud.DYNAMIC_CURRENT_ENV
})


exports.main = async (event, context) => {
  const {cashFee,subOpenid,outTradeNo,timeEnd} = event
  try {
    const result = await cloud.openapi.subscribeMessage.send({
      touser: subOpenid,
      page: 'index',
      templateId: "p5ypZiN4TcZrzke4Q_MBB1qri33rb80z-tb16Sg-Kpg",
      data: {
        "thing6": {
          "value": '零基础小程序云开发训练营'
        },
        "amount7": {
          "value": cashFee/100 +'元'
        },
        "character_string9": {
          "value": outTradeNo
        },
        "date10": {
          "value": timeEnd.slice(0,4)+'年'+timeEnd.slice(4,6)+'月'+timeEnd.slice(6,8)+'日'+' '+timeEnd.slice(8,10)+':'+timeEnd.slice(10,12)
        },
        "thing5": {
          "value": "多谢您的支持哦~爱你哦~"
        }
    }
    })
    return result
  } catch (err) {
    console.log(err)
    return err
  }
}