微信小游戏支付教程

微信小游戏支付教程

背景

最近在做微信小游戏支付相关的东西,包括虚拟支付-道具直购,通过客服消息拉起jsapi支付,现在把流程走通了,但是其中遇到了很多坑以及之前没有预料到的各种细节问题,所以记录下来,方便以后查阅,也给有相关需求的朋友提供参考。

这里正好也感谢一下这篇博客的作者,博客:微信小游戏sdk接入支付和登录,解决了wx小游戏内不支持ios支付的痛点
我在最开始收到做这个工作的需求时,是一脸懵逼的,特别是iOS端由于需要绕过苹果的限制,只能走客服消息来引导用户点击拉起jsapi支付,整个流程在当时的我看来非常复杂。也正是看了这篇帖子之后,才恍然大悟,原来还可以这样子玩。

需求

  1. Android端直接使用微信支付
  2. iOS端使用客服消息拉起jsapi支付

技术选型

  1. Android端有游戏币充值道具直购两种
  • 按照微信官方(其实腾讯系都是这个逻辑,也包括谷歌商店也是类似)分别对应了两种使用场景,分别是游戏内有所谓的一级货币(托管货币),我以QQ游戏大厅举例来说,一级货币就是Q币。而欢乐豆就是二级货币。相对应的在市面上其他游戏来说,分别就对应钻石和金币。一般来说,一级货币(钻石)是只能通过充值获得,而二级货币(金币)是可以在游戏内通过各种任务直接获得的。
    为什么一级货币又被称为托管货币,这是因为,按照设计一级货币的数量是由微信服务器来管理,而二级货币则是由游戏方来管理。
    举例说明:
    玩家充值之后,钻石(一级货币)的增加也是由微信后台直接完成,而玩家在游戏内使用钻石购买道具,比如:屠龙刀,花了6钻石,这6个钻石的扣除,需要由游戏服务器与微信后台通信,告知微信扣除6钻石,扣除成功之后,游戏服务器再给玩家发放道具屠龙刀。

  • 而道具直购,则不存在托管货币一说,这个更接近于传统渠道,比如:OV华米这些厂商提供的服务,玩家买屠龙刀需要6元,那就充6元,充成功之后,游戏方直接发放屠龙刀。玩家买6个钻石,需要6元,则充值6元,游戏方直接发放6个钻石。玩家有多少钻石,有多少游戏币,有哪些道具,都是由游戏方来管理,跟微信完全没关系,微信只是提供了一个支付通道而已。

相比之下,游戏币充值比道具直购要复杂不少,需要去微信查询余额(一级货币数量),玩家买道具时,需要去微信扣除余额,流程一多,这其中的细节问题就会更多,问题也更多。所以,如果不是有特定的需求,不建议使用游戏币充值,而是使用道具直购。
微信官方关于支付的文档:https://developers.weixin.qq.com/minigame/dev/guide/open-ability/virtual-payment/guide.html

  1. iOS端微信官方直接不支持拉起道具直购,市面上其他厂商的方案都是。玩家点击购买商品时,游戏跳转到客服聊天窗口,由游戏后台通过微信的接口,直接给玩家发送一条客服消息,玩家点击客服消息之后,会通过微信的weiview拉起微信支付。这个方案实现的前提是,微信内的webview直接带有微信的支付api环境,可以调用。而且厂商必须要有jsapi支付的参数。

流程梳理及文档整理

上面说完了大概的支付流程,但是这些都只是玩家能够看到的一些流程,游戏服务器在这背后还有很多事情要做。

Android道具直购流程

本文讨论的是在微信小游戏环境下的支付流程,为了简化描述,下文中说到的小游戏,都特指微信小游戏。

  1. 小游戏客户端调用用户登录接口,获取用户登录凭证(code)
    登录
  2. 客户端将code传递给游戏服务器,游戏服务器调用微信的接口,获取用户信息 - openid,session_key 等
    code2Session
  3. 玩家点击购买商品时,游戏服务器需要生成一笔订单,同时需要将订单号,以及客户端拉起支付所需要的所有参数返回给客户端。
  • 道具直购指南

  • 客户端拉起道具支付文档
    游戏服务端需要将signData、paySig、signature返回给客户端,供客户端使用拉起支付。其中paySig算法文档中写的很清楚,主要是signature(用户态签名),文档中提到的rawData很容易造成混淆,因为在客户端的wx.getUserInfo接口中返回的内容中也有一个rawData字段。我一开始用这个radData来签用户态签名,一直报错签名不对。这里需要使用的rawData就是signData。

  1. 客户端拉起支付,进行支付之后,游戏服需要接收微信的回调通知,微信支付使用的是 支付类事件订阅 ,它其实就是 消息推送 服务中的一种,这里我理解的是消息推送服务,其实也包括了客服消息的推送。
    道具发货推送需要去事件订阅中配置开启,这其中需要游戏服写两个接口,其实URL是同一个,只是请求方式不同,一个是GET请求的,一个是POST请求的。GET请求的接口,是微信会不定时来请求,确认你的接口是OK的,也正常验签了的。POST请求是真正的消息推送接口,用来处理消息、发货等的。
    微信的工具库wechatpay-java,其中包括了各支付方式的订单操作,建议优先使用,具体使用参考仓库文档。

iOS通过客服消息进行jsapi支付

  1. 小游戏客户端向游戏服请求下单,游戏服调用预下单接口进行下单,拿到预下单号,根据自己的规则构造一个支付参数串,同时返回给客户端。
  2. 小游戏客户端调用进入客服会话接口,进入客服会话后,正常情况下,游戏服会收到微信的通知,有用户进入了会话,引导玩家将支付参数串发送给客服。
  3. 参考接收消息和事件文档,处理客服消息。
  4. 游戏服收到客服消息后,取出支付参数串,根据自己的规则取出支付信息,包括商品id,价格,订单号,等等。然后发送客服消息给玩家,引导玩家点击客服消息,跳转网页进行支付。这里的客服消息使用link类型,传递的url是游戏服自己的一个接口地址(GET请求),这个接口在收到请求时,返回一个html页面给玩家,里面会调用微信的jsapi进行支付。
  5. 玩家点击客服消息,微信会自动打开网页,加载link中的url,其实拿到的就是html页面。里面自动就调起微信支付。支付的参数,自己在代码中动态拼接即可。html内容可以参考下面,这个微信的文档上也有:
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
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>微信支付</title>
</head>
<body>
<script th:inline="javascript">
/*<![CDATA[*/
function onBridgeReady() {
const payResponse = /*[[${payResponse}]]*/ {};

WeixinJSBridge.invoke('getBrandWCPayRequest', {
"appId": payResponse.appId,
"timeStamp": payResponse.timeStamp,
"nonceStr": payResponse.nonceStr,
"package": payResponse.packageStr,
"signType": payResponse.signType,
"paySign": payResponse.paySign
}, function(res) {
console.log(res.err_msg);
if (res.err_msg == "get_brand_wcpay_request:ok") { // 支付成功
document.write("payment success");
WeixinJSBridge.call('closeWindow');
}
if (res.err_msg == "get_brand_wcpay_request:fail") { // 支付失败
document.write("payment fail");
WeixinJSBridge.call('closeWindow');
}
if (res.err_msg == "get_brand_wcpay_request:cancel") { // 支付取消
document.write("payment cancel");
WeixinJSBridge.call('closeWindow');
}
});
}

if (typeof WeixinJSBridge == "undefined") {
if (document.addEventListener) {
document.addEventListener('WeixinJSBridgeReady', onBridgeReady, false);
} else if (document.attachEvent) {
document.attachEvent('WeixinJSBridgeReady', onBridgeReady);
document.attachEvent('onWeixinJSBridgeReady', onBridgeReady);
}
} else {
onBridgeReady();
}
/*]]>*/
</script>
</body>
</html>
  1. 处理回调通知,jsapi支付成功回调通知

这里有个优化的点,因为玩家进入客服会话后,微信会通知游戏服,其中的数据包括了玩家的openid,如果游戏服在预下单后将支付参数串(其实也就相当于支付相关的参数,甚至更简单点就把订单号和openid关联起来)缓存在redis也好,或者直接落地数据库中,那么进入会话之后可以判断openid是否存在可用支付参数,直接就可以构造消息,发送给玩家,省去玩家发送消息的步骤。

总结

我在做的过程中,也是各种踩坑,因为微信的文档比较分散,有些地方说的也是不清不楚比较模糊,也有一些接口是自己完全手写实现的,后来才发现有微信提供的库,赶项目进度,也就没有再去替换使用。
我个人建议优先使用微信提供的库,里面帮你解决了各种加密、解密问题,你只需要传证书,传参数进去即可,方便很多。

做完这套东西,我总感觉很多自己手动实现的地方,容易出bug,能换成微信的库可能更好,我是打算后面再去捋一捋微信的库的功能,能替换的都替换掉。这里立了个flag,希望自己能做到,哈哈哈哈。