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

# 安全与签名

> 说明 GatePay API 的协议规则、通用响应结构、请求头与签名算法。

## 协议规则

| 字段   | 描述                                      |
| ---- | --------------------------------------- |
| 传输方式 | 为保证交易安全性，采用 `HTTPS` 传输，并使用 `TLS1.2` 及以上 |
| 数据格式 | 请求和响应数据都为 `JSON` 格式                     |
| 签名算法 | `HMAC-SHA512` 算法                        |
| 签名要求 | 请求和接收数据均需要校验签名                          |
| 判断逻辑 | 先判断协议 status 字段返回，再判断业务返回，最后判断交易状态      |

## 响应格式

| 字段名          | 类型     | 说明                        |
| ------------ | ------ | ------------------------- |
| status       | string | 接口响应结果，SUCCESS 成功，FAIL 失败 |
| code         | string | 响应错误码                     |
| label        | string | 响应错误名称                    |
| errorMessage | string | 错误描述                      |
| data         | string | JSON 格式业务响应数据             |

### 成功示例

```json theme={null}
{
  "status": "SUCCESS",
  "code": "000000",
  "label": "成功",
  "errorMessage": "",
  "data": "{...}"
}
```

### 失败示例

```json theme={null}
{
  "status": "FAIL",
  "code": "300001",
  "label": "参数错误",
  "errorMessage": "merchantTradeNo 不能为空",
  "data": ""
}
```

## 参数规定

### 商户订单号

商户支付的订单号由商户自定义生成，仅支持使用字母、数字、中划线-、下划线\_这些英文半角字符的组合，请勿使用汉字或全角等特殊字符，限定长度在 100 个字符内。GatePay 要求商户订单号保持唯一性（建议根据当前系统时间加随机序列来生成订单号）。重新发起一笔支付要使用原订单号，避免重复支付。

### 交易金额

所有金额参数都采用字符串传输，精度精确到小数点后 6 位，单笔交易金额最小单位为 `0.0001`，最大为 `5000000`，个人收款码最大金额为 `10000`。

### 币种类型

GatePay 支持的币种请参考支持币种章节。

### 时间

所有时间字段，如果没有额外说明，格式都是毫秒级的 `Unix` 时间戳。

## 请求头

| 请求头                            | 说明                                                     |
| ------------------------------ | ------------------------------------------------------ |
| X-GatePay-Certificate-ClientId | 商户在 Gate 商户后台创建应用后分配的 ClientId                         |
| X-GatePay-On-Behalf-Of         | 代理归属请求头。除机构专属接口外，本版所有 API 均支持按需携带该字段，用于标识请求归属的子主体或商户主体 |
| X-GatePay-Timestamp            | 请求生成时的 UTC 毫秒时间戳；GatePay 收到请求时若与当前时间相差超过 10 秒，将拒绝处理    |
| X-GatePay-Nonce                | 随机字符串，建议长度不超过 32 位，仅支持数字与字母                            |
| X-GatePay-Signature            | 请求签名字段，GatePay 使用该字段校验请求合法性                            |

机构 API 的请求头规范请参见 [机构公共头信息](/api-reference/version/100/cn/common/institutionalCommonHeaders)。

## 签名

### 签名规范

构造签名串

我们希望商户的技术开发人员按照当前文档约定的规则构造签名串。GatePay 会使用同样的方式构造签名串。如果商户构造签名串的方式错误，将导致签名验证不通过。下面先说明签名串的具体格式。

每一行为一个参数。行尾以 `\n`（换行符，ASCII编码值为0x0A）结束。如果参数本身以 `\n` 结束，也需要附加一个 `\n`。

`请求时间戳\n请求随机串\n请求报文主体\n`

### 签名算法

**Go 语言**

```go theme={null}
import (
  "crypto/hmac"
  "crypto/sha512"
  "encoding/hex"
  "fmt"
)

// GenerateSignature 生成请求签名
// timestamp: 转换成字符串的UTC时间戳，精度是millisecond
// nonce: 随机字符串
// body: 请求体
// secretKey: Gate提供的api_secret
// return: 字符串签名
func GenerateSignature(timestamp string, nonce string, body string, secretKey string) string {
  payload := fmt.Sprintf("%s\n%s\n%s\n", timestamp, nonce, body)
  mac := hmac.New(sha512.New, []byte(secretKey))
  mac.Write([]byte(payload))
  signature := mac.Sum(nil)
  return hex.EncodeToString(signature)
}
```

**Java 语言**

```java theme={null}
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Formatter;

public class Main {

    private static final String HMAC_SHA512 = "HmacSHA512";

    private static String toHexString(byte[] bytes) {
        Formatter formatter = new Formatter();
        for (byte b : bytes) {
            formatter.format("%02x", b);
        }
        return formatter.toString();
    }

    public static String calculateHMAC(String data, String key)
            throws InvalidKeyException, NoSuchAlgorithmException {
        SecretKeySpec secretKeySpec = new SecretKeySpec(key.getBytes(), HMAC_SHA512);
        Mac mac = Mac.getInstance(HMAC_SHA512);
        mac.init(secretKeySpec);
        return toHexString(mac.doFinal(data.getBytes()));
    }

    public static void main(String[] args) throws Exception {
        String timeStamp = "1673613945439";
        String nonce = "3133420233";
        String body = "{\"code\":\"ac8B7Pl7C-XgfH6zxtd3SidYt7XIfWKU\",\"grant_type\":\"authorization_code\",\"redirect_uri\":\"https://gate.bytetopup.com\",\"client_id\":\"2Ugf9YGMCFRk85Yy\"}";
        String data = String.format("%s\n%s\n%s\n", timeStamp, nonce, body);
        String key = "zgsN5DntmQ2NCQiyJ4kJLyyEO25ewdDHydOSFIHdGrM=";
        String hmac = calculateHMAC(data, key);
        System.out.println(hmac);
    }
}
```

**Python 语言**

```python theme={null}
import hashlib
import hmac

def generate_signature(timestamp: str, nonce: str, body: str, secret: str) -> str:
    '''
    生成请求签名
    :param timestamp: 转换成字符串的UTC时间戳，精度是millisecond
    :param nonce: 随机字符串
    :param body: 请求体
    :param secret: GatePay提供的api_secret
    :return: 返回字符串签名
    '''
    payload = '%s\n%s\n%s\n' % (timestamp, nonce, body)
    signed = hmac.new(secret.encode(), payload.encode(), hashlib.sha512)
    return signed.digest().hex()
```

**PHP 语言**

```php theme={null}
<?php

function generateSignature($timestamp, $nonce, $body, $secretKey) {
    $payload = "$timestamp\n$nonce\n$body\n";
    $signature = hash_hmac('sha512', $payload, $secretKey, true);
    return bin2hex($signature);
}

$timestamp = "1631257823000";
$nonce = "abcd1234";
$body = 'the post request body content';
$secretKey = "your_secret_key";

$signature = generateSignature($timestamp, $nonce, $body, $secretKey);
echo "Signature: " . $signature;
```

## 支付回调处理

### 回调描述

Gate Pay 会通过 POST 的方式向回调地址发送回调报文，回调通知的请求主体中会包含 JSON 格式的通知参数，具体的通知参数列表如下：

| 名称         | 类型        | 说明                                                                                                                       |
| ---------- | --------- | ------------------------------------------------------------------------------------------------------------------------ |
| bizType    | string    | 异步回调类型枚举，"PAY" 表示非地址支付，"PAY\_BATCH" 异步奖励单，"PAY\_ADDRESS" 地址支付，"PAY\_FIXED\_ADDRESS" 固定收款码地址支付，"TRANSFER\_ADDRESS" 地址支付转账 |
| bizId      | string    | 支付订单 ID                                                                                                                  |
| bizStatus  | string    | 订单状态                                                                                                                     |
| client\_id | string    | 支付订单关联的 client\_id                                                                                                       |
| data       | json 格式对象 | 根据不同业务订单 data 格式不同，请参考具体业务文档                                                                                             |

### 回调验签与应答

商户接收到回调通知报文后，需完成对报文的验签，并应答回调通知。

商户验签后响应字段如下：

| 参数            | 描述                                                                                             |
| ------------- | ---------------------------------------------------------------------------------------------- |
| returnCode    | 回调是否成功处理。"SUCCESS" 表示成功处理，当返回 "SUCCESS" GatePay 不会再重试异步回调发送。"FAIL" 表示处理失败，当 GatePay 收到该状态会发送重试 |
| returnMessage | 失败原因字符串类型描述，可以为空                                                                               |

响应 JSON 格式：

```json theme={null}
{
    "returnCode": "SUCCESS",
    "returnMessage": ""
}
```

### 回调处理步骤

对回调进行验签步骤：

回调报文的 HTTP 请求头中会包含报文的签名信息，用于验签，具体如下：

| 参数                  | 描述       |
| ------------------- | -------- |
| X-GatePay-Timestamp | 验签的时间戳   |
| X-GatePay-Nonce     | 验签的随机字符串 |
| X-GatePay-Signature | 验签的签名值   |

验签需使用请求头中的 `X-GatePay-Timestamp`、`X-GatePay-Nonce` 以及请求主体中 JSON 格式的通知参数构建出验签串（验签计算参考签名算法章节），然后使用商户支付密钥计算出验签再跟 `X-GatePay-Signature` 值进行比较是否相等，确保接收的回调内容是来自 Gate Pay。

### 签名验证工具

商户的技术开发人员可使用该工具来排查签名验证不通过的原因。

[签名验证工具](https://www.gate.com/zh/gatepay/public/signUtil)
