跳到主要内容

平台能力集成

Host SDK 支持接入方覆写部分开放给小游戏开发者的 JS 接口以集成平台方登录 SDK、广告 SDK、支付 SDK 等功能。 接入 Host SDK 的开发者可以通过 CustomCommand 接口 接入定制功能。

1 登录 SDK​

1.1 登录组件接口列表​

Host SDK 给小游戏开发者提供了的登录 JS 接口如下。

  • tj.login

    • 输入参数: Object args
    属性类型必填说明
    timeoutnumberN超时时间,单位ms
    successfunctionN接口调用成功的回调函数
    failfunctionN接口调用失败的回调函数
    completefunctionN接口调用结束的回调函数(调用成功、失败都会执行)
    • args.success 回调函数参数: Object res
    属性类型说明
    codestring调用接口获取登录凭证(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
  • onLoad 传入的监听函数。不传此参数则移除所有监听函数。
RewardedVideoAd.onError(function listener)监听激励视频错误事件。参数:function listener
  • 激励视频错误事件的监听函数
RewardedVideoAd.offError(function listener)移除激励视频错误事件的监听函数参数:function listener
  • onError 传入的监听函数。不传此参数则移除所有监听函数。
RewardedVideoAd.onClose(function listener)监听用户点击 关闭广告 按钮的事件。参数:function listener
  • 用户点击 关闭广告 按钮的事件的监听函数
RewardedVideoAd.offClose(function listener)移除用户点击 关闭广告 按钮的事件的监听函数参数:function listener
  • onClose 传入的监听函数。不传此参数则移除所有监听函数。

下面介绍 “创建激励视频广告” 怎样覆盖广告接口,接入广告逻辑。

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)​

参数说明

属性名类型默认值必填说明
modestringgame是支付的类型,枚举值
  • game -目前仅支持该类型
envnumber0是环境配置,枚举值
  • 0 - 表示支付正式环境,目前仅支持该类型
currencyTypestringCNY是币种,枚举值
  • CNY - 表示人民币
  • VND - 表示越南盾
  • EUR - 表示欧元
  • ...
platformstring是申请接入时的平台,android或者ios
buyQuantitynumber否所购买的数量
zoneIdstring1否游戏服务区 id,开发者自定义。游戏不分大区则默认填写"1"
customIdstring是游戏开发者自定义的唯一订单号,订单支付成功后通过服务端支付结果回调回传。必须具有唯一性,如果不传或重复传相同值,则会报错。
extraInfostring否游戏开发者自定义的其他信息,订单支付成功后通过服务端支付结果回调回传。字符串长度最大不能超过 256。
goodTypenumber2否支付场景,枚举值
  • 2 - 表示道具直购场景,目前仅支持该类型
orderAmountnumber是代表道具现金价格,单位为【人民币分】
goodNamestring是goodType为道具直购场景时,代表道具名称,长度限制小于等于10个字符,用于区分道具类型
optionsmap否扩展参数,可以根据平台需求定制
successfunction否接口调用成功的回调函数
failfunction否接口调用失败的回调函数
completefunction否接口调用结束的回调函数(调用成功、失败都会执行)

3.2.1 回调成功参数​

Object类型,属性如下

属性名类型说明
errMsgstringrequestPayment:ok

3.2.2 回调失败参数​

Object类型,属性如下

属性名类型说明
errMsgstring"requestPayment:fail " + 错误详情
errCodenumber错误码,对应信息可参考错误码说明

3.2.3 回调结束参数​

Object类型,属性如下

属性名类型说明
errMsgstringrequestPayment:ok

3.2.4 错误码说明​

根据实际支持情况映射

errCodeerrMsg说明
-1requestPayment:fail 支付失败用户支付失败
-1requestPayment:fail internal error小游戏框架内部错误
-2requestPayment:fail 支付取消用户取消支付
3requestPayment:fail error_tip_start_cash_register_fail拉起收银台失败,如遇小游戏框架内部错误
4requestPayment:fail 网络异常用户网络异常
-15002requestPayment:fail 请求参数不合法请求参数不合法
-15006requestPayment:fail app 没有支付权限请确认是否设置游戏币汇率
-15098requestPayment:fail 当前用户未通过实名认证当前用户未通过实名认证
-15099requestPayment:fail 当前用户累计支付金额超过限制当前用户累计支付金额超过限制
-15101requestPayment:fail customId 为空或者不唯一customId 为空或者不唯一
-16000requestPayment:fail 用户未登录用户未登录
-20002requestPayment:fail 交易存在风险交易存在风险,被风控策略拦截
21113requestPayment: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 请求参数​
参数名类型说明
timestampstring时间戳
noncestring随机数
msgstringJson string类型的支付结果信息,参见2.1.3PaymentInfo对象
signaturestring签名,签名算法参见2.1.4签名部分
3.3.1.3 PaymentInfo​
参数名类型说明
appidstring平台方小游戏appid
cp_ordernostring开发者自定义订单号,tj.requestPayment方法中的customId参数
cp_extrastring开发者传的额外参数,tj.requestPayment方法中的extra参数
order_no_channelstring小游戏后台交易单号
amount_centint64订单金额,单位人民币分
amount_coinint64购买游戏币数量,平台方集成中不涉及该场景,可忽略
currencystring支付币种,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 请求参数​
参数名类型说明
tokenstring双方约定服务端token
ordernostringcp 自定义订单号,在下单的时候传入,tj.requestPayment方法中的customId参数
3.3.2.3 响应​
参数名类型说明
statusstring支付状态。success 表示支付成功且发币到账,unsuccess 表示支付失败
3.3.2.4 鉴权​

请求需包含双方约定好的服务器令牌,放在Auhthorization header里,格式形如

Bearer {service_token}