Skip to main content

Platform Capability Integration

Host SDK allows integrators to override some JS interfaces open to minigame developers to integrate platform-side login SDK, advertising SDK, payment SDK, and other functions. Developers who integrate Host SDK can access custom functions through the CustomCommand Interface.

1 Login SDK​

1.1 Login Component Interface List​

The login JS interface provided by the Host SDK to minigame developers is as follows.

tj.login​
  • Input parameters: Object args
AttributeTypeRequiredDescription
timeoutnumberNTimeout, unit: ms
successfunctionNCallback function for successful interface call
failfunctionNCallback function for interface call failure
completefunctionNCallback function for the end of interface call (executed upon both successful and failed calls)
  • args.success callback function parameter: Object res
AttributeTypeDescription
codestringGame developers should call Game Server code2session (Step 9 in login process diagram) with this code to obtain the login credential

Login capabilities can be implemented by overriding the tj.login interface.

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");
};

The code also uses CustomCommand to execute the Native Func loginUnity () in Java, loginUnity () whose main function is to obtain the Code through the platform's own login SDK and return it to the host. After the host passes it to the game, the game can exchange it for user information through Game Server code2session API.

In the scenario of a multi-process game, if the user has already logged in to the main process, they can use AIDL or other Inter-Process Communication methods to initiate a request for obtaining the Code from the game process to the main process, and the main process will uniformly return the 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 Full Login Process​

The schematic diagram of the entire login process is as follows:

After the game client obtains the code by calling tj.login, platform app needs to obtain the user's login information through JS code2session to implement user login (Step 10 in the diagram above).

jscode2session​

Here defines the implementation guidelines for the platform's APIs. If you are a game developer, please refer to Minihost Platform Funtionality Documentation - Section 2.1.2 - jscode2session for integration.

Provides the login capability of the platform. The host backend will pass the user's login credential (code) to the platform, which needs to return the user's login information based on this credential.

The host backend will map the uid returned by the platform to the openid and unionid available on the game side, based on the context of the request, and forward the session_key to the game side as a login credential.

Example of request:

GET /jscode2session?js_code={code} HTTP/1.1
Authorization: Bearer {service_token}

Request Parameter Description:

parameterDescription
js_codePlatform Login Credentials
AuthorizationServer token, formatted as Bearer {service_token}

Response Example:

HTTP/1.1 200 OK
Content-Type: application/json

{
"uid": "123456"
"session_key": "abcdefg",
"errcode": 0,
"errmsg": "ok"
}

Response field description:

FieldDescription
uidUnique user identifier returned by the platform
session_keysession_key returned by the platform
errcodeError code, 0 indicates success
errmsgError Message

1.3 Login Interface Integration Example​

The access party's App can provide a custom JS file within the project to override the JS interface, and specify the path of the custom JS file when setting the game startup parameters.

Example of auth.js

// auth.js - Custom authentication implementation

// Override tj.login
tj.login = function(obj) {
tj.customCommand('platformLogin', obj);
};

// Override tj.checkSession
tj.checkSession = function(obj) {
tj.customCommand('checkSession', obj);
};

// Override tj.pluginLogin
tj.pluginLogin = function(obj) {
tj.customCommand('pluginLogin', obj);
};

Inject the login JS file into the host

const { TJConstants } = require('webglhost-runtime-pc');
const path = require('path');

const options = {};
options[TJConstants.LAUNCH_KEY] = 'launch-key';
options[TJConstants.USER_ID] = 'user-123';

// Inject custom auth script
options[TJConstants.CUSTOM_SCRIPT_FILES] = [
path.join(__dirname, 'custom-scripts', 'auth.js')
];

await gameHandle.setGameStartOptions(options);

Implement login logic in the host

// Setup custom command listeners for auth
gameHandle.setCustomCommandListener('platformLogin', (request, handle) => {
// Call your platform's login SDK
yourPlatformSDK.login()
.then(result => {
handle.success({
code: result.authCode,
userId: result.userId
});
})
.catch(error => {
handle.fail(20001, error.message);
});
});

gameHandle.setCustomCommandListener('checkSession', (request, handle) => {
// Check session validity
yourPlatformSDK.checkSession()
.then(() => {
handle.success({});
})
.catch(error => {
handle.fail(20002, 'Session expired');
});
});

gameHandle.setCustomCommandListener('pluginLogin', (request, handle) => {
// Handle plugin login
yourPlatformSDK.pluginLogin()
.then(result => {
handle.success({
code: result.authCode
});
})
.catch(error => {
handle.fail(20003, error.message);
});
});

2 Advertising SDK​

2.1 Advertising Component Interface List​

The advertising JS interfaces provided by the Host SDK to minigame developers are as follows.

tj.createRewardedVideoAd()​

The return value is RewardedVideoAd, and the exposed methods are:

Function NameFunction DescriptionReturn value
RewardedVideoAd.load()Load rewarded video adPromise
  • Result of incentive video ad loading data
RewardedVideoAd.show()Show incentive video adPromise
  • Shows the result of the incentive video ad display operation
RewardedVideoAd.destroy()Destroy the rewarded video ad instance
RewardedVideoAd.onLoad(function listener)Listen for the incentive video ad loading event. Parameter: function listener
  • Listener function for the loading event of rewarded video ads
RewardedVideoAd.offLoad(function listener)Listen for the incentive video ad loading event. Parameter: function listener
  • The listener function passed to onLoad. If this parameter is not passed, all listener functions will be removed.
RewardedVideoAd.onError(function listener)Listen for the error event of the incentive video. Parameter: function listener
  • Listener function for incentive video error event
RewardedVideoAd.offError(function listener)Remove the listener function for the incentive video error event Parameter: function listener
  • The listener function passed to onError. If this parameter is not passed, all listener functions will be removed.
RewardedVideoAd.onClose(function listener)Listen for the event when the user clicks the close ad button. Parameter: function listener
  • The event listener function for the user clicking the Close Ad button
RewardedVideoAd.offClose(function listener)Remove the event listener function for the user click Close Ad button Parameter: function listener
  • The listener function passed to onClose. If this parameter is not passed, all listener functions will be removed.

Below, taking "Create Rewarded Video Ad" as an example, we will introduce how to override the ad interface and integrate ad logic.

2.2 Advertising Component Interface Example​

tj.createRewardedVideoAd()

This interface is used to create a rewarded video ad component and returns RewardedVideoAd instance. Some of the methods exposed by the instance are:

  • RewardedVideoAd.load() loads a rewarded ad and returns a Promise

  • RewardedVideoAd.onLoad(callback) accepts a function as a parameter to listen for the ad load event

  • RewardedVideoAd.offLoad(callback) removes the listener function for the ad load event; if no parameters are passed, all listeners will be removed

The Host SDK does not have built-in advertising functionality, but only provides a default implementation of tj.createRewardedVideoAd() , and the methods exposed by the RewardedVideoAd instance returned by this implementation are all empty implementations. When a minigame developer calls RewardedVideoAd.load() , it cannot actually load a rewarded ad.

Therefore, the access party's App needs to override this tj.createRewardedVideoAd(), so that the RewardedVideoAd.load() calls the loading interface of the third-party advertising SDK integrated within the App, and similarly implements the other interfaces exposed by RewardedVideoAd to achieve the integration of advertising functionality.

2.3 Access Third-Party Advertising SDK​

Taking the integrated incentive ad API as an example, you can refer to the following ad.js implementation:

ad.js Example

// ad.js - Custom ad implementation

let rewardedVideoAdOnLoadCallbacks = [];
let rewardedVideoAdOnErrorCallbacks = [];
let rewardedVideoAdOnCloseCallbacks = [];

tj.createRewardedVideoAd = (obj) => {
const adUnitId = obj.adUnitId;

return {
onLoad: (callback) => {
console.info("[Host] invoke RewardedVideoAd.onLoad");
if (callback && typeof callback === "function") {
if (!rewardedVideoAdOnLoadCallbacks.includes(callback)) {
rewardedVideoAdOnLoadCallbacks.push(callback);
}
}
},

load: () => {
console.info("[Host] invoke RewardedVideoAd.load");

return new Promise((resolve, reject) => {
tj.customCommand("loadRewardAd", {
adUnitId: adUnitId,
success: function (res) {
console.info("[Host] RewardedVideoAd.load success");
resolve(res);
},
fail: function (obj) {
console.info("[Host] RewardedVideoAd.load fail");
reject(obj);
},
complete: function () {
console.info("[Host] RewardedVideoAd.load complete");
},
});
});
},

show: () => {
console.info("[Host] invoke RewardedVideoAd.show");

return new Promise((resolve, reject) => {
tj.customCommand("showRewardAd", {
adUnitId: adUnitId,
success: function (res) {
console.info("[Host] RewardedVideoAd.show success");
resolve(res);
},
fail: function (obj) {
console.info("[Host] RewardedVideoAd.show fail");
reject(obj);
},
});
});
},

offLoad: (callback) => {
console.info("[Host] invoke RewardedVideoAd.offLoad");
if (callback === undefined) {
rewardedVideoAdOnLoadCallbacks = [];
} else if (callback && typeof callback === "function") {
rewardedVideoAdOnLoadCallbacks = rewardedVideoAdOnLoadCallbacks.filter(
(cb) => cb !== callback
);
}
},

onError: (callback) => {
console.info("[Host] invoke RewardedVideoAd.onError");
if (callback && typeof callback === "function") {
if (!rewardedVideoAdOnErrorCallbacks.includes(callback)) {
rewardedVideoAdOnErrorCallbacks.push(callback);
}
}
},

offError: (callback) => {
console.info("[Host] invoke RewardedVideoAd.offError");
if (callback === undefined) {
rewardedVideoAdOnErrorCallbacks = [];
} else if (callback && typeof callback === "function") {
rewardedVideoAdOnErrorCallbacks = rewardedVideoAdOnErrorCallbacks.filter(
(cb) => cb !== callback
);
}
},

onClose: (callback) => {
console.info("[Host] invoke RewardedVideoAd.onClose");
if (callback && typeof callback === "function") {
if (!rewardedVideoAdOnCloseCallbacks.includes(callback)) {
rewardedVideoAdOnCloseCallbacks.push(callback);
}
}
},

offClose: (callback) => {
console.info("[Host] invoke RewardedVideoAd.offClose");
if (callback === undefined) {
rewardedVideoAdOnCloseCallbacks = [];
} else if (callback && typeof callback === "function") {
rewardedVideoAdOnCloseCallbacks = rewardedVideoAdOnCloseCallbacks.filter(
(cb) => cb !== callback
);
}
},

destroy: () => {
console.info("[Host] invoke RewardedVideoAd.destroy");
rewardedVideoAdOnLoadCallbacks = [];
rewardedVideoAdOnErrorCallbacks = [];
rewardedVideoAdOnCloseCallbacks = [];
}
};
};

// Callback functions to be called from host
rewardedVideoLoadCallback = () => {
try {
rewardedVideoAdOnLoadCallbacks.forEach((callback) => callback());
} catch (error) {
console.error("[Host] Error in rewardedVideoLoadCallback:", error);
}
};

rewardedVideoErrorCallback = (error) => {
try {
rewardedVideoAdOnErrorCallbacks.forEach((callback) => callback(error));
} catch (error) {
console.error("[Host] Error in rewardedVideoErrorCallback:", error);
}
};

rewardedVideoCloseCallback = (result) => {
try {
rewardedVideoAdOnCloseCallbacks.forEach((callback) => callback(result));
} catch (error) {
console.error("[Host] Error in rewardedVideoCloseCallback:", error);
}
};

Inject advertising JS files into the host

const { TJConstants } = require('webglhost-runtime-pc');
const path = require('path');

const options = {};
options[TJConstants.LAUNCH_KEY] = 'launch-key';
options[TJConstants.USER_ID] = 'user-123';

// Inject custom ad script
options[TJConstants.CUSTOM_SCRIPT_FILES] = [
path.join(__dirname, 'custom-scripts', 'ad.js')
];

await gameHandle.setGameStartOptions(options);

Implement advertising logic in the host

// Setup custom command listeners for ads
gameHandle.setCustomCommandListener('loadRewardAd', (request, handle) => {
const adUnitId = request.adUnitId;

console.log('Loading reward ad:', adUnitId);

// Call your ad SDK to load the ad
yourAdSDK.loadRewardedVideo(adUnitId)
.then(() => {
handle.success({});

// Notify JS that ad loaded successfully
gameHandle.runCustomScript(`
rewardedVideoLoadCallback();
`, (result, error) => {
if (error) {
console.error('Error calling load callback:', error);
}
});
})
.catch(error => {
handle.fail(30001, error.message);

// Notify JS that ad load failed
gameHandle.runCustomScript(`
rewardedVideoErrorCallback({ errCode: 30001, errMsg: '${error.message}' });
`, (result, error) => {
if (error) {
console.error('Error calling error callback:', error);
}
});
});
});

gameHandle.setCustomCommandListener('showRewardAd', (request, handle) => {
const adUnitId = request.adUnitId;

console.log('Showing reward ad:', adUnitId);

// Call your ad SDK to show the ad
yourAdSDK.showRewardedVideo(adUnitId)
.then(result => {
handle.success({});

// Notify JS when ad is closed
const isAdWatched = result.isAdWatched; // User watched ad completely
gameHandle.runCustomScript(`
rewardedVideoCloseCallback({ isEnded: ${isAdWatched} });
`, (result, error) => {
if (error) {
console.error('Error calling close callback:', error);
}
});
})
.catch(error => {
handle.fail(30002, error.message);
});
});

2.4 Complete Ad Integration Example​

const { TJHostHandle, TJConstants } = require('webglhost-runtime-pc');
const path = require('path');

class AdManager {
constructor(gameHandle) {
this.gameHandle = gameHandle;
this.setupAdListeners();
}

setupAdListeners() {
// Load ad listener
this.gameHandle.setCustomCommandListener('loadRewardAd', (request, handle) => {
this.handleLoadRewardAd(request, handle);
});

// Show ad listener
this.gameHandle.setCustomCommandListener('showRewardAd', (request, handle) => {
this.handleShowRewardAd(request, handle);
});
}

async handleLoadRewardAd(request, handle) {
const adUnitId = request.adUnitId;

try {
console.log(`Loading rewarded video ad: ${adUnitId}`);

// Simulate ad loading (replace with actual ad SDK call)
await this.simulateAdLoad(adUnitId);

// Success callback to host
handle.success({});

// Notify JS layer that ad loaded
this.notifyAdLoaded();

} catch (error) {
handle.fail(30001, error.message);
this.notifyAdError(error);
}
}

async handleShowRewardAd(request, handle) {
const adUnitId = request.adUnitId;

try {
console.log(`Showing rewarded video ad: ${adUnitId}`);

// Simulate ad showing (replace with actual ad SDK call)
const result = await this.simulateAdShow(adUnitId);

// Success callback to host
handle.success({});

// Notify JS layer that ad closed
this.notifyAdClosed(result.isEnded);

} catch (error) {
handle.fail(30002, error.message);
this.notifyAdError(error);
}
}

notifyAdLoaded() {
this.gameHandle.runCustomScript(`
if (typeof rewardedVideoLoadCallback === 'function') {
rewardedVideoLoadCallback();
}
`, (result, error) => {
if (error) {
console.error('Error notifying ad loaded:', error);
}
});
}

notifyAdError(error) {
this.gameHandle.runCustomScript(`
if (typeof rewardedVideoErrorCallback === 'function') {
rewardedVideoErrorCallback({
errCode: ${error.code || 30000},
errMsg: '${error.message}'
});
}
`, (result, err) => {
if (err) {
console.error('Error notifying ad error:', err);
}
});
}

notifyAdClosed(isEnded) {
this.gameHandle.runCustomScript(`
if (typeof rewardedVideoCloseCallback === 'function') {
rewardedVideoCloseCallback({ isEnded: ${isEnded} });
}
`, (result, error) => {
if (error) {
console.error('Error notifying ad closed:', error);
}
});
}

// Simulate ad loading (replace with actual ad SDK integration)
async simulateAdLoad(adUnitId) {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log(`Ad ${adUnitId} loaded successfully`);
resolve();
}, 1000);
});
}

// Simulate ad showing (replace with actual ad SDK integration)
async simulateAdShow(adUnitId) {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log(`Ad ${adUnitId} shown successfully`);
// Simulate user watching ad completely
resolve({ isEnded: true });
}, 2000);
});
}
}

// Usage
async function initializeWithAds() {
const hostHandle = await new Promise((resolve, reject) => {
TJHostHandle.initialize('sdk-key', 'sdk-secret', (handle, error) => {
if (error) reject(error);
else resolve(handle);
});
});

const gameHandle = await hostHandle.createGameHandle();

// Initialize ad manager
const adManager = new AdManager(gameHandle);

// Setup game options with custom ad script
const options = {
[TJConstants.LAUNCH_KEY]: 'launch-key',
[TJConstants.USER_ID]: 'user-123',
[TJConstants.CUSTOM_SCRIPT_FILES]: [
path.join(__dirname, 'custom-scripts', 'ad.js')
]
};

await gameHandle.setGameStartOptions(options);
await gameHandle.start();
await gameHandle.play();
}

initializeWithAds().catch(console.error);

3 Payment SDK​

3.1 Flowchart​

3.1.1 Minigame Payment​

3.1.2 Game Servers Actively Query Order​

3.2 Client Interface​

tj.requestPayment(Object obj)​

Parameter Description

Attribute NameTypeDefault ValueRequiredDescription
modestringgameYesPayment type, enumeration value
  • game - Currently only this type is supported
envnumber0YesEnvironment Configuration, Enumerated Value
  • 0 - Represents the formal payment environment, currently only this type is supported
currencyTypestringCNYYesCurrency, enumeration value
  • CNY - represents the Chinese Yuan
  • VND - represents Vietnamese Dong
  • EUR - represents the euro
  • ...
platformstringYesPlatform at the time of application for access, android or ios
buyQuantitynumberNo Quantity Purchased
zoneIdstring1No Game service area ID, customized by the developer. If the game does not have different regions, default to "1".
customIdstringYesA unique order number customized by the game developer, which will be postbacked via server payment result callback after the order payment is successful. It must be unique; otherwise, an error will occur if it is not passed or the same value is passed repeatedly.
extraInfostringNo Other information customized by game developers will be postbacked viaserver payment result callbackafter the order payment is successful. The maximum length of the string cannot exceed 256.
goodTypenumber2No Payment scenario, enumeration value
  • 2 - Represents the direct purchase scenario for items, currently only this type is supported
orderAmountnumberYesrepresents the cash price of the item, with the unit being [Chinese Yuan cents]
goodNamestringYesWhen goodType is for the direct purchase scenario of items, it represents the item name, with a length limit of less than or equal to 10 characters, used to distinguish item types
optionsmapNoExtended Parameters, can be customized based on platform requirements
successfunctionNo Callback function for successful interface call
failfunctionNo Callback function for interface call failure
completefunctionNo Callback function for the end of interface call (executed upon both successful and failed calls)

3.2.1 Callback Success Parameters​

Object type, with the following properties

Attribute NameTypeDescription
errMsgstringrequestPayment:ok

3.2.2 Callback Failure Parameters​

Object type, with the following properties

Attribute NameTypeDescription
errMsgstring"requestPayment:fail " + Error Details
errCodenumberError code, for corresponding information, please refer to the error code description

3.2.3 Callback End Parameters​

Object type, with the following properties

Attribute NameTypeDescription
errMsgstringrequestPayment:ok

3.2.4 Error Code Description​

Map according to actual support situation

errCodeerrMsgDescription
-1requestPayment:fail Payment failedUser payment failed
-1requestPayment:fail internal errorInternal error in the minigame framework
-2requestPayment:fail Payment cancelledUser cancelled payment
3requestPayment:fail error_tip_start_cash_register_failFailed to launch the cashier desk, such as encountering an internal error in the minigame framework
4requestPayment:fail Network errorUser network is abnormal
-15002requestPayment:fail The request parameters are invalidRequest parameters are invalid
-15006requestPayment:fail The app does not have payment permissionsPlease confirm whether to set the game coin exchange rate
-15098requestPayment:fail The current user has not passed real-name authenticationThe current user has not passed real-name authentication
-15099requestPayment:fail The current user's cumulative payment amount exceeds the limitThe cumulative payment amount of the current user exceeds the limit
-15101requestPayment:fail customId is empty or not uniquecustomId is empty or not unique
-16000requestPayment:fail User not logged inUser is not logged in
-20002requestPayment:fail Transaction is at riskThe transaction is risky and has been intercepted by the risk control strategy
21113requestPayment:fail face authentication failedA suspected minor order was detected, face verification was initiated, and the face verification failed

3.2.5 Example Code​

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: 'Remove Ads',
options: {
'PRODUCT_CODE': '1001'
},
success: function success(res) {
console.log('[Game JS Side] Payment successful:', res);
tj.showToast({
title: 'Payment successful, play again!',
duration: 2000
});
},
fail: function fail(err) {
if (err.errCode && err.errCode == -2) {
console.log('[Game JS Side] User canceled payment');
tj.showToast({
title: 'Payment canceled, please try again!',
duration: 2000
});
} else {
console.error('[Game JS Side] Payment failed:', err);
tj.showToast({
title: 'Payment failed, please retry!',
duration: 2000
});
}
},
complete: function complete(res) {
console.log('[Game JS Side] Payment process completed:', res);
}
})

3.3 Server Interface​

3.3.1 Server Callback Interface​

After the payment is successful, the platform server sends a payment success callback to the host server. After verifying the signature, the host server sends the callback to the game server.

3.3.1.1 Callback Address​

POST https://minihost.tuanjie.cn/game-api/v1/payment/callback

3.3.1.2 Request Parameters​
Parameter NameTypeDescription
timestampstringTimestamp
noncestringRandom Number
msgstringPayment result information of Json string type, see 2.1.3 PaymentInfo object
signaturestringSignature, see Section 2.1.4 Signature for the signature algorithm
3.3.1.3 PaymentInfo​
Parameter NameTypeDescription
appidstringPlatform-side minigame appid
cp_ordernostringDeveloper-defined order number, the customId parameter in the tj.requestPayment method
cp_extrastringExtra parameters passed by the developer, the extra parameter in the tj.requestPayment method
order_no_channelstringMinigame Backend Transaction Order Number
amount_centint64Order amount, in RMB cents
amount_coinint64The quantity of game coins purchased is not involved in the platform's integration process and can be ignored.
currencystringPayment currency, CNY Renminbi, DIAMOND Diamond
3.3.1.4 Signature​

Generate a signature using the SHA1 algorithm based on token, timestamp, nonce, and msg, and compare it with the signature returned by the platform's server.

Precondition: When associating a game, the platform party needs to pass token to the host for signature verification

Sample Golang code:

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

The host server returns a 200 status code as a successful response. If the returned HTTP status code is not 200, it indicates that the notification has failed and a retry is required.

3.3.2 Game Server Actively Query Order Interface​

Server-side payment callbacks may fail to reach 100% due to reasons such as network anomalies. Developers can use this interface to actively check the order status to prevent over-delivery or under-delivery. Developers send a query order request to the host server, which then encapsulates and sends the query order request to the platform server.

3.3.2.1 Interface Address​

Provided by the platform

3.3.2.2 Request Parameters​
Parameter NameTypeDescription
tokenstringThe two parties agree on the server token
ordernostringcp Custom Order Number, passed in when placing an order, the customId parameter in the tj.requestPayment method
3.3.2.3 Response​
Parameter NameTypeDescription
statusstringPayment status. "success" indicates successful payment and coin arrival, "unsuccess" indicates payment failure
3.3.2.4 Authentication​

The request must include the server token agreed upon by both parties, placed in the Authorization header, with a format like

Bearer {service_token}
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.2.1.5 Response​

The host server returns a 200 status code as a successful response. If the returned HTTP status code is not 200, it indicates that the notification has failed and a retry is required.

3.2.2 Active Query Order Interface​

Server-side payment callbacks may fail to reach 100% due to reasons such as network anomalies. Developers can use this interface to actively check the order status to prevent over-delivery or under-delivery. Developers send a query order request to the host server, which then encapsulates and sends the query order request to the platform server.

3.2.2.1 Interface Address​

Provided by the platform

3.2.2.2 Request Parameters​
Parameter NameTypeDescription
tokenstringThe two parties agree on the server token
ordernostringcp Custom Order Number, passed in when placing an order, the customId parameter in the tj.requestPayment method
3.2.2.3 Response​
Parameter NameTypeDescription
statusstringPayment status. "success" indicates successful payment and coin arrival, "unsuccess" indicates payment failure
3.2.2.4 Authentication​

The request must include the server token agreed upon by both parties, placed in the Authorization header, with a format like

Bearer {service_token}

3.4 Payment Interface Integration Example​

payment.js Example

// payment.js - Custom payment implementation

// Override tj.requestPayment
tj.requestPayment = function(obj) {
tj.customCommand('platformPayment', obj);
};

// Override tj.requestVirtualPayment
tj.requestVirtualPayment = function(obj) {
tj.customCommand('virtualPayment', obj);
};

Implement payment logic in the host

const { TJConstants } = require('webglhost-runtime-pc');
const path = require('path');

// Setup payment options
const options = {};
options[TJConstants.LAUNCH_KEY] = 'launch-key';
options[TJConstants.USER_ID] = 'user-123';
options[TJConstants.CUSTOM_SCRIPT_FILES] = [
path.join(__dirname, 'custom-scripts', 'payment.js')
];

await gameHandle.setGameStartOptions(options);

// Setup payment listeners
gameHandle.setCustomCommandListener('platformPayment', (request, handle) => {
// Call your payment SDK
yourPaymentSDK.requestPayment({
orderId: request.orderId,
amount: request.amount,
description: request.description
})
.then(result => {
handle.success({
transactionId: result.transactionId,
status: 'success'
});
})
.catch(error => {
handle.fail(40001, error.message);
});
});

gameHandle.setCustomCommandListener('virtualPayment', (request, handle) => {
// Handle virtual payment
yourPaymentSDK.requestVirtualPayment(request)
.then(result => {
handle.success(result);
})
.catch(error => {
handle.fail(40002, error.message);
});
});

4 Complete Platform Integration Example​

const { TJHostHandle, TJConstants } = require('webglhost-runtime-pc');
const path = require('path');

class PlatformIntegrationManager {
constructor(gameHandle) {
this.gameHandle = gameHandle;
this.setupAllListeners();
}

setupAllListeners() {
this.setupAuthListeners();
this.setupAdListeners();
this.setupPaymentListeners();
}

// Authentication
setupAuthListeners() {
this.gameHandle.setCustomCommandListener('platformLogin', (request, handle) => {
this.handleLogin(request, handle);
});

this.gameHandle.setCustomCommandListener('checkSession', (request, handle) => {
this.handleCheckSession(request, handle);
});
}

async handleLogin(request, handle) {
try {
const result = await yourPlatformSDK.login();
handle.success({
code: result.authCode,
userId: result.userId
});
} catch (error) {
handle.fail(20001, error.message);
}
}

async handleCheckSession(request, handle) {
try {
await yourPlatformSDK.checkSession();
handle.success({});
} catch (error) {
handle.fail(20002, 'Session expired');
}
}

// Ads
setupAdListeners() {
this.gameHandle.setCustomCommandListener('loadRewardAd', (request, handle) => {
this.handleLoadAd(request, handle);
});

this.gameHandle.setCustomCommandListener('showRewardAd', (request, handle) => {
this.handleShowAd(request, handle);
});
}

async handleLoadAd(request, handle) {
try {
await yourAdSDK.loadAd(request.adUnitId);
handle.success({});
this.notifyAdLoaded();
} catch (error) {
handle.fail(30001, error.message);
}
}

async handleShowAd(request, handle) {
try {
const result = await yourAdSDK.showAd(request.adUnitId);
handle.success({});
this.notifyAdClosed(result.isEnded);
} catch (error) {
handle.fail(30002, error.message);
}
}

notifyAdLoaded() {
this.gameHandle.runCustomScript('rewardedVideoLoadCallback();', () => {});
}

notifyAdClosed(isEnded) {
this.gameHandle.runCustomScript(
`rewardedVideoCloseCallback({ isEnded: ${isEnded} });`,
() => {}
);
}

// Payment
setupPaymentListeners() {
this.gameHandle.setCustomCommandListener('platformPayment', (request, handle) => {
this.handlePayment(request, handle);
});
}

async handlePayment(request, handle) {
try {
const result = await yourPaymentSDK.requestPayment(request);
handle.success(result);
} catch (error) {
handle.fail(40001, error.message);
}
}
}

// Initialize with all platform integrations
async function initializeWithPlatformIntegration() {
const hostHandle = await new Promise((resolve, reject) => {
TJHostHandle.initialize('sdk-key', 'sdk-secret', (handle, error) => {
if (error) reject(error);
else resolve(handle);
});
});

const gameHandle = await hostHandle.createGameHandle();

// Initialize platform integration manager
const platformManager = new PlatformIntegrationManager(gameHandle);

// Setup game options with all custom scripts
const options = {
[TJConstants.LAUNCH_KEY]: 'launch-key',
[TJConstants.USER_ID]: 'user-123',
[TJConstants.CUSTOM_SCRIPT_FILES]: [
path.join(__dirname, 'custom-scripts', 'auth.js'),
path.join(__dirname, 'custom-scripts', 'ad.js'),
path.join(__dirname, 'custom-scripts', 'payment.js')
]
};

await gameHandle.setGameStartOptions(options);
await gameHandle.start();
await gameHandle.play();

return { hostHandle, gameHandle, platformManager };
}

// Usage
initializeWithPlatformIntegration()
.then(({ gameHandle }) => {
console.log('Platform integration initialized successfully');
})
.catch(error => {
console.error('Platform integration failed:', error);
});