支付
游戏内玩家支付的逻辑涉及玩家使用GameTok APP货币购买游戏内物品。
1. 配置商品
进入GameTok开发者中心 Operations -> Products
页面, 点击 Add New Product,
并填写以下信息:
- 商品名称:产品的名称
- 商品标识:产品的唯一标识符。用于在游戏中识别产品;请确保它是唯一的。
- 商品价格:产品的价格,以GameTok货币“金币(Coins)”计价。“金币(Coins)”与美元的兑换比例为
100:1
.
2. 获取App ID和App Key
进入GameTok开发者中心 Deployment -> Configuration
页面获取App ID和App Key。
3. 服务端API验证与支付
GameTok服务端API的接入地址是: https://game-gateway.lobah.net
使用服务端API的第一步是执行签名算法,然后调用购买接口进行支付。更多详情请参阅 下一章中 全部API参考
部分。
4. 示例代码
以下是Java、PHP、Go语言、NodeJS(服务端)等编程语言调用服务端API的代码示例:
4.1 Java
java
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.time.Instant;
import java.util.Map;
import java.util.TreeMap;
import java.util.UUID;
public class Demo {
private static final String appKey = ""; // Configuration -> appKey
private static final String accessToken = ""; // params.token
private static final String appId = ""; // params.gameId
private static final String sessionId = ""; // params.sessionId
private static final String uid = ""; // params.uid
private static final String productId = ""; // Product Identification
private static final String method = "POST";
private static final String host = "https://game-gateway.lobah.net";
public static void main(String[] args) throws Exception {
test();
getProfile();
purchase();
}
public static void test() throws Exception {
String path = "/1.0/open-gateway/game/test";
String postBody = "";
httpPost(path, postBody);
}
public static void purchase() throws Exception {
String referenceId = UUID.randomUUID().toString().replace("-", "").substring(0, 12);
String path = "/1.0/open-gateway/game/purchase";
String postBody = "{ \"app_id\": " + appId + ", " + " \"product_id\": \"" + productId + "\", \"reference_id\": \"" + referenceId + "\", \"session_id\": " + sessionId + ", \"uid\": " + uid + " }";
httpPost(path, postBody);
}
public static void getProfile() throws Exception {
String path = "/1.0/open-gateway/game/get-profile";
String postBody = "{\"app_id\": " + appId + ", \"token\": \"" + accessToken + "\", \"uid\": " + uid + " }";
httpPost(path, postBody);
}
public static void httpPost(String requestPath, String postBody) throws IOException, NoSuchAlgorithmException, InvalidKeyException {
Map<String, String> reqParamsMap = new TreeMap<>();
reqParamsMap.put("access_token", accessToken);
reqParamsMap.put("app_id", appId);
reqParamsMap.put("nonce", UUID.randomUUID().toString().replace("-", "").substring(0, 8));
reqParamsMap.put("ts", String.valueOf(Instant.now().getEpochSecond()));
reqParamsMap.put("uid", uid);
reqParamsMap.put("zone", "SA");
String reqParam = encodeURL(reqParamsMap);
String encodeStr = method + requestPath + reqParam + postBody;
String urlEncodedData = URLEncoder.encode(encodeStr, "UTF-8");
String digest = genDigest(urlEncodedData, appKey, "HmacMD5");
System.out.println("Encode String: " + encodeStr);
System.out.println("Encode Data: " + urlEncodedData);
System.out.println("Digest: " + digest);
httpPostWithSig(host + requestPath + "?" + reqParam + "&sig=" + digest, postBody);
}
private static void httpPostWithSig(String requestUrl, String requestBody) throws IOException {
System.out.println("Request Url: " + requestUrl);
URL url = new URL(requestUrl);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod(method);
connection.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64)");
connection.setRequestProperty("Content-Type", "application/json; utf-8");
connection.setRequestProperty("Accept", "application/json");
connection.setDoOutput(true);
try (OutputStream os = connection.getOutputStream()) {
byte[] input = requestBody.getBytes(StandardCharsets.UTF_8);
os.write(input, 0, input.length);
}
int responseCode = connection.getResponseCode();
System.out.println("Response Code: " + responseCode);
try (BufferedReader br = new BufferedReader(new InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_8))) {
StringBuilder response = new StringBuilder();
String responseLine;
while ((responseLine = br.readLine()) != null) {
response.append(responseLine.trim());
}
System.out.println("Response Body: " + response);
}
connection.disconnect();
}
private static String genDigest(String msg, String keyString, String algo) throws NoSuchAlgorithmException, InvalidKeyException {
SecretKeySpec key = new SecretKeySpec((keyString).getBytes(StandardCharsets.UTF_8), algo);
Mac mac = Mac.getInstance(algo);
mac.init(key);
byte[] bytes = mac.doFinal(msg.getBytes(StandardCharsets.US_ASCII));
StringBuilder hash = new StringBuilder();
for (byte aByte : bytes) {
String hex = Integer.toHexString(0xFF & aByte);
if (hex.length() == 1) {
hash.append('0');
}
hash.append(hex);
}
return hash.toString();
}
private static String encodeURL(Map<String, String> parameters) {
StringBuilder result = new StringBuilder(2048);
for (String encodedName : parameters.keySet()) {
String encodedValue = parameters.get(encodedName);
if (result.length() > 0) {
result.append('&');
}
result.append(encodedName);
if (encodedValue != null) {
result.append("=");
result.append(encodedValue);
}
}
return result.toString();
}
}
4.2 PHP
php
<?php
// 使用示例
function main() {
$client = new GameAPIClient();
// 设置配置(请替换为你的实际配置)
$client->setConfig(
'', // Configuration -> appKey
'', // params.token
'', // params.gameId
'', // params.sessionId
'', // params.uid
'' // Product Identification
);
try {
echo "=== 测试API ===\n";
$client->test();
echo "\n=== 获取用户资料 ===\n";
$client->getProfile();
echo "\n=== 购买商品 ===\n";
$client->purchase();
} catch (Exception $e) {
echo "执行失败: " . $e->getMessage() . "\n";
}
}
class GameAPIClient {
private $config;
private $host = 'https://game-gateway.lobah.net';
private $method = 'POST';
private $zone = 'SA';
public function __construct($config = []) {
$this->config = array_merge([
'appKey' => '',
'accessToken' => '',
'appId' => '',
'sessionId' => '',
'uid' => '',
'productId' => ''
], $config);
}
public function setConfig($appKey, $accessToken, $appId, $sessionId, $uid, $productId) {
$this->config = [
'appKey' => $appKey,
'accessToken' => $accessToken,
'appId' => $appId,
'sessionId' => $sessionId,
'uid' => $uid,
'productId' => $productId
];
}
public function test() {
$path = '/1.0/open-gateway/game/test';
$postBody = '';
return $this->httpPost($path, $postBody);
}
public function purchase() {
$referenceId = $this->generateReferenceID();
$path = '/1.0/open-gateway/game/purchase';
$postBody = json_encode([
'app_id' => $this->config['appId'],
'product_id' => $this->config['productId'],
'reference_id' => $referenceId,
'session_id' => $this->config['sessionId'],
'uid' => $this->config['uid']
]);
return $this->httpPost($path, $postBody);
}
public function getProfile() {
$path = '/1.0/open-gateway/game/get-profile';
$postBody = json_encode([
'app_id' => $this->config['appId'],
'token' => $this->config['accessToken'],
'uid' => $this->config['uid']
]);
return $this->httpPost($path, $postBody);
}
private function httpPost($requestPath, $postBody) {
// 构建请求参数
$reqParams = [
'access_token' => $this->config['accessToken'],
'app_id' => $this->config['appId'],
'nonce' => $this->generateNonce(),
'ts' => (string) time(),
'uid' => $this->config['uid'],
'zone' => $this->zone
];
// 排序参数
ksort($reqParams);
$reqParam = http_build_query($reqParams, '', '&');
// 构建签名字符串
$encodeStr = $this->method . $requestPath . $reqParam . $postBody;
$urlEncodedData = urlencode($encodeStr);
$digest = $this->genDigest($urlEncodedData, $this->config['appKey'], 'md5');
echo "Encode String: " . $encodeStr . "\n";
echo "Encode Data: " . $urlEncodedData . "\n";
echo "Digest: " . $digest . "\n";
$finalURL = $this->host . $requestPath . '?' . $reqParam . '&sig=' . $digest;
return $this->httpPostWithSig($finalURL, $postBody);
}
private function httpPostWithSig($requestUrl, $requestBody) {
echo "Request Url: " . $requestUrl . "\n";
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $requestUrl,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $requestBody,
CURLOPT_HTTPHEADER => [
'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64)',
'Content-Type: application/json; utf-8',
'Accept: application/json',
'Content-Length: ' . strlen($requestBody)
],
CURLOPT_TIMEOUT => 30,
CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_SSL_VERIFYHOST => false
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if (curl_errno($ch)) {
$error = curl_error($ch);
curl_close($ch);
throw new Exception("CURL Error: " . $error);
}
curl_close($ch);
echo "Response Code: " . $httpCode . "\n";
echo "Response Body: " . $response . "\n";
return [
'statusCode' => $httpCode,
'body' => $response
];
}
private function genDigest($msg, $key, $algo) {
if ($algo === 'md5') {
return hash_hmac('md5', $msg, $key);
}
return hash($algo, $msg);
}
private function generateNonce() {
$chars = '0123456789abcdef';
$result = '';
for ($i = 0; $i < 8; $i++) {
$result .= $chars[rand(0, strlen($chars) - 1)];
}
return $result;
}
private function generateReferenceID() {
$chars = '0123456789abcdef';
$result = '';
for ($i = 0; $i < 12; $i++) {
$result .= $chars[rand(0, strlen($chars) - 1)];
}
return $result;
}
private function urlencode($str) {
return urlencode($str);
}
}
// 如果直接运行此文件
if (basename(__FILE__) === basename($_SERVER['PHP_SELF'])) {
main();
}
// 返回客户端实例(供其他文件使用)
return new GameAPIClient();
4.3 Go
go
package main
import (
"bytes"
"crypto/hmac"
"crypto/md5"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"sort"
"strings"
"time"
)
// Config 配置信息
type Config struct {
AppKey string
AccessToken string
AppID string
SessionID string
UID string
ProductID string
Host string
Method string
Zone string
}
// Client API客户端
type Client struct {
config *Config
client *http.Client
}
// 使用示例
func main() {
client := NewClient()
// 设置配置(请替换为你的实际配置)
client.SetConfig(
"", // Configuration -> appKey
"", // params.token
"", // params.gameId
"", // params.sessionId
"", // params.uid
"", // Product Identification
)
// 运行测试
fmt.Println("=== 测试API ===")
if err := client.Test(); err != nil {
fmt.Printf("测试失败: %v\n", err)
}
fmt.Println("\n=== 获取用户资料 ===")
if err := client.GetProfile(); err != nil {
fmt.Printf("获取用户资料失败: %v\n", err)
}
fmt.Println("\n=== 购买商品 ===")
if err := client.Purchase(); err != nil {
fmt.Printf("购买失败: %v\n", err)
}
}
// NewClient 创建新的API客户端
func NewClient() *Client {
return &Client{
config: &Config{
AppKey: "", // Configuration -> appKey
AccessToken: "", // params.token
AppID: "", // params.gameId
SessionID: "", // params.sessionId
UID: "", // params.uid
ProductID: "", // Product Identification
Host: "https://game-gateway.lobah.net",
Method: "POST",
Zone: "SA",
},
client: &http.Client{Timeout: 30 * time.Second},
}
}
// SetConfig 设置配置
func (c *Client) SetConfig(appKey, accessToken, appID, sessionID, uid, productID string) {
c.config.AppKey = appKey
c.config.AccessToken = accessToken
c.config.AppID = appID
c.config.SessionID = sessionID
c.config.UID = uid
c.config.ProductID = productID
}
// Test 测试API
func (c *Client) Test() error {
path := "/1.0/open-gateway/game/test"
postBody := ""
return c.httpPost(path, postBody)
}
// Purchase 购买商品
func (c *Client) Purchase() error {
referenceID := generateReferenceID()
path := "/1.0/open-gateway/game/purchase"
requestBody := map[string]interface{}{
"app_id": c.config.AppID,
"product_id": c.config.ProductID,
"reference_id": referenceID,
"session_id": c.config.SessionID,
"uid": c.config.UID,
}
jsonBody, _ := json.Marshal(requestBody)
return c.httpPost(path, string(jsonBody))
}
// GetProfile 获取用户资料
func (c *Client) GetProfile() error {
path := "/1.0/open-gateway/game/get-profile"
requestBody := map[string]interface{}{
"app_id": c.config.AppID,
"token": c.config.AccessToken,
"uid": c.config.UID,
}
jsonBody, _ := json.Marshal(requestBody)
return c.httpPost(path, string(jsonBody))
}
// httpPost 发送HTTP POST请求
func (c *Client) httpPost(requestPath string, postBody string) error {
// 构建请求参数
reqParams := map[string]string{
"access_token": c.config.AccessToken,
"app_id": c.config.AppID,
"nonce": generateNonce(),
"ts": fmt.Sprintf("%d", time.Now().Unix()),
"uid": c.config.UID,
"zone": c.config.Zone,
}
// 排序参数
keys := make([]string, 0, len(reqParams))
for k := range reqParams {
keys = append(keys, k)
}
sort.Strings(keys)
// 构建参数字符串
var paramBuilder strings.Builder
for i, k := range keys {
if i > 0 {
paramBuilder.WriteString("&")
}
paramBuilder.WriteString(k)
paramBuilder.WriteString("=")
paramBuilder.WriteString(reqParams[k])
}
reqParam := paramBuilder.String()
// 构建签名字符串
encodeStr := c.config.Method + requestPath + reqParam + postBody
urlEncodedData := url.QueryEscape(encodeStr)
digest := genDigest(urlEncodedData, c.config.AppKey, "HmacMD5")
fmt.Printf("Encode String: %s\n", encodeStr)
fmt.Printf("Encode Data: %s\n", urlEncodedData)
fmt.Printf("Digest: %s\n", digest)
// 构建最终URL
finalURL := fmt.Sprintf("%s%s?%s&sig=%s", c.config.Host, requestPath, reqParam, digest)
return c.httpPostWithSig(finalURL, postBody)
}
// httpPostWithSig 发送带签名的HTTP请求
func (c *Client) httpPostWithSig(requestURL string, requestBody string) error {
fmt.Printf("Request Url: %s\n", requestURL)
req, err := http.NewRequest(c.config.Method, requestURL, bytes.NewBufferString(requestBody))
if err != nil {
return fmt.Errorf("创建请求失败: %v", err)
}
req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64)")
req.Header.Set("Content-Type", "application/json; utf-8")
req.Header.Set("Accept", "application/json")
resp, err := c.client.Do(req)
if err != nil {
return fmt.Errorf("发送请求失败: %v", err)
}
defer resp.Body.Close()
fmt.Printf("Response Code: %d\n", resp.StatusCode)
body, err := io.ReadAll(resp.Body)
if err != nil {
return fmt.Errorf("读取响应失败: %v", err)
}
fmt.Printf("Response Body: %s\n", string(body))
return nil
}
// genDigest 生成HMAC签名
func genDigest(msg string, key string, algo string) string {
mac := hmac.New(md5.New, []byte(key))
mac.Write([]byte(msg))
return hex.EncodeToString(mac.Sum(nil))
}
// generateNonce 生成随机nonce
func generateNonce() string {
const charset = "0123456789abcdef"
b := make([]byte, 8)
for i := range b {
b[i] = charset[time.Now().UnixNano()%int64(len(charset))]
}
return string(b)
}
// generateReferenceID 生成reference_id
func generateReferenceID() string {
const charset = "0123456789abcdef"
b := make([]byte, 12)
for i := range b {
b[i] = charset[time.Now().UnixNano()%int64(len(charset))]
}
return string(b)
}
4.4 NodeJS
js
const crypto = require('crypto');
const https = require('https');
// 使用示例
async function main() {
const client = new GameAPIClient(config);
// 设置配置(请替换为你的实际配置)
client.setConfig(
'', // Configuration -> appKey
'', // params.token
'', // params.gameId
'', // params.sessionId
'', // params.uid
'' // Product Identification
);
try {
console.log('=== 测试API ===');
await client.test();
console.log('\n=== 获取用户资料 ===');
await client.getProfile();
console.log('\n=== 购买商品 ===');
await client.purchase();
} catch (error) {
console.error('执行失败:', error);
}
}
// 配置信息
const config = {
appKey: '', // Configuration -> appKey
accessToken: '', // params.token
appId: '', // params.gameId
sessionId: '', // params.sessionId
uid: '', // params.uid
productId: '', // Product Identification
method: 'POST',
host: 'game-gateway.lobah.net',
zone: 'SA'
};
// API客户端类
class GameAPIClient {
constructor(config = {}) {
this.config = { ...config };
}
// 设置配置
setConfig(appKey, accessToken, appId, sessionId, uid, productId) {
this.config.appKey = appKey;
this.config.accessToken = accessToken;
this.config.appId = appId;
this.config.sessionId = sessionId;
this.config.uid = uid;
this.config.productId = productId;
}
// 测试API
async test() {
const path = '/1.0/open-gateway/game/test';
const postBody = '';
return await this.httpPost(path, postBody);
}
// 购买商品
async purchase() {
const referenceId = this.generateReferenceID();
const path = '/1.0/open-gateway/game/purchase';
const postBody = JSON.stringify({
app_id: this.config.appId,
product_id: this.config.productId,
reference_id: referenceId,
session_id: this.config.sessionId,
uid: this.config.uid
});
return await this.httpPost(path, postBody);
}
// 获取用户资料
async getProfile() {
const path = '/1.0/open-gateway/game/get-profile';
const postBody = JSON.stringify({
app_id: this.config.appId,
token: this.config.accessToken,
uid: this.config.uid
});
return await this.httpPost(path, postBody);
}
// HTTP POST请求
async httpPost(requestPath, postBody) {
// 构建请求参数
const reqParams = {
access_token: this.config.accessToken,
app_id: this.config.appId,
nonce: this.generateNonce(),
ts: Math.floor(Date.now() / 1000).toString(),
uid: this.config.uid,
zone: this.config.zone
};
// 排序参数
const sortedKeys = Object.keys(reqParams).sort();
const paramPairs = [];
for (const key of sortedKeys) {
paramPairs.push(`${key}=${reqParams[key]}`);
}
const reqParam = paramPairs.join('&');
// 构建签名字符串
const encodeStr = this.config.method + requestPath + reqParam + postBody;
const urlEncodedData = encodeURIComponent(encodeStr);
const digest = this.genDigest(urlEncodedData, this.config.appKey, 'md5');
console.log('Encode String:', encodeStr);
console.log('Encode Data:', urlEncodedData);
console.log('Digest:', digest);
// 构建最终URL
const finalURL = `https://${this.config.host}${requestPath}?${reqParam}&sig=${digest}`;
return await this.httpPostWithSig(finalURL, postBody);
}
// 发送带签名的HTTP请求
async httpPostWithSig(requestURL, requestBody) {
console.log('Request Url:', requestURL);
const url = new URL(requestURL);
const options = {
hostname: url.hostname,
port: url.port || 443,
path: url.pathname + url.search,
method: this.config.method,
headers: {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)',
'Content-Type': 'application/json; utf-8',
'Accept': 'application/json',
'Content-Length': Buffer.byteLength(requestBody)
}
};
return new Promise((resolve, reject) => {
const req = https.request(options, (res) => {
console.log('Response Code:', res.statusCode);
let responseData = '';
res.on('data', (chunk) => {
responseData += chunk;
});
res.on('end', () => {
console.log('Response Body:', responseData);
resolve({
statusCode: res.statusCode,
body: responseData
});
});
});
req.on('error', (error) => {
console.error('Request failed:', error);
reject(error);
});
req.write(requestBody);
req.end();
});
}
// 生成HMAC签名
genDigest(msg, key, algo) {
return crypto
.createHmac(algo, key)
.update(msg, 'ascii')
.digest('hex');
}
// 生成随机nonce
generateNonce() {
const chars = '0123456789abcdef';
let result = '';
for (let i = 0; i < 8; i++) {
result += chars[Math.floor(Math.random() * chars.length)];
}
return result;
}
// 生成reference_id
generateReferenceID() {
const chars = '0123456789abcdef';
let result = '';
for (let i = 0; i < 12; i++) {
result += chars[Math.floor(Math.random() * chars.length)];
}
return result;
}
}
// 如果直接运行此文件
if (require.main === module) {
main();
}
// 导出供其他模块使用
module.exports = GameAPIClient;