跳转到内容

支付

游戏内玩家支付的逻辑涉及玩家使用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;

Swipe & Play Endless Game Together