跳到主要内容

认证

商户 API 使用 MD5 排序签名认证(GodaPay 兼容方案)。每个请求都在请求体里带一个 signature 字段,用你的密钥计算,服务端据此验证请求确实来自你、且未被篡改。

HMAC-SHA256 可作为按商户开启的安全增强项(在管理后台配置)。开启后,把下面第 4 步的 MD5 换成 HMAC-SHA256(signStr, apiSecret)(同样输出小写十六进制),其余步骤不变。

API 凭证

商户账户创建后,你会收到:

  • Merchant ID(UUID)。放在请求体的 merchantId 字段中。
  • API Secret(私密)。用于计算 signature绝不通过网络传输;只保存在你的服务器上。

把 API Secret 当作密码对待。存到环境变量里,不要提交到代码仓库,泄露后立即轮换。

签名算法

  1. 取请求体的所有字段,排除 signature,并去掉所有值为 null/undefined 的字段。
  2. 按字段名做 ASCII 升序排序。
  3. 拼成 key=value&key=value&…,再在末尾追加 &key=<API_SECRET>
  4. 对整个字符串做 MD5,输出小写十六进制。
  5. 把结果作为 signature 字段放回请求体。

签名字符串示例

请求体(不含 signature):

{
"merchantId": "aef6025b-f435-443c-bb18-f10e65d9314c",
"orderNo": "ORD-001",
"returnUrl": "https://your-server.example/webhook",
"playerPhone": "01711000000",
"playerRef": "player_1"
}

排序拼接后(<secret> 为你的 API Secret):

merchantId=aef6025b-f435-443c-bb18-f10e65d9314c&orderNo=ORD-001&playerPhone=01711000000&playerRef=player_1&returnUrl=https://your-server.example/webhook&key=<secret>

对上面这行做 MD5 → 小写十六进制,即为 signature

参考实现

Node.js

const crypto = require('crypto');

function sign(params, apiSecret) {
const signStr = Object.keys(params)
.filter((k) => k !== 'signature' && params[k] != null)
.sort()
.map((k) => `${k}=${params[k]}`)
.join('&') + `&key=${apiSecret}`;
return crypto.createHash('md5').update(signStr).digest('hex').toLowerCase();
}

const body = {
merchantId: process.env.MERCHANT_ID,
orderNo: 'ORD-001',
returnUrl: 'https://your-server.example/webhook',
playerPhone: '01711000000',
playerRef: 'player_1',
};
body.signature = sign(body, process.env.API_SECRET);

await fetch(`${API_BASE}/pay/order/create`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body),
});

Python

import hashlib, os
import requests

def sign(params: dict, api_secret: str) -> str:
items = sorted(
(k, v) for k, v in params.items()
if k != 'signature' and v is not None
)
sign_str = '&'.join(f'{k}={v}' for k, v in items) + f'&key={api_secret}'
return hashlib.md5(sign_str.encode()).hexdigest().lower()

body = {
'merchantId': os.environ['MERCHANT_ID'],
'orderNo': 'ORD-001',
'returnUrl': 'https://your-server.example/webhook',
'playerPhone': '01711000000',
'playerRef': 'player_1',
}
body['signature'] = sign(body, os.environ['API_SECRET'])

requests.post(f'{API_BASE}/pay/order/create', json=body)

cURL(bash)

#!/usr/bin/env bash
set -euo pipefail

API_BASE="${API_BASE:?e.g. https://api.168tonpay.xyz/api/v1/compat/godapay}"
MERCHANT_ID="${MERCHANT_ID:?}"
API_SECRET="${API_SECRET:?}"

# 按 key 排序拼接(此处字段已手动排好序)
SIGN_STR="merchantId=${MERCHANT_ID}&orderNo=ORD-001&playerPhone=01711000000&playerRef=player_1&returnUrl=https://your-server.example/webhook&key=${API_SECRET}"
SIGNATURE=$(printf '%s' "$SIGN_STR" | openssl dgst -md5 -hex | awk '{print $NF}')

curl -sS -X POST "${API_BASE}/pay/order/create" \
-H "Content-Type: application/json" \
-d "{\"merchantId\":\"${MERCHANT_ID}\",\"orderNo\":\"ORD-001\",\"returnUrl\":\"https://your-server.example/webhook\",\"playerPhone\":\"01711000000\",\"playerRef\":\"player_1\",\"signature\":\"${SIGNATURE}\"}"

常见签名错误

返回 INVALID_SIGNATURE 通常是以下原因之一:

1. 参与签名的字段集不对

签名字符串必须包含请求体里signature的所有非空字段。漏掉一个字段(或把 null 字段算进去)都会导致不匹配。

2. 排序错误

必须按字段名 ASCII 升序 排序,而不是按你写字段的顺序。

3. 数值/字符串表示不一致

key=value 里的 value 用字段的原始字符串形式。布尔用 true/false,数字不要补零或改精度,确保和你实际发送的 JSON 一致。

4. Base URL 用错了

规范接口在 GodaPay 兼容路径下。不要再用已废弃的 /api/v1/merchant/*(那是旧的请求头 HMAC 方案)。

{API_BASE}/pay/order/create      (API_BASE = https://api.168tonpay.xyz/api/v1/compat/godapay)
/api/v1/merchant/orders/collect (已废弃)

Webhook 签名

服务端发出的回调 webhook 使用相同的 MD5 排序算法:对 webhook 表单字段(除 signature 外)排序拼接并加 &key=<secret>,再做 MD5。详见 Webhooks

下一步