平台能力集成
Host SDK 支持接入方覆写部分开放给小游戏开发者的 JS 接口以集成平台方登录 SDK、广告 SDK、支付 SDK 等功能。 接入 Host SDK 的开发者可以通过 CustomCommand 接口 接入定制功能。
1 登录 SDK
1.1 登录组件接口列表
Host SDK 给小游戏开发者提供了的登录 JS 接口如下。
-
tj.login
- 输入参数: Object args
属性 类型 必填 说明 timeout number N 超时时间,单位ms success function N 接口调用成功的回调函数 fail function N 接口调用失败的回调函数 complete function N 接口调用结束的回调函数(调用成功、失败都会执行) - args.success 回调函数参数: Object res
属性 类型 说明 code string 调用接口获取登录凭证(code)
登录能力可通过覆写 tj.login 接口实现。
tj.login = function (obj) {
tj.customCommand({
success: function(res) {
let resObj = JSON.parse(res);
obj.success(resObj);
},
fail: function(res) {
let apiRes = {
errMsg: res.errorMsg,
errno: res.errorCode
};
obj.fail(apiRes);
},
complete: function() {
obj.complete();
}
}, "loginUnity");
};
代码中同样使用 CustomCommand 执行 Java 中的 Native Func loginUnity(),loginUnity() 的主要功能是通过平台自身的登录 SDK 获取 Code 并返回给宿主,宿主将其传递给游戏后,游戏可通过 Code2Session() 换取用户信息。
在多进程游戏的场景下,如果用户在主进程已经登录,则可以使用 AIDL 或其他进程间通信方式从游戏进程向主进程发起获取 Code 的请求,并由主进程统一返回 Code。
hostHandle.setCustomCommandListener("loginUnity", ((customCommandHandle, bundle) -> {
try {
loginService.requesetLogin(new ILoginCallback.Stub() {
@Override
public void loginEnd(String code) {
JSONObject loginRes = new JSONObject();
try {
loginRes.put("code", code);
} catch (Exception e) {
customCommandHandle.fail(1002, "Failed to serialize login result" + e.getMessage());
}
customCommandHandle.pushResult(loginRes.toString());
customCommandHandle.success();
}
});
} catch (RemoteException e) {
customCommandHandle.fail(1001, "Failed to connect Login Service: " + e.getMessage());
}
}));
1.2 登录全流程
登录全流程示意图如下:

游戏客户端通过调用 tj.login(流程图中的第3步)获取code后,需要通过JS code2session获取用户的登录信息,实现用户登录。
jscode2session
提供平台方的登录能力,宿主后台会将用户的登录凭证(code)传递给平台方,平台方需要根据该凭证返回用户的登录信息。
宿主后台会结合请求的上下文,将平台方返回的 uid 映射为游戏侧可用的 openid 和 unionid,同时转发 session_key 给游戏侧做为登录凭证。
请求示例:
GET /jscode2session?js_code={code} HTTP/1.1
Authorization: Bearer {service_token}
请求参数说明:
| 参数 | 说明 |
|---|---|
| js_code | 平台方登录凭证 |
| Authorization | 服务端令牌,格式为 Bearer {service_token} |
响应示例:
HTTP/1.1 200 OK
Content-Type: application/json
{
"uid": "123456"
"session_key": "abcdefg",
"errcode": 0,
"errmsg": "ok"
}
响应字段说明:
| 字段 | 说明 |
|---|---|
| uid | 平台方返回的用户唯一标识 |
| session_key | 平台方返回的 session_key |
| errcode | 错误码,0 表示成功 |
| errmsg | 错误信息 |
2 广告 SDK
2.1 广告组件接口
Host SDK 给小游戏开发者提供了的广告 JS 接口如下。
tj.createRewardedVideoAd()
返回值为RewardedVideoAd,暴露的方法有:
| 函数名 | 函数说明 | 返回值 |
|---|---|---|
| RewardedVideoAd.load() | 加载激励视频广告 | Promise
|
| RewardedVideoAd.show() | 显示激励视频广告 | Promise
|
| RewardedVideoAd.destroy() | 销毁激励视频广告实例 | |
| RewardedVideoAd.onLoad(function listener) | 监听激励视频广告加载事件。参数:function listener
| |
| RewardedVideoAd.offLoad(function listener) | 监听激励视频广告加载事件。参数:function listener
| |
| RewardedVideoAd.onError(function listener) | 监听激励视频错误事件。参数:function listener
| |
| RewardedVideoAd.offError(function listener) | 移除激励视频错误事件的监听函数参数:function listener
| |
| RewardedVideoAd.onClose(function listener) | 监听用户点击 关闭广告 按钮的事件。参数:function listener
| |
| RewardedVideoAd.offClose(function listener) | 移除用户点击 关闭广告 按钮的事件的监听函数参数:function listener
|
下面介绍 “创建激励视频广告” 怎样覆盖广告接口,接入广告逻辑。
2.2 广告组件接口示例
tj.createRewardedVideoAd()
此接口用于创建插屏广告组件,并返回 RewardedVideoAd 实例,实例暴露的部分方法有:
-
RewardedVideoAd.load()加载激励广告,返回一个 Promise -
RewardedVideoAd.onLoad(callback)接收函数作为参数,监听插屏广告加载事件 -
RewardedVideoAd.offLoad(callback)移除插屏广告加载事件的监听函数,不传参数则移除所有
Host SDK 并不内置广告功能,仅提供了 tj.createRewardedVideoAd() 的一个默认实现,此实现返回的 RewardedVideoAd 实例暴露的方法均为空实现,小游戏开发者调用 RewardedVideoAd.load() 并不能真实加载激励广告。
因此接入方 App 需要覆写此 tj.createRewardedVideoAd(),使得 RewardedVideoAd.load() 内调用 App 内接入的第三方广告 SDK 的加载接口,同理实现 RewardedVideoAd 暴露的其他接口以实现广告功能的集成。
接入方 App 可在工程内提供若干自定义 JS 文件(Android 为 assets 目录下)用于覆写 JS 接口,并在设置游戏启动参数时指定自定义 JS 文件的路径。
如以 Android 为例,可在 assets 目录下创建一个 customScripts 目录,添加用于覆写广告接口的 JS 文件 ad.js 后,设置游戏启动参数如下:
bundle.putString(TJConstants.BUILTIN_CUSTOM_SCRIPTS_PATH, "customScripts");
Host SDK 会读取此目录下所有 JS 文件,并使用文件内定义的方法覆写 Host SDK 提供的 JS 接口。
2.3 接入第三方广告 SDK
以集成说明所述激励广告 API 为例,可参考 Demo 项目中 ad.js 如下:
let rewardedVideoAdOnLoadCallbacks = [];
tj.createRewardedVideoAd = (obj) => {
return {
onLoad: (callback) => {
if (callback && typeof callback === "function") {
if (!rewardedVideoAdOnLoadCallbacks.includes(callback)) {
rewardedVideoAdOnLoadCallbacks.push(callback);
}
}
},
load: () => {
tj.customCommand({},"loadRewardAd");
},
offLoad: (callback) => {
console.info("[Host] invoke RewardedVideoAd.offLoad");
if (callback === undefined) rewardedVideoAdOnLoadCallbacks = [];
else if (callback && typeof callback === "function") {
rewardedVideoAdOnLoadCallbacks = rewardedVideoAdOnLoadCallbacks.filter(
(cb) => cb !== callback,
);
}
},
// Other RewardedVideoAd methods ...
};
};
rewardedVideoLoadCallbackFromJava = () => {
try {
rewardedVideoAdOnLoadCallbacks.forEach((callback) => callback());
} catch (error) {
console.error("[Host] Error in rewardedVideoLoadCallbackFromJava:", error);
}
};
在此 ad.js 中,通过定义 tj.createRewardedVideoAd 实现了对其默认实现的覆写,并在其返回的 RewardedVideoAd 实例的 load() 方法内使用 CustomCommand(用于 JS 与 Java 代码之间的相互调用,具体可参考 API Reference) 执行 Java 中的 Native Func loadRewardAd()
Java 中设置监听 loadRewardAd 的代码如下:
hostHandle.setCustomCommandListener("loadRewardAd", ((customCommandHandle, bundle) -> {
if (isGamePlayed) {
adManager.loadRewardAd(); // Ad SDK API
}
}));
// In the Ad SDK loadSuccess callback
String script = "rewardedVideoLoadCallbackFromJava();";
customCommand.runCustomScript(script, bundle -> Log.i("AdManager", "onVideoAdLoadSuccess rewardedVideoLoadCallbackFromJava end"));
在 Java 示例中,监听到来自 JS 的 loadRewardAd 调用后,实际执行 Ad SDK API 加载激励广告,并在加载完成的回调内再通过 CustomCommand 调用 JS 方法 rewardedVideoLoadCallbackFromJava() 通知 JS 侧执行小游戏开发者注册的回调,从而实现小游戏开发者调用 Host JS 接口到 Java Native Ad SDK API 再到小游戏开发者注册的 JS 回调的完整逻辑。
3 支付 SDK
3.1 流程图
3.1.1 小游戏支付

3.1.2 主动查询订单

3.2 客户端接口
tj.requestPayment(Object obj)
参数说明
| 属性名 | 类型 | 默认值 | 必填 | 说明 |
|---|---|---|---|---|
| mode | string | game | 是 | 支付的类型,枚举值
|
| env | number | 0 | 是 | 环境配置,枚举值
|
| currencyType | string | CNY | 是 | 币种,枚举值
|
| platform | string | 是 | 申请接入时的平台,android或者ios | |
| buyQuantity | number | 否 | 所购买的数量 | |
| zoneId | string | 1 | 否 | 游戏服务区 id,开发者自定义。游戏不分大区则默认填写"1" |
| customId | string | 是 | 游戏开发者自定义的唯一订单号,订单支付成功后通过服务端支付结果回调回传。必须具有唯一性,如果不传或重复传相同值,则会报错。 | |
| extraInfo | string | 否 | 游戏开发者自定义的其他信息,订单支付成功后通过服务端支付结果回调回传。字符串长度最大不能超过 256。 | |
| goodType | number | 2 | 否 | 支付场景,枚举值
|
| orderAmount | number | 是 | 代表道具现金价格,单位为【人民币分】 | |
| goodName | string | 是 | goodType为道具直购场景时,代表道具名称,长度限制小于等于10个字符,用于区分道具类型 | |
| options | map | 否 | 扩展参数,可以根据平台需求定制 | |
| success | function | 否 | 接口调用成功的回调函数 | |
| fail | function | 否 | 接口调用失败的回调函数 | |
| complete | function | 否 | 接口调用结束的回调函数(调用成功、失败都会执行) |
3.2.1 回调成功参数
Object类型,属性如下
| 属性名 | 类型 | 说明 |
|---|---|---|
| errMsg | string | requestPayment:ok |
3.2.2 回调失败参数
Object类型,属性如下
| 属性名 | 类型 | 说明 |
|---|---|---|
| errMsg | string | "requestPayment:fail " + 错误详情 |
| errCode | number | 错误码,对应信息可参考错误码说明 |
3.2.3 回调结束参数
Object类型,属性如下
| 属性名 | 类型 | 说明 |
|---|---|---|
| errMsg | string | requestPayment:ok |
3.2.4 错误码说明
根据实际支持情况映射
| errCode | errMsg | 说明 |
|---|---|---|
| -1 | requestPayment:fail 支付失败 | 用户支付失败 |
| -1 | requestPayment:fail internal error | 小游戏框架内部错误 |
| -2 | requestPayment:fail 支付取消 | 用户取消支付 |
| 3 | requestPayment:fail error_tip_start_cash_register_fail | 拉起收银台失败,如遇小游戏框架内部错误 |
| 4 | requestPayment:fail 网络异常 | 用户网络异常 |
| -15002 | requestPayment:fail 请求参数不合法 | 请求参数不合法 |
| -15006 | requestPayment:fail app 没有支付权限 | 请确认是否设置游戏币汇率 |
| -15098 | requestPayment:fail 当前用户未通过实名认证 | 当前用户未通过实名认证 |
| -15099 | requestPayment:fail 当前用户累计支付金额超过限制 | 当前用户累计支付金额超过限制 |
| -15101 | requestPayment:fail customId 为空或者不唯一 | customId 为空或者不唯一 |
| -16000 | requestPayment:fail 用户未登录 | 用户未登录 |
| -20002 | requestPayment:fail 交易存在风险 | 交易存在风险,被风控策略拦截 |
| 21113 | requestPayment:fail face authentication failed | 检测到疑似未成年下单,拉起人脸核验,人脸验证不通过 |
3.2.5 示例代码
tj.requestPayment({
mode: 'game',
env: 0,
currencyType: 'CNY',
platform: 'ios',
zoneId: '1',
customId: 'order_' + Date.now(),
extraInfo: JSON.stringify({
orderId: 'test_order_' + Date.now(),
userId: '10001'
}),
goodType: 2,
orderAmount: 100,
goodName: '去广告',
options: {
'PRODUCT_CODE': '1001'
},
success: function success(res) {
console.log('[游戏JS端] 支付成功:', res);
tj.showToast({
title: '支付成功,再来一局!',
duration: 2000
});
},
fail: function fail(err) {
if (err.errCode && err.errCode == -2) {
console.log('[游戏JS端] 用户取消支付');
tj.showToast({
title: '支付取消,请重新支付!',
duration: 2000
});
} else {
console.error('[游戏JS端] 支付失败:', err);
tj.showToast({
title: '支付失败,请重试!',
duration: 2000
});
}
},
complete: function complete(res) {
console.log('[游戏JS端] 支付流程结束:', res);
}
})
3.3 服务端接口
3.3.1 服务端回调接口
支付成功后,平台方服务端向宿主服务端发送支付成功回调。宿主服务端在验签后将回调发送给游戏服务端。
3.3.1.1 回调地址
POST https://minihost.tuanjie.cn/game-api/v1/payment/callback
3.3.1.2 请求参数
| 参数名 | 类型 | 说明 |
|---|---|---|
| timestamp | string | 时间戳 |
| nonce | string | 随机数 |
| msg | string | Json string类型的支付结果信息,参见2.1.3PaymentInfo对象 |
| signature | string | 签名,签名算法参见2.1.4签名部分 |
3.3.1.3 PaymentInfo
| 参数名 | 类型 | 说明 |
|---|---|---|
| appid | string | 平台方小游戏appid |
| cp_orderno | string | 开发者自定义订单号,tj.requestPayment方法中的customId参数 |
| cp_extra | string | 开发者传的额外参数,tj.requestPayment方法中的extra参数 |
| order_no_channel | string | 小游戏后台交易单号 |
| amount_cent | int64 | 订单金额,单位人民币分 |
| amount_coin | int64 | 购买游戏币数量,平台方集成中不涉及该场景,可忽略 |
| currency | string | 支付币种,CNY 人民币,DIAMOND 钻石 |
3.3.1.4 签名
根据 token,timestamp,nonce,msg ,使用 SHA1 算法生成 signature,与平台方的服务端返回的 signature 对比。
前置条件:在关联游戏时,平台方需要将token传 递给宿主,用于签名校验
golang示例代码:
sortedString := make([]string, 4)
sortedString = append(sortedString, token)
sortedString = append(sortedString, timestamp)
sortedString = append(sortedString, nonce)
sortedString = append(sortedString, string(msg))
sort.Strings(sortedString)h := sha1.New()
h.Write([]byte(strings.Join(sortedString, "")))_signature := h.Sum(nil)
3.3.1.5 响应
宿主服务器返回200状态码,作为成功响应。若返回 http 状态码非200,表示通知失败,需要进行重试。
3.3.2 主动查询订单接口
服务端支付回调可能由于网络异常等原因导致无法 100%触达,开发者可以通过该接口主动回查订单状态,防止超发漏发。开发者向宿主服务端发送查询订单请求,宿主封装后向平台方服务端发送查询订单请求。
3.3.2.1 接口地址
由平台方提供
3.3.2.2 请求参数
| 参数名 | 类型 | 说明 |
|---|---|---|
| token | string | 双方约定服务端token |
| orderno | string | cp 自定义订单号,在下单的时候传入,tj.requestPayment方法中的customId参数 |
3.3.2.3 响应
| 参数名 |
|---|