腾讯高级前端工程师支招,云开发实现小程序打赏和提现|云开发实践

  • 2020 年 3 月 31 日
  • 笔记

导语

微信打赏支付和红包提现,是日常高频功能,那么基于小程序云开发,如何实现小程序的打赏支付和红包提现呢?腾讯工程师给你支招。

如何实现小程序打赏支付

1.1 小程序打赏支付功能介绍

这次的打赏功能,主要用于活跃年会晚会,功能集成在年会小程序上。互动流程如下:晚会节目开始表演时,现场观众打开小程序,会自动定位到当前节目名称,点击节目名称即可进入节目打赏界面。我们提供了免费打赏(送爱心)和付费打赏。(备注:打赏并未涉及向个体的虚拟物品打赏,而是用户向商户的付费购买。)

图1. 晚会节目打赏功能流程图

这个年会小程序的付费打赏主要是利用小程序的微信支付能力来实现的,具体如何操作呢?下面我给大家介绍一下:首先需要有一个收款类型的商户号,其次需要为小程序开通微信支付功能,具体请看下面的接入流程。

1.2 小程序打赏支付接入流程

在成功申请商户号之后,可登录商户平台[1],点击产品中心 -> 我的产品,查看已开通的支付产品,里面必须包含 JSAPI 支付才能进行小程序微信支付的开发。请注意,付款类型的商户号是没有 JSAPI 支付的,需要是收款类型的商户号才有。

图2. 商户平台的产品中心截图

在商户平台 -> API 安全,可设置 API 密钥(如原先已生成过密钥,请找管理员获取密钥),这个密钥和商户号在后续的微信支付开发需要用到。

有了商户号,小程序就可以申请开通微信支付功能了。具体操作是将小程序 AppID 与商户号进行绑定。到小程序的微信公众平台,点击微信支付 -> 商户号管理,可以查到已绑定的商户号(一个小程序可以绑定多个互不冲突、影响的商户号)。接着就可以愉快地进入小程序微信支付相关功能的开发了。

图3.在小程序微信公众平台查看已绑定的商户号

1.3 通过云开发实现小程序打赏支付

本次小程序的微信支付后台逻辑是借助云开发来实现的,其中在接收支付结果通知回调中,用到了中转服务器来做数据的中转。当然,想百分百通过云开发来实现的话,可试试云开发最近提供的通过 HTTP 访问云函数[2]

在发起微信支付之前,需要先去微信支付服务后台生成预支付交易单,获得预支付交易会话标识 prepay_id 和签名 paySign 等参数后,才能调用小程序前端微信支付接口进行支付操作。这里要注意,小程序前端微信支付接口的成功回调,是需要用户在支付成功后点击“完成”按钮才会执行,当用户未点击“完成”按钮,则不会执行成功回调。所以,我们要确认用户是否支付成功,需要在生成预支付交易单时,传递 notify_url 参数,用来接收支付结果通知回调,当用户支付成功时,微信支付服务后台会将成功状态通过 notify_url 参数填写的地址推送过来,这时我们就能够知道用户已经支付成功。

生成预支付交易单,要确保同一个商户号下的商户订单号是唯一的,同一笔交易不能多次提交。交易类型写 JSAPI,接收支付结果通知的回调地址不能携带参数,附相关文档[3]

支付结果通知回调,接收微信支付服务后台的支付结果通知时,一定要做签名验证,并校验返回的订单号、订单金额是否与数据库保存的一致,防止出现“假通知”,造成资金损失,附相关文档[4]

图4. 小程序微信支付开发流程图

详细全面日志记录,利用云开发提供的日志服务,对每个打赏交易订单做详细的日志记录,确保用户的每一笔交易都有迹可寻。

图5. 详细全面的日志记录

如何实现小程序红包提现

2.1 小程序红包提现功能介绍

晚会进入中场环节时,现场用户可通过小程序进入互动小游戏,某用户在玩游戏的过程中,只要首次分数上报成功,后台就会自动给其分配一个随机金额的红包,当用户退出互动小游戏,返回到小程序时,前端就会弹出红包,用户拆开红包,金额自动提现到账微信零钱。

图6. 晚会中场互动小游戏红包发放功能流程图

红包发放主要是利用微信支付的企业付款到零钱接口来实现的,首先需要有一个付款类型的商户号,其次需要为小程序开通微信支付功能,将小程序 AppID 与付款商户号进行绑定。

在小程序中给用户发红包,一开始我们有想过利用小程序红包[5]来实现这个功能,这样让用户有微信原生发放和领取红包的体验,但目前仅支持用户微信扫码打开小程序时进行红包领取,其他场景暂不支持,所以没有采用这种方法。

2.2 小程序红包提现接入流程 在成功申请商户号之后,可登录商户平台[6],点击产品中心 -> 我的产品,查看已开通的运营工具,里面必须包含企业付款到零钱才能进行红包提现的开发。请注意,收款类型的商户号是没有企业付款到零钱的,需要是付款类型的商户号才有。

图7.商户平台的产品中心截图

在商户平台 -> API 安全,可设置 API 密钥(如原先已生成过密钥,请找管理员获取密钥),这个密钥和商户号在后续的红包提现开发需要用到。

有了商户号,小程序就可以申请开通微信支付功能了。小程序的微信支付功能开通后,我们到小程序的微信公众平台,点击微信支付 -> 商户号管理,可以查到已绑定的商户号(一个小程序可以绑定多个互不冲突、影响的商户号)。接着就可以愉快地进入红包提现相关功能的开发了。

图8. 在小程序微信公众平台查看已绑定的商户号

2.3 通过云开发实现小程序红包提现

本次小程序的红包分配和提现的后台逻辑都是借助云开发来实现的。

红包金额生成,在小程序上线前,后台就根据付款商户号总金额和预计线下总参与人数,预先生成一批随机的红包金额,随机打乱顺序后入库 allRedPacketAmounts。

//随机的红包金额入库格式如下  {    "_id": "xxxxxx", //记录id    "red_packets": [8.88,18.88,66.66,88.88], //随机的红包金额数组    "user_openids": [] //已分配红包的用户openid数组  }

红包金额分配,每个用户只能分配一个红包,为了避免由于并发导致多个用户同时分配到同一金额的红包,我们做了红包分配的串行处理,每个用户排队分配红包。在给用户分配红包之前,先使用原子操作 db.command.addToSet()将其 openid 加入到 user_openids 数组中,并获得其 openid 在 user_openids 中的索引,在 red_packets 中获取相同索引的红包金额作为此用户分配到的红包金额。当 red_packets 数组的红包金额分配完以后,用户分配到的红包金额将为 0,用户拆开红包时,前端会展示“手慢了,红包派完了”。

//给用户分配红包金额  const _ = db.command  //原子操作将openid加入到user_openids数组中  let updated = await db.collection('allRedPacketAmounts').doc('xxxxxx')  .update({    data: {      user_openids: _.addToSet(openid)    }  })  //获取最新的红包金额分配情况  let record = (await db.collection('allRedPacketAmounts').doc('xxxxxx').get())  .data  //获取此用户openid在user_openids数组中的索引  let index = record.user_openids.indexOf(openid)  //获取此用户分配到的红包金额  let amount = record.red_packets[index] || 0

红包金额提现,小程序前端获取当前用户分配到的红包 id,并传给红包提现的云函数,进行红包金额的提现,直接到账用户的微信零钱。红包提现的云函数,使用到的核心接口是微信支付的企业付款到零钱接口,附[相关文档](https://pay.weixin.qq.com/wiki/doc/api/tools/mch_pay.php?chapter=14_2)

图9. 小程序红包分配与提现开发流程图

调用企业付款到零钱接口,需要先到微信商户平台的产品中心 -> 企业付款到零钱 -> 产品设置 -> 发起方式 -> API 接口发起中配置接口调用 IP 白名单。这里需要填一个固定的 IP 地址,而我们使用的是云函数来请求企业付款到零钱接口,而云函数默认是动态(非固定)IP 的。

下面说明如何给云函数配置固定 IP,首先进入腾讯云[7],使用此小程序的管理员微信进行扫码,然后选择此小程序进行登录。接着进入腾讯云云开发云函数控制台[8],点击要配置为固定 IP 的云函数名称,编辑网络配置,给其选择一个私有网络[9]子网[10]就可以有个固定 IP,如果没有私有网络/子网,则需新建。

图10. 编辑云函数的网络配置
//通过以下方法获取云函数IP,校验云函数IP是否是固定的  const cp = require('child_process')  const util = require('util')  exports.main = async(event, context) => {    return await util.promisify(cp.exec)('curl -k https://ip.cn')  }

请求企业付款到零钱接口,需要带上商户 API 证书,可到微信商户平台 -> 账户中心 -> 账户设置 -> API 安全中下载 API 证书。只需要使用到 API 证书压缩包里面的 apiclient_cert.p12 文件。证书文件名应改为复杂且不易猜测的文件名,存放于安全目录中,防止泄露。我们将 API 证书文件存放在红包提现的云函数目录下,安全且便于引用。

//请求企业付款到零钱接口时带上API证书示例  const mchid = 'xxxxxx'  const fs = require('fs')  const rp = require('request-promise')  let xml = (await rp({    url: 'https://api.mch.weixin.qq.com/mmpaymkttransfers/promotion/transfers',    method: 'POST',    agentOptions: {      pfx: fs.readFileSync(__dirname + '/yXZ2tfp0R9FWi3bVorza7T1nGcJKBQg8.p12'),      passphrase: mchid    },    body: 'some xml data'  })).toString('utf-8')

请求企业付款到零钱接口,所传的商户订单号(partner_trade_no)要确保其历史全局唯一性。如果商户向同一用户付款,传递了两个不同的商户订单号,则会给用户付两次款。为了避免给用户重复支付资金,造成损失,我们限定“1 用户 1 红包 1 商户订单号”,无论接口返回什么错误码(err_code),都会使用原商户订单号重试付款,这样就不会出现重复支付等资金风险。这里要特别注意一下,企业付款到零钱接口默认的调用频率为 30/s,如果接口返回错误码 FREQ_LIMIT,最好前端提示:“提现人数过多,请稍后再试”。

图11. 商户平台资金流水截图

我们利用云开发提供的日志服务,对每个用户的红包金额分配和提现状态做详细的日志记录,确保每一笔资金都有迹可寻。此外,也可登录商户平台,查询资金流水。

作者介绍

苏秋宏,腾讯高级前端工程师,就职于腾讯游戏 TGideas 团队。

参考资料

[1]商户平台: https://pay.weixin.qq.com/

[2]通过 HTTP 访问云函数: https://tencentcloudbase.github.io/2019-09-03-access-function-by-http/#%E9%80%9A%E8%BF%87-http-%E8%AE%BF%E9%97%AE%E4%BA%91%E5%87%BD%E6%95%B0

[3]相关文档: https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=9_1

[4]相关文档: https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=9_7&index=8

[5]小程序红包: https://pay.weixin.qq.com/wiki/doc/api/tools/miniprogram_hb.php?chapter=18_4&index=1

[6]商户平台: https://pay.weixin.qq.com/

[7]腾讯云: https://cloud.tencent.com/login/mp

[8]腾讯云云开发云函数控制台: https://console.cloud.tencent.com/tcb/scf

[9]私有网络: https://console.cloud.tencent.com/vpc/vpc?rid=4

[10]子网: https://console.cloud.tencent.com/vpc/subnet?rid=4

云开发(CloudBase)是一款云端一体化的产品方案 ,采用 serverless 架构,免环境搭建等运维事务 ,支持一云多端,助力快速构建小程序、Web应用、移动应用。

技术文档:https://www.cloudbase.net/

微信搜索:腾讯云云开发,获取项目最新进展