7.4. 微信支付¶
7.4.1. 功能概述¶
本文档主要介绍微信支付功能,您可以通过本文示例体验体验下单、支付、退款等支付类的功能。
7.4.2. 体验功能¶
7.4.3. 体验 DEMO¶
本章的案例代码,请参考代码 tcb-demo-basic 。
7.4.4. DEMO 接入流程¶
代码下载完成后,请按照以下步骤操作:
在云开发中创建好至少一个环境。
在 app.js 文件中,如果使用默认环境,则没有改动。如果需要使用非默认环境, 则需要在 wx.cloud.init 中传入环境 ID。如果使用非默认环境,在云函数的 cloud.init 处,也需要补充环境 ID。如下图所示:
示例代码如下:
wx.cloud.init({ env: 'xxxx', // 环境 id traceUser: true });
在云函数根目录 cloud/functions 中找到函数 pay,在 config 目录中新建 index.js, 配置环境 ID、微信支付商户号、微信支付商户密钥、和双向证书中的 apiclient_cert.p12, 该证书放也放在 config 目录中,用 npm 安装两个云函数的依赖,并上传该函数。
找到云函数 pay-message,在 config 目录中新建 index.js。
配置小程序 AppSecret 和模板 ID(选择合适的模板,合适的字段即可),用 npm 安装两个云函数的依赖,然后上传该函数。
在云开发数据库中,新建两个 colleciton,第一个名为 goods,权限设置为 所有用户可读, 仅创建者及管理员可写,而另一个名为 orders,权限保持不变。
在云开发存储中,上传 cloud/storage 中的图片到 goods 目录下。
然后复制文件 fileID,并填入 cloud/database/goods.json 中。
在 collections goods 中,导入 cloud/database/goods.json。
7.4.5. 源码介绍¶
7.4.5.1. 准备工作¶
微信支付,最主要的包括订单创建、发起支付、订单查询、申请退款、退款查询等等,本节主要介绍如何基于云开发实现微信支付。
开发前,建议先阅读以下相关的文档:微信支付小程序文档 和 微信小程序支持接口。
7.4.5.2. 订单创建¶
在 DEMO 里,我们有三个页面,分别是 pay-list(商品列表), pay-order (订单列表) 和 pay-result (订单详情)。 我们在商品列表里,直接单击【下单】,就会帮我们调用云函数 pay 进行订单的创建。 云函数 pay 是一个复合的云函数, 通过 switch 对支付的不同操作类型进行分支处理,创建订单的类型是 unifiedorder。
本节截选了部分云函数 pay 的代码。我们借助 wx-js-utils 封装好微信支付的能力。 初始化的时候,我们要填入小程序 appId, 微信支付商店号, 微信支付密钥等参数。
注解
由于某些敏感操作。例如,申请退款需要使用 证书,因此我们也需要带上证书并进行读取, 但对于云函数来说,由于服务器里有内置 CA 证书,因此不需要填写 caFileContent 参数。
请参考微信支付 统一下单 文档,先拼凑好请求参数。
注解
notify_url 参数,由于目前云函数还没支持 HTTP 触发器和公开的外网地址,因此这里可以先随便填一个, 可以是自有业务的地址。支付成功后,建议由小程序端主动去触发订单查询并更新状态。
示例代码如下:
const {
WXPay,
WXPayConstants,
WXPayUtil
} = require('wx-js-utils');
// 此处省略其它代码
const pay = new WXPay({
appId: APPID, // 小程序 appID
mchId: MCHID, // 微信支付商户号
key: KEY, // 微信支付密钥
certFileContent: CERT_FILE_CONTENT, // 微信支付证书
timeout: TIMEOUT, // 超时时间
signType: WXPayConstants.SIGN_TYPE_MD5, // 加密方式
useSandbox: false // 不使用沙箱环境
});
// 此处省略其它代码
// 拼凑订单参数
const curTime = Date.now();
const tradeNo = `${goodId}-${curTime}`; // 自这义的trade number
const body = good.name; // 商品名作为 body 内容
const spbill_create_ip = ip.address() || '127.0.0.1'; // 获取服务器的 ip 地址
const notify_url = 'http://www.qq.com'; //'云函数暂时没有外网地址和HTTP触发起,暂时随便填个地址。建议填自己站点的域名'
const total_fee = good.price; // 商品的价格
const time_stamp = '' + Math.ceil(Date.now() / 1000); // 订单创建的时间
const out_trade_no = `${tradeNo}`;
const sign_type = WXPayConstants.SIGN_TYPE_MD5; // 加密方式
let orderParam = {
body,
spbill_create_ip,
notify_url,
out_trade_no,
total_fee,
openid: OPENID,
trade_type: 'JSAPI',
timeStamp: time_stamp,
};
// 在微信支付服务端生成该订单
const {
return_code,
...restData
} = await pay.unifiedOrder(orderParam);
7.4.5.3. 发起支付与订单查询¶
发起支付的过程,除了后台接口,还涉及到小程序的 wx.requestPayment 接口。 下面代码截选自 pay-result 订单详情页面,进入页面后,会先通过 getOrder 方法查询订单并获取订单所有数据, 用户单击支付后,会根据订单的数据拼凑参数。然后调用 wx.requestPayment 发起支付, 再去请求云函数 pay,在 payorder 处理分支中,进行订单信息的更新。
// 获取订单详情
async getOrder() {
const {result} = await wx.cloud.callFunction({
name: 'pay',
data: {
type: 'orderquery',
data: {
out_trade_no: this.data.out_trade_no
}
}
})
const data = result.data || {}
this.setData({
order: data
}, () => {
// 如果状态是退款中,则每次进来都会查询一下退款情况
if (data.status === 3) {
this.queryRefund()
}
})
},
// 发起支付
pay() {
const orderQuery = this.data.order
const out_trade_no = this.data.out_trade_no
const {
time_stamp,
nonce_str,
sign,
prepay_id,
body,
total_fee
} = orderQuery
wx.requestPayment({
timeStamp: time_stamp,
nonceStr: nonce_str,
package: `prepay_id=${prepay_id}`,
signType: 'MD5',
paySign: sign,
success: async () => {
wx.showLoading({
title: '正在支付',
})
wx.showToast({
title: '支付成功',
icon: 'success',
duration: 1500,
success: async () => {
this.getOrder()
await wx.cloud.callFunction({
name: 'pay',
data: {
type: 'payorder',
data: {
body,
prepay_id,
out_trade_no,
total_fee
}
}
})
wx.hideLoading()
}
})
},
fail() {}
})
},
通过云函数 pay 的 payorder 处理分支,主要就是通过 pay.orderQuery 查询微信服务端,如果支付成功, 就会更新订单数据,并取发送一条模板消息告诉用户支付成功。详情请参考 模板消息文档。
// 进行微信支付及更新订单状态
case 'payorder': {
const {
out_trade_no,
prepay_id,
body,
total_fee
} = data
const {return_code, ...restData} = await pay.orderQuery({
out_trade_no
})
if (restData.trade_state === 'SUCCESS') {
await orderCollection
.where({out_trade_no})
.update({
data: {
status: 1,
trade_state: restData.trade_state,
trade_state_desc: restData.trade_state_desc
}
})
// console.log('======restData======');
// console.log(restData);
const curDate = new Date()
const time = `${curDate.getFullYear()}-${curDate.getMonth() + 1}-${curDate.getDate()} ${curDate.getHours()}:${curDate.getMinutes()}:${curDate.getSeconds()}`
try {
const messageResult = await cloud.callFunction({
name: 'pay-message',
data: {
formId: prepay_id,
openId: OPENID,
appId: APPID,
page: `pages/pay-result/index?id=${out_trade_no}`, // pages 前面不允许带有 "/",否则识别为非法参数
data: {
keyword1: {
value: out_trade_no // 订单号
},
keyword2: {
value: body // 物品名称
},
keyword3: {
value: time// 支付时间
},
keyword4: {
value: (total_fee / 100) + '元' // 支付金额
}
}
}
})
console.log('=======message=========')
console.log(messageResult)
} catch (e) {
console.log('===========')
console.log(e)
}
}
return {
code: return_code === 'SUCCESS' ? 0 : 1,
data: restData
}
}
7.4.5.4. 申请退款¶
申请退款是针对已经支付过的订单。申请退款属于敏感操作,需要使用双向证书。 通过云函数 pay 的 refund 操作分支申请退款,退款不是马上生效的,可能会有一定的延迟。 示例代码如下:
// 申请退款,但不会马上退
async refund() {
wx.showLoading({
title: '正在申请退款',
})
const result = await wx.cloud.callFunction({
name: 'pay',
data: {
type: 'refund',
data: {
out_trade_no: this.data.out_trade_no
}
}
})
wx.hideLoading()
if (!result.code) {
const order = this.data.order
order.trade_state_desc = '正在退款'
order.status = 3
order.trade_state = 'REFUNDING'
this.setData({
order
})
} else {
this.showToast({
title: result.message,
icon: 'none'
})
}
},
在云函数中,主要需要的参数是 out_trade_no 和 total_fee, 分别作为退款的订单号和退款金额传给 pay.refund 方法进行退款申请即可。示例代码如下:
// 申请退款
case 'refund': {
const {out_trade_no} = data
const orders = await orderCollection.where({out_trade_no}).get()
console.log(orders)
if (!orders.data.length) {
return {
code: 1,
message: '找不到订单'
}
}
const order = orders.data[0]
const {
total_fee,
} = order
const result = await pay.refund({
out_trade_no,
total_fee,
out_refund_no: out_trade_no,
refund_fee: total_fee
})
const {return_code} = result
if (return_code === 'SUCCESS') {
try {
await orderCollection.where({out_trade_no}).update({
data: {
trade_state: 'REFUNDING',
trade_state_desc: '正在退款',
status: 3
}
})
} catch (e) {
return {
code: 1,
mesasge: e.message
}
}
} else {
return {
code: 1,
mesasge: '退款失败,请重试'
}
}
return {
code: 0,
data: {}
}
}
7.4.5.5. 退款查询¶
申请退款也有 notify_url 回调参数的,不过是可选项。但由于不支持 HTTP 触发和公网地址, 因此建议您在小程序端主动触发更新订单信息。示例代码如下:
// 查询退款情况
async queryRefund() {
const {result} = await wx.cloud.callFunction({
name: 'pay',
data: {
type: 'queryrefund',
data: {
out_trade_no: this.data.out_trade_no
}
}
})
// 退款成功,则更新本地数据状态
if (!result.code && result.data) {
const data = result.data
const order = this.data.order
order.trade_state_desc = data.trade_state_desc
order.status = data.status
order.trade_state = data.trade_state
this.setData({
order
})
}
},
在云函数 pay 中查询并更新退款订单。示例代码如下:
// 查询退款情况
case 'queryrefund': {
const {out_trade_no} = data
const result = await pay.refundQuery({
out_trade_no
})
const {refund_status_0, return_code} = result
if (return_code === 'SUCCESS' && refund_status_0 === 'SUCCESS') {
try {
await orderCollection.where({out_trade_no}).update({
data: {
trade_state: 'REFUNDED',
trade_state_desc: '已退款',
status: 4
}
})
return {
code: 0,
data: {
trade_state: 'REFUNDED',
trade_state_desc: '已退款',
status: 4
}
}
} catch (e) {
return {
code: 0
}
}
} else {
return {
code: 0
}
}
// eslint-disable-next-line no-unreachable
return {
code: 0
}
}