# 小程序-云开发-实现微信云支付功能

# 快速导航

# 前言

对于支付下单在小程序当中是一个非常重要的功能,在未接入云支付之前,想要实现一个支付下单的功能,借助微信官方提供的wx.requestPayment()这个接口,发起微信支付

需要获取小程序的openId,然后调起数据签名(timeStamp,nonceStr,package,signType,paySign),这些字段参数处理起来仍是有些麻烦的

但接入了云支付之后,实现一个支付功能,就相当简单容易了,免签名,直接获取小程序wx.requestPayment的所需参数

开发者无需关心证书、签名、也无需依赖第三方模块,免去了泄漏证书,支付等敏感信息的风险;还支持云函数作为微信支付进行支付和退款的回调地址,不再需要定时轮询,更加高效

您将在本文中学习到:

  • 随机生成商品订单号,订单号不能重复
  • 实现云支付的功能
小程序-云开发

# 前提条件

资质: 小程序主体开通微信支付(微信支付不支持个人小程序,需要企业账户才可以)的能力,并且已绑定商户号(绑定开通商户号)的小程序

小程序-云开发 小程序-云开发

# 开通

开通微信支付云调用,在云控制台 -> 设置 -> 全局设置中开通,如下所示

小程序-云开发

点击添加商户号后进行账号绑定,这时候绑定了微信支付的商户号管理员的微信会收到一条授权确认的模板消息

点击模板消息会弹出服务商助手小程序,确认授权之后就可以在云开发控制台看到绑定状态为“已绑定”,而JS API权限也会显示“已授权”

# 微信支付流程

  1. 在小程序端:用户在小程序端点击支付时,使用wx.cloud.callFunction调用云函数(例如:支付云函数名为questionPay),并将商品描述(body),商品订单号outTradeNo,子商户号subMchId,总金额totalFee等信息参数传递给questionPay云函数
  2. 在云函数端: 在questionPay云函数中调用统一下单接口cloud.cloudPay.unifiedOrder(),该函数接收一对象,包含的参数有,商品描述(body),商品订单号(outTradeNo),云坏境的 Id,以及需要填写结果通知回调函数(如:wechatpay),它是用来接收异步支付的结果,questionPay云函数会返回成功结果的对象中会包含payment字段(包含:appId,nonceStr,package,paySign,signType(MD5),timeStamp)参数,会唤起微信支付的界面
  3. 在小程序端wx.cloudFunctionsuccess回调函数(即调用questionPay云函数返回的对象)里调用wx.requestPayment接口发起支付请求,而从questionPay云函数返回的payment对象,参数,包含这个接口所需要的所有信息(参数),会弹出微信支付的界面
  4. 用户在小程序端支付成功,questionPay就会接收到异步的支付结果

# 微信云支付-小程序端代码

以下是小程序端的示例代码

点击即可查看小程序端代码
// miniprogram/pages/testpay/testpay.js
Page({
  /**
   * 页面的初始数据
   */
  data: {
    slideVal: 5, // slide滑块默认初始值
    showSlide: false, // slide滑块默认显示
    showInput: true, // input表单默认隐藏
    onOff: true, // 其他金额切换开关
  },

  /**
   * 生命周期函数--监听页面加载
   */
  onLoad: function(options) {},

  // 滑动滑块
  sliderChange(event) {
    const slideVal = event.detail.value;
    this.setData({
      slideVal,
    });
  },

  // 点击其他金额进行切换
  onOtherPayTap() {
    const onOff = this.data.onOff;
    if (onOff) {
      this.toggleChange(false, true);
    } else {
      this.toggleChange(true, false);
    }
    this.setData({
      onOff: !onOff,
    });
  },

  toggleChange(showSlide, showInput) {
    this.setData({
      showSlide,
      showInput,
    });
  },

  // 点击支付按钮,发起支付
  questionPay(event) {
    const { sliderVal, value } = event.detail.value;
    const showSlide = this.data.showSlide;
    const goodsnum = this._getGoodsRandomNumber();
    const subMchId = '1594460621'; // 子商户号,微信支付商户号,必填
    const body = '解锁探秘';
    const sliderPayVal = sliderVal * 100;
    const inputPayVal = value * 100;
    if (showSlide === false) {
      this._callQuestionPay(body, goodsnum, subMchId, sliderPayVal);
    } else {
      if (value) {
        this._callQuestionPay(body, goodsnum, subMchId, inputPayVal);
      } else {
        wx.showToast({
          icon: 'none',
          title: '亲,您没有输入任何数额,无法解锁哦',
          duration: 2000,
        });
      }
    }
  },

  // 请求questionPay云函数,调用支付能力
  _callQuestionPay(body, goodsnum, subMchId, payVal) {
    wx.cloud
      .callFunction({
        name: 'questionPay',
        data: {
          // 需要将data里面的参数传给questionPay云函数
          body,
          goodsnum, // 商品订单号不能重复
          subMchId, // 子商户号,微信支付商户号,必填
          payVal, // 这里必须整数,不能是小数,而且类型是number,否则就会报错
        },
      })
      .then((res) => {
        console.log(res);
        const payment = res.result.payment;
        console.log(payment); // 里面包含appId,nonceStr,package,paySign,signType,timeStamp这些支付参数
        wx.requestPayment({
          // 根据获取到的参数调用支付 API 发起支付
          ...payment, // 解构参数appId,nonceStr,package,paySign,signType,timeStamp
          success: (res) => {
            console.log('支付成功', res);
          },
          fail: (err) => {
            console.error('支付失败', err);
          },
        });
      })
      .catch((err) => {
        console.error(err);
      });
  },

  // 随机生成商品订单号,订单号不能重复
  _getGoodsRandomNumber() {
    const date = new Date(); // 当前时间
    let Year = `${date.getFullYear()}`; // 获取年份
    let Month = `${
      date.getMonth() + 1 < 10 ? `0${date.getMonth() + 1}` : date.getMonth() + 1
    }`; // 获取月
    let Day = `${date.getDate() < 10 ? `0${date.getDate()}` : date.getDate()}`; // 获取天
    let hour = `${
      date.getHours() < 10 ? `0${date.getHours()}` : date.getHours()
    }`; // 获取小时
    let min = `${
      date.getMinutes() < 10 ? `0${date.getMinutes()}` : date.getMinutes()
    }`; // 获取分钟
    let sec = `${
      date.getSeconds() < 10 ? `0${date.getSeconds()}` : date.getSeconds()
    }`; // 获取秒
    let formateDate = `${Year}${Month}${Day}${hour}${min}${sec}`; // 时间
    return `${Math.round(Math.random() * 1000)}${formateDate +
      Math.round(Math.random() * 89 + 100).toString()}`;
  },
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124

wxml 代码

<view class="wrap">
  <form bindsubmit="questionPay">
    <view hidden="{{showSlide}}">
      <slider
        bindchange="sliderChange"
        block-size="15"
        activeColor="#34bfa3"
        value="5"
        min="5"
        name="sliderVal"
        max="200"
        block-color="#34bfa3"
        show-value="{{false}}"
      />
      <view><text>{{slideVal}}</text></view>
    </view>
    <view class="show-input-wrap" hidden="{{ showInput }}">
      <text></text>
      <input
        class="show-input"
        placeholder="1.00-200.00元"
        maxlength="3"
        type="number"
        focus="{{true}}"
        confirm-hold="{{true}}"
        name="value"
        value=""
      />
    </view>
    <view class="other-pay-count" bindtap="onOtherPayTap">
      <text>{{showSlide === false? "其他金额": "取消"}}</text>
    </view>
    <button class="pay-btn" form-type="submit">支付</button>
  </form>
</view>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

wxss 代码

/* miniprogram/pages/testpay/testpay.wxss */
.wrap {
  height: auto;
  background: #fff;
  text-align: center;
  font-size: 32rpx;
}

.show-input-wrap {
  display: flex;
  justify-content: center;
  margin-top: 40rpx;
}

.other-pay-count {
  font-size: 30rpx;
  color: #34bfa3;
  margin: 40rpx 0;
}

.pay-btn {
  outline: none;
  border: none;
  background: #d43c33;
  color: #fff;
  font-size: 32rpx;
  border-radius: 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

上面完成了在小程序端的支付的功能,触发支付操作,请求云函数,并且携带一些参数给该云函数,在云函数返回成功的结果中,拿到返回的参数,然后调用wx.requestPayment唤起微信支付.

注意

  • subMchId:子商户号,微信支付商户号,必填
  • body: 商品描述信息也必须填
  • inputPayVal: 支付默认金额值是0.01,所以需要做一下转化,必须是整型(int),不能是小数
  • goodsnum: 随机生成商品订单号,订单号不能重复(主要解决支付第一次后,无法在重复支付的问题,将订单号,设置为随机数就可以了的)

上面的...payment,其实是对象的解构,包含了如下参数,你不用解构也是可以的,挨个的取到对象value,传入wx.requestPayment中也是可以的

{
  appId: 'wx9fbad659d5dfgdc9bc'; // 是以你自己小程序appId为准
  nonceStr: 'nWQePZDsUCaKgNvP';
  package: 'prepay_id=wx30090731253190aa666629200';
  paySign: 'BFB4834C7969F661962dgdf96E1C37';
  signType: 'MD5';
  timeStamp: '1596071251';
}
1
2
3
4
5
6
7
8

...payment

wx.requestPayment({
  // 根据获取到的参数调用支付 API 发起支付
  ...payment, // 解构参数appId,nonceStr,package,paySign,ignType,timeStamp
  success: (res) => {
    console.log('支付成功', res);
  },
  fail: (err) => {
    console.error('支付失败', err);
  },
});
1
2
3
4
5
6
7
8
9
10

等价于

const payment = res.result.payment;
const { appId, nonceStr, paySign, signType, timeStamp } = payment;
wx.requestPayment({
  // 根据获取到的参数调用支付 API 发起支付
  // ...payment,       // 等价于下面
  appId,
  nonceStr,
  package: payment.package, // 这个package无法解构,真是醉了的
  paySign,
  signType,
  timeStamp,
  success: (res) => {
    console.log('支付成功', res);
  },
  fail: (err) => {
    console.error('支付失败', err);
  },
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 云函数端支付代码

在云函数根目录文件夹cloudfunctions右键,选择“新建 Nodejs 云函数”,新建一个云函数questionPay,然后再在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.unifiedOrder({
    body: event.body, // 商品描述,必填
    outTradeNo: event.goodsnum, // 商户订单号,必填,不能重复
    spbillCreateIp: '127.0.0.1', // 终端IP,必填
    subMchId: event.subMchId, // 子商户号,微信支付商户号,必填
    totalFee: event.payVal, // 总金额,必填
    envId: 'xxx', // 结果通知回调云函数环境,你自己小程序的坏境id
    functionName: 'wechatpay', // 结果通知回调云函数名,非必填参数,即使为空,也不影响支付,但是官方文档里写的是必填参数,表示已醉
  });
  return res;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

利用了cloud.cloudPay.unifiedOrder云支付接口,接收小程序端请求该云函数的参数,最终将结果返回

注意

  • body为你的商家名(店名)-销售商品的类名
  • outTradeNo: 是商户订单号,32 个字符内,只能是数字、大小写字母_-,代码中是用时间戳随机数解决的
  • subMchId:你的商户 ID 或子商户 ID,填写云开发控制台- 设置- 全局设置- 微信支付配置里的商户号也可以
  • totalFee: 是支付的金额,单位是分,注意在小程序要做一下单位转化
  • envId: 是你的结果通知回调云函数所在的环境 ID
  • functionName: 结果通知云函数的名称(可以自定义)

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

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

小程序-云开发

# 结语

你会发现用云开发的云支付实现微信支付功能,非常便捷,没有几行代码,你只需要专注自己的业务逻辑开发就可以了的,无需关心证书、签名、也无需依赖第三方模块,免去了泄漏证书,支付等敏感信息的风险

简直是太方便了的

# 相关文档

关注公众号

一个走心,有温度的号,同千万同行一起交流学习

加作者微信

扫二维码 备注 【加群】

扫码易购

福利推荐