Skip to content

通信协议

环境地址

  • 接口地址前缀:请联系官方团队获取

接入授权

  • 商户在接入前需要向官方申请接入授权,包含以下内容:

    • 基本信息
    • 接口域名
    • 白名单 IP
  • 在申请通过后,官方将发放接入授权给商户,包含以下内容:

    • 商户编号 (​merchant-id​):唯一数字编号
    • 商户授权私钥 (​secret​):协议加密密钥,32 位长度字符串
  • 以上授权信息将被适用于商户服务器与官方服务器的接口交互中,商户需要妥善保管,且秘钥信息应当仅存在于服务器中。

  • 授权信息遗失或者泄露,可以向官方申请更换,更换授权不影响已有数据(用户、资金、订单等)的使用

通信规范

加密算法

1. 概述

游戏对接 API 中使用的加密方案,采用 AES-256-CBC 模式结合 PKCS7 填充进行数据加密与解密。API 请求中,所有业务数据需要进行加密处理后通过参数 x 传递。

2. 加密算法详情

  • 加密算法​:AES (Advanced Encryption Standard)
  • 密钥长度​:256 位 (32 字节)
  • 加密模式​:CBC (Cipher Block Chaining)
  • 填充方式​:PKCS7
  • 初始化向量(IV):16 字节,需与每次请求一起传递或使用约定方式生成

3. 请求格式说明

3.2 请求体格式

所有请求的 header 中需要包含以下参数:

  • merchant-id: 商户编号,用于标识接入的商户身份

所有请求的 body 必须包含以下公共参数后进行加密:

  • timestamp: int64 类型,表示当前时间戳,精确到毫秒,13 位
  • request_id: string 类型,唯一请求 ID

这些公共参数与业务参数一起组成 JSON 对象,加密后通过单一参数x发送:

json
{
  "x": "加密后的Base64字符串"
}
3.2 示例流程
  1. 设置请求头:
TEXT
merchant-id: M202405120001
  1. 准备原始请求数据:
json
{
  "timestamp": 1650123456789,
  "request_id": "abcd-1234-abcd-1234",
  "username": "game001",
  "user_id": "user123",
  "amount": 100
}
  1. 将原始 JSON 加密后,得到请求体:
json
{
  "x": "hG7bVwMRV0Gq+Lh5HvdkE1jBJxHIQcm9z3r4XBY/5r8tXKr1xU0MCvTgn12..."
}

4. 加密流程

  1. 准备包含公共参数和业务参数的完整 JSON 数据
  2. 使用 PKCS7 填充方式将数据填充至 16 字节的倍数
  3. 使用 32 位密钥和初始化向量(IV,密钥的前 16 位)通过 AES-CBC 模式加密数据
  4. 将加密后的数据进行 Base64 编码,得到参数x的值
  5. 构造最终请求体{"x": "加密后的Base64字符串"}
  6. 在 HTTP 头部添加 merchant-id

5. 解密流程

  1. 从请求体中提取参数x的值
  2. 对密文进行 Base64 解码
  3. 使用相同的 32 位密钥和初始化向量(IV, 密钥的前 16 位)通过 AES-CBC 模式解密数据
  4. 去除 PKCS7 填充,得到原始 JSON 数据
  5. 解析 JSON 数据,提取公共参数和业务参数

6. 代码示例

6.1 Java 实现
java
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;

public class APIRequestExample {

    private static final String SECRET_KEY = "32位密钥,需要和服务端一致";
    private static final String IV = "16位初始向量";
    private static final String MERCHANT_ID = "M202405120001";
    private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();

    public static void sendRequest(String apiUrl, Map<String, Object> businessParams) throws Exception {
        // 添加公共参数
        businessParams.put("timestamp", System.currentTimeMillis());
        businessParams.put("request_id", generateRequestId());

        // 将完整参数转为JSON
        String jsonData = OBJECT_MAPPER.writeValueAsString(businessParams);

        // 加密
        String encryptedData = encrypt(jsonData, SECRET_KEY, IV);

        // 构造最终请求体
        Map<String, String> requestBody = new HashMap<>();
        requestBody.put("x", encryptedData);
        String finalRequestBody = OBJECT_MAPPER.writeValueAsString(requestBody);

        // 发送HTTP请求
        try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
            HttpPost httpPost = new HttpPost(apiUrl);

            // 设置请求头
            httpPost.setHeader("Content-Type", "application/json");
            httpPost.setHeader("merchant-id", MERCHANT_ID);

            // 设置请求体
            StringEntity entity = new StringEntity(finalRequestBody);
            httpPost.setEntity(entity);

            // 执行请求
            httpClient.execute(httpPost);
        }
    }

    private static String encrypt(String plainText, String key, String iv) throws Exception {
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        SecretKeySpec keySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES");
        IvParameterSpec ivSpec = new IvParameterSpec(iv.getBytes("UTF-8"));
        cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);
        byte[] encrypted = cipher.doFinal(plainText.getBytes("UTF-8"));
        return Base64.getEncoder().encodeToString(encrypted);
    }

    private static String generateRequestId() {
        return "req_" + System.nanoTime();
    }

    public static void main(String[] args) throws Exception {
        Map<String, Object> params = new HashMap<>();
        params.put("game_id", "game001");
        params.put("user_id", "user123");
        params.put("amount", 100);

        sendRequest("https://api.example.com/game/action", params);
    }
}
6.2 PHP 实现
php
function sendRequest($apiUrl, $businessParams) {
    // 添加公共参数
    $businessParams['timestamp'] = round(microtime(true) * 1000);
    $businessParams['request_id'] = 'req_' . uniqid();

    // 转为JSON
    $jsonData = json_encode($businessParams);

    // 加密
    $key = "32位密钥,需要和服务端一致";
    $iv = "16位初始向量";
    $encryptedData = encrypt($jsonData, $key, $iv);

    // 构造最终请求体
    $requestBody = ['x' => $encryptedData];
    $finalRequestBody = json_encode($requestBody);

    // 设置HTTP头
    $headers = [
        'Content-Type: application/json',
        'merchant-id: M202405120001'
    ];

    // 发送请求
    $ch = curl_init($apiUrl);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_POSTFIELDS, $finalRequestBody);
    $response = curl_exec($ch);
    curl_close($ch);

    return $response;
}

function encrypt($plaintext, $key, $iv) {
    $method = "AES-256-CBC";
    $encrypted = openssl_encrypt($plaintext, $method, $key, OPENSSL_RAW_DATA, $iv);
    return base64_encode($encrypted);
}

// 使用示例
$params = [
    'game_id' => 'game001',
    'user_id' => 'user123',
    'amount' => 100
];

$response = sendRequest("https://api.example.com/game/action", $params);
echo $response;
6.3 Node.js 实现
javascript
const crypto = require('crypto')
const axios = require('axios')

async function sendRequest(apiUrl, businessParams) {
  // 添加公共参数
  businessParams.timestamp = Date.now()
  businessParams.request_id = 'req_' + Date.now() + Math.random().toString(36).substring(2, 10)

  // 转为JSON
  const jsonData = JSON.stringify(businessParams)

  // 加密
  const key = '32位密钥,需要和服务端一致'
  const iv = '16位初始向量'
  const encryptedData = encrypt(jsonData, key, iv)

  // 构造最终请求体
  const requestBody = { x: encryptedData }

  // 设置HTTP头
  const headers = {
    'Content-Type': 'application/json',
    'merchant-id': 'M202405120001',
  }

  // 发送请求
  try {
    const response = await axios.post(apiUrl, requestBody, { headers })
    return response.data
  } catch (error) {
    console.error('Request failed:', error)
    throw error
  }
}

function encrypt(plaintext, key, iv) {
  const cipher = crypto.createCipheriv('aes-256-cbc', Buffer.from(key), Buffer.from(iv))
  let encrypted = cipher.update(plaintext, 'utf8', 'base64')
  encrypted += cipher.final('base64')
  return encrypted
}

// 使用示例
async function test() {
  const params = {
    game_id: 'game001',
    user_id: 'user123',
    amount: 100,
  }

  try {
    const response = await sendRequest('https://api.example.com/game/action', params)
    console.log('Response:', response)
  } catch (error) {
    console.error('Error:', error)
  }
}

test()

7. 注意事项

  1. HTTP 请求头必须包含 merchant-id 商户编号
  2. 公共参数timestamprequest_id是必须的
  3. timestamp必须是 13 位毫秒级时间戳
  4. request_id必须保证唯一性
  5. 密钥长度必须为 32 字节(256 位)
  6. IV 长度必须为 16 字节(128 位)
  7. 确保所有业务参数与公共参数一起加密
  8. 最终请求体只包含单个参数x
  9. GET 请求的接口,是路由参数的话,最终加密的数据需要 url 编码

8. 错误处理

常见错误及处理方法:

  1. 密钥不正确 - 确认商户密钥是否正确
  2. IV 不正确 - 确认 IV 是否正确
  3. merchant-id 不存在 - 确认 HTTP 头部是否正确设置
  4. 时间戳过期 - 确保系统时间准确
  5. 请求 ID 重复 - 确保每次请求生成唯一 ID

返回示例

所有哈希游戏提供的接口,均使用如下标准返回。

json
{
  "code": 0,
  "msg": "success",
  "data": {}
}
  • code: 返回码,0 为成功,不等于 0 表示发生错误。
  • msg: 返回信息,当 code != 0 时,msg 包含了错误信息。
  • data: 当成功返回存在数据内容时,会以对象或者数组的方式存在于 data 属性中;当无数据返回需要时,data 属性不存在。