Payment
The logic for player payments within the game involves players using Lobah APP currency to purchase in-game items.
1. Configure Products
Go to the Lobah Developer Center Operations -> Products
page, click Add New Product,
and fill in the following information:
- Product name: Name of the product
- Product identification: Unique identifier for the product. This is used to identify products within the game; please ensure it is distinct.
- Product price: Price of the product, in Lobah currency Coins. The ratio of Coins to USD is
100:1
.
2. Obtain App ID and App Key
Go to the Lobah Developer Center Deployment -> Configuration
page to obtain the App ID and App Key.
3. Server API Validation and Payment
The Lobah Lobah Server API endpoint is: https://game-gateway.lobah.net
The first step in using the Server API is to perform a signature algorithm, then call the Purchase interface to make the payment. For more details, refer to the Authentication
section in the API Reference
documentation.
4. Sample Code
The following are code examples for calling the Server API in programming languages such as Java, PHP, Go, and NodeJS (server-side):
4.1 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
// Usage example
function main() {
$client = new GameAPIClient();
// Set configuration (replace with your actual configuration)
$client->setConfig(
'', // Configuration -> appKey
'', // params.token
'', // params.gameId
'', // params.sessionId
'', // params.uid
'' // Product Identification
);
try {
echo "=== Test API ===\n";
$client->test();
echo "\n=== Get User Profile ===\n";
$client->getProfile();
echo "\n=== Purchase Product ===\n";
$client->purchase();
} catch (Exception $e) {
echo "Execution failed: " . $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) {
// Build request parameters
$reqParams = [
'access_token' => $this->config['accessToken'],
'app_id' => $this->config['appId'],
'nonce' => $this->generateNonce(),
'ts' => (string) time(),
'uid' => $this->config['uid'],
'zone' => $this->zone
];
// Sort parameters
ksort($reqParams);
$reqParam = http_build_query($reqParams, '', '&');
// Build signature string
$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);
}
}
// Run this file directly
if (basename(__FILE__) === basename($_SERVER['PHP_SELF'])) {
main();
}
// Return client instance (for other files to use)
return new GameAPIClient();
4.3 Go
package main
import (
"bytes"
"crypto/hmac"
"crypto/md5"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"sort"
"strings"
"time"
)
// Config configuration information
type Config struct {
AppKey string
AccessToken string
AppID string
SessionID string
UID string
ProductID string
Host string
Method string
Zone string
}
// Client API client
type Client struct {
config *Config
client *http.Client
}
// Usage example
func main() {
client := NewClient()
// Set configuration (replace with your actual configuration)
client.SetConfig(
"", // Configuration -> appKey
"", // params.token
"", // params.gameId
"", // params.sessionId
"", // params.uid
"", // Product Identification
)
// Run tests
fmt.Println("=== Test API ===")
if err := client.Test(); err != nil {
fmt.Printf("Test failed: %v\n", err)
}
fmt.Println("\n=== Get User Profile ===")
if err := client.GetProfile(); err != nil {
fmt.Printf("Get user profile failed: %v\n", err)
}
fmt.Println("\n=== Purchase Product ===")
if err := client.Purchase(); err != nil {
fmt.Printf("Purchase failed: %v\n", err)
}
}
// NewClient create new API client
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 set configuration
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 test API
func (c *Client) Test() error {
path := "/1.0/open-gateway/game/test"
postBody := ""
return c.httpPost(path, postBody)
}
// Purchase purchase product
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 get user profile
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 send HTTP POST request
func (c *Client) httpPost(requestPath string, postBody string) error {
// Build request parameters
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,
}
// Sort parameters
keys := make([]string, 0, len(reqParams))
for k := range reqParams {
keys = append(keys, k)
}
sort.Strings(keys)
// Build parameter string
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()
// Build signature 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)
// Build final URL
finalURL := fmt.Sprintf("%s%s?%s&sig=%s", c.config.Host, requestPath, reqParam, digest)
return c.httpPostWithSig(finalURL, postBody)
}
// httpPostWithSig send HTTP request with signature
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("create request failed: %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("send request failed: %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("read response failed: %v", err)
}
fmt.Printf("Response Body: %s\n", string(body))
return nil
}
// genDigest generate HMAC signature
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 generate random 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 generate 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
const crypto = require('crypto');
const https = require('https');
// Usage example
async function main() {
const client = new GameAPIClient(config);
// Set configuration (replace with your actual configuration)
client.setConfig(
'', // Configuration -> appKey
'', // params.token
'', // params.gameId
'', // params.sessionId
'', // params.uid
'' // Product Identification
);
try {
console.log('=== Test API ===');
await client.test();
console.log('\n=== Get User Profile ===');
await client.getProfile();
console.log('\n=== Purchase Product ===');
await client.purchase();
} catch (error) {
console.error('Execution failed:', error);
}
}
// Configuration information
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 client class
class GameAPIClient {
constructor(config = {}) {
this.config = { ...config };
}
// Set configuration
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;
}
// Test API
async test() {
const path = '/1.0/open-gateway/game/test';
const postBody = '';
return await this.httpPost(path, postBody);
}
// Purchase product
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);
}
// Get user profile
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 request
async httpPost(requestPath, postBody) {
// Build request parameters
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
};
// Sort parameters
const sortedKeys = Object.keys(reqParams).sort();
const paramPairs = [];
for (const key of sortedKeys) {
paramPairs.push(`${key}=${reqParams[key]}`);
}
const reqParam = paramPairs.join('&');
// Build signature string
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);
// Build final URL
const finalURL = `https://${this.config.host}${requestPath}?${reqParam}&sig=${digest}`;
return await this.httpPostWithSig(finalURL, postBody);
}
// Send signed HTTP request
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();
});
}
// Generate HMAC signature
genDigest(msg, key, algo) {
return crypto
.createHmac(algo, key)
.update(msg, 'ascii')
.digest('hex');
}
// Generate random nonce
generateNonce() {
const chars = '0123456789abcdef';
let result = '';
for (let i = 0; i < 8; i++) {
result += chars[Math.floor(Math.random() * chars.length)];
}
return result;
}
// Generate 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 this file is run directly
if (require.main === module) {
main();
}
// Export for use by other modules
module.exports = GameAPIClient;