> ## Documentation Index
> Fetch the complete documentation index at: https://doc.va777.net/llms.txt
> Use this file to discover all available pages before exploring further.

# 簽章規則

> v1 每次呼叫 API 都必須帶入的 sign 參數產生方式、範例與各語言程式碼。

每次呼叫我方服務時都必須帶入 `sign` 參數。`sign` 由此次傳遞的參數加上 API Key 組合後產生。

採用[單錢包](https://doc.va777.net/zh-Hant/v1/guides/seamless-wallet.md)時，我方呼叫貴司 API 也會以相同方式產生 `sign`，貴司請以相同方式驗證。

## 哪些參數要納入

* 只納入各 API 參數表中標示「參與簽章」的欄位。沒有標示的欄位與 `sign` 本身都不納入。
* 標示「參與簽章」的欄位即使沒有值也要納入。例如第一次查詢時 `nextToken` 為空字串，要以 `nextToken=` 的形式納入。
* 參數值以**實際送出的字串**計算。例如 `amount` 送出 `10.00`，簽章時也要用 `10.00`，不能寫成 `10`。
* 陣列參數以同名 key 重複出現，順序與送出時相同，例如 `gameIDList=102&gameIDList=101`。

## 產生步驟

1. **依 key 升序排序**：依參數名稱的字元碼（ASCII）排序，大寫字母排在小寫字母之前。
2. **URL-encode 並組成 query string**：key 與 value 都要編碼，以 `=` 連接，參數之間以 `&` 連接。編碼規則如下：
   * 英文字母、數字與 `-`、`_`、`.`、`~` 不編碼。
   * 空白編成 `+`。
   * 其餘字元以 UTF-8 編成 `%XX`，十六進位須為**大寫**，例如 `:` 編成 `%3A`、`+` 編成 `%2B`。
3. **接上 API Key 並計算 MD5**：在 query string 後方直接接上 API Key，中間不加任何符號，計算 MD5 後以**小寫**十六進位字串（32 個字元）作為 `sign`。

時間參數編碼後的樣子：`2025-12-01T00:00:00.000+08:00` 會編成 `2025-12-01T00%3A00%3A00.000%2B08%3A00`。

## 範例

以[存款](https://doc.va777.net/zh-Hant/v1/api-reference/transaction/deposit.md)為例，假設 API Key 為 `a188va`：

| 步驟 | 結果 |
| - | - |
| 參與簽章的參數 | `transactionId=deposit-20250101000001`、`channelId=1`、`account=player001`、`currency=CNY`、`amount=10.00` |
| 排序並組成 query string | `account=player001&amount=10.00&channelId=1&currency=CNY&transactionId=deposit-20250101000001` |
| 接上 API Key | `account=player001&amount=10.00&channelId=1&currency=CNY&transactionId=deposit-20250101000001a188va` |
| MD5 結果（`sign`） | `6368a5fd551daad9f202d7cda331bbeb` |

## 程式範例

以下範例都以上方的範例資料實際執行過，結果皆為 `6368a5fd551daad9f202d7cda331bbeb`。

```go Go theme={null}
package main

import (
	"crypto/md5"
	"encoding/hex"
	"fmt"
	"net/url"
)

// GenerateSign 依 key 排序並 URL-encode（url.Values.Encode），接上 API Key 後計算 MD5
func GenerateSign(params url.Values, apiKey string) string {
	sum := md5.Sum([]byte(params.Encode() + apiKey))
	return hex.EncodeToString(sum[:])
}

func main() {
	params := url.Values{}
	params.Set("transactionId", "deposit-20250101000001")
	params.Set("channelId", "1")
	params.Set("account", "player001")
	params.Set("currency", "CNY")
	params.Set("amount", "10.00")
	// 陣列參數請用 Add 依序加入，例如 params.Add("gameIDList", "102")
	fmt.Println(GenerateSign(params, "a188va")) // 6368a5fd551daad9f202d7cda331bbeb
}
```

```php PHP theme={null}
<?php
// $params: 標示「參與簽章」的參數；$apiKey: 我方提供的 API Key
function generateSign($params, $apiKey)
{
    ksort($params, SORT_STRING);
    $pairs = array();
    foreach ($params as $key => $value) {
        // 陣列參數以同名 key 重複出現，順序與送出時相同
        foreach ((array) $value as $v) {
            $pairs[] = urlencode($key) . '=' . urlencode($v);
        }
    }
    // urlencode 會把 ~ 編成 %7E，我方不編碼 ~，需還原
    $query = str_replace('%7E', '~', implode('&', $pairs));
    return md5($query . $apiKey);
}

echo generateSign(array(
    "transactionId" => "deposit-20250101000001",
    "channelId" => 1,
    "account" => "player001",
    "currency" => "CNY",
    "amount" => "10.00"
), "a188va"); // 6368a5fd551daad9f202d7cda331bbeb
```

```python Python theme={null}
import hashlib
from urllib.parse import urlencode, quote_plus


def generate_sign(params: dict, api_key: str) -> str:
    # 依 key 排序；陣列參數以同名 key 重複出現（doseq=True），順序與送出時相同
    query = urlencode(sorted(params.items()), doseq=True, quote_via=quote_plus)
    return hashlib.md5((query + api_key).encode("utf-8")).hexdigest()


print(generate_sign({
    "transactionId": "deposit-20250101000001",
    "channelId": 1,
    "account": "player001",
    "currency": "CNY",
    "amount": "10.00",
}, "a188va"))  # 6368a5fd551daad9f202d7cda331bbeb
```

```javascript Node.js theme={null}
const crypto = require("crypto");

// 與我方相同的編碼：空白編成 +、~ 不編碼，其餘以大寫十六進位編碼
function encode(value) {
  return encodeURIComponent(value)
    .replace(/[!'()*]/g, (c) => "%" + c.charCodeAt(0).toString(16).toUpperCase())
    .replace(/%20/g, "+");
}

function generateSign(params, apiKey) {
  const query = Object.keys(params)
    .sort()
    // 陣列參數以同名 key 重複出現，順序與送出時相同
    .flatMap((key) => [].concat(params[key]).map((v) => encode(key) + "=" + encode(String(v))))
    .join("&");
  return crypto.createHash("md5").update(query + apiKey, "utf8").digest("hex");
}

console.log(generateSign({
  transactionId: "deposit-20250101000001",
  channelId: 1,
  account: "player001",
  currency: "CNY",
  amount: "10.00",
}, "a188va")); // 6368a5fd551daad9f202d7cda331bbeb
```

## 簽章錯誤排查

回應訊息碼為 `102`（Sign invalid）時，請依序檢查：

1. 是否只納入標示「參與簽章」的欄位，且沒有遺漏。
2. 簽章用的參數值，是否與實際送出的值完全相同，包括小數位數與大小寫。
3. 編碼是否為大寫十六進位，空白是否編成 `+`（不是 `%20`），`~` 是否保持不編碼。
4. MD5 結果是否為小寫。
5. API Key 是否正確，前後不能有空白或換行。


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.