> For the complete documentation index, see [llms.txt](https://docs.catfee.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.catfee.io/getting-started/buy-energy-via-api-on-catfee/nodejs.md).

# Node.js 调用示例

Node.js 调用 CatFee.IO Rest API 示例

### **前提条件**

1. [您需要一个有效的 API Key 和 API Secret](/getting-started/buy-energy-via-api-on-catfee/api-overview.md#apply-api-info)。
2. 使用 Node.js 18 及以上版本。
3. 安装 `axios`：

   ```bash
   npm install axios
   ```

### **示例代码**

```javascript
const axios = require('axios');
const crypto = require('crypto');

const API_KEY = 'your_api_key'; // 请替换为您的 API Key
const API_SECRET = 'your_api_secret'; // 请替换为您的 API Secret
const BASE_URL = 'https://api.catfee.io';
const TIMEOUT_MS = 15000;

// 生成带毫秒的 ISO 8601 UTC 时间戳。
function generateTimestamp() {
    return new Date().toISOString();
}

// 构建参与签名并实际发送的完整请求路径。
function buildRequestPath(path, queryParams = []) {
    if (queryParams.length === 0) {
        return path;
    }

    return `${path}?${new URLSearchParams(queryParams).toString()}`;
}

// 使用 HMAC-SHA256 生成 Base64 编码的请求签名。
function generateSignature(timestamp, method, requestPath) {
    const signString = timestamp + method.toUpperCase() + requestPath;
    return crypto
        .createHmac('sha256', API_SECRET)
        .update(signString, 'utf8')
        .digest('base64');
}

// 签名并发送 CatFee API 请求。
async function sendRequest(method, path, queryParams = []) {
    method = method.toUpperCase();
    const requestPath = buildRequestPath(path, queryParams);
    const timestamp = generateTimestamp();
    const signature = generateSignature(timestamp, method, requestPath);

    const headers = {
        'Content-Type': 'application/json',
        'CF-ACCESS-KEY': API_KEY,
        'CF-ACCESS-SIGN': signature,
        'CF-ACCESS-TIMESTAMP': timestamp,
    };

    // requestPath 必须同时用于签名和实际请求，避免参数编码或顺序不一致。
    return axios.request({
        method,
        url: BASE_URL + requestPath,
        headers,
        timeout: TIMEOUT_MS,
        validateStatus: () => true,
    });
}

async function main() {
    // 示例：创建一笔能量订单。
    // 使用数组固定参数顺序；client_order_id 用于网络异常时安全重试。
    const clientOrderId = crypto.randomUUID();
    const queryParams = [
        ['quantity', '65000'],
        ['receiver', 'TRON_ADDRESS'],
        ['duration', '1h'],
        ['client_order_id', clientOrderId],
        ['activate', 'true'],
    ];

    console.log('Client Order ID:', clientOrderId);

    try {
        const response = await sendRequest('POST', '/v1/order', queryParams);
        console.log('HTTP Status:', response.status);
        console.log('Response Body:', response.data);

        // HTTP 层失败（例如 4xx、5xx）。
        if (response.status < 200 || response.status >= 300) {
            throw new Error(`HTTP request failed with status ${response.status}`);
        }

        const result = response.data;
        if (!result || typeof result !== 'object') {
            throw new Error('Response is not valid JSON');
        }

        // CatFee API 通常返回 HTTP 200，仍需检查响应体中的业务 code。
        if (result.code !== 0) {
            const message = result.msg || result.sub_msg || 'Unknown error';
            throw new Error(
                `API request failed: code=${result.code}, message=${message}`
            );
        }

        console.log('Order ID:', result.data?.id || '');
    } catch (error) {
        if (error.code === 'ECONNABORTED') {
            console.error('Request timed out.');
        } else {
            console.error('Request failed:', error.message);
        }
        console.error('如需重试，请复用 Client Order ID:', clientOrderId);
    }
}

main();
```

### **代码解析**

1. **`generateTimestamp()`**：\
   使用 `toISOString()` 生成带毫秒的 ISO 8601 UTC 时间戳，用于 `CF-ACCESS-TIMESTAMP` 请求头。
2. **`buildRequestPath()`**：\
   使用 `URLSearchParams` 编码查询参数并拼接请求路径。返回结果同时用于签名和实际请求，确保两者完全一致。
3. **`generateSignature()`**：\
   按照 `timestamp + method + requestPath` 拼接签名原文，使用 API Secret 计算 HMAC-SHA256，并对结果进行 Base64 编码。
4. **`sendRequest()`**：\
   构造三个 CatFee 鉴权请求头，通过 Axios 发送请求，并设置 15 秒超时。
5. **`main()`**：\
   调用 `POST /v1/order` 创建能量订单，并同时检查 HTTP 状态码和响应体中的业务 `code`。

### **注意事项**

* **API Key 和 Secret**：\
  请确保将 `API_KEY` 和 `API_SECRET` 替换为您从 CatFee.IO 获取的实际值。
* **签名路径必须与请求路径一致**：\
  查询参数的内容、URL 编码和顺序都必须一致。不要在生成签名后使用 Axios 的 `params` 选项重新构造查询字符串。
* **订单参数**：\
  `quantity` 当前不得小于 `65000`，`duration` 当前仅支持 `1h`；`receiver` 必须是有效的 TRON 地址。
* **幂等重试**：\
  每笔新订单使用唯一且不超过 64 个字符的 `client_order_id`。发生超时或连接中断时，应保存并复用原值重试，不能重新生成。
* **地址激活**：\
  `activate=true` 表示必要时允许激活接收地址；如果设为 `false` 且地址未激活，接口将返回错误。
* **响应判断**：\
  HTTP `200` 不一定表示业务成功，还必须确认响应 JSON 中的 `code` 为 `0`。

### **总结**

此示例展示了如何使用 Node.js 调用 `POST /v1/order` 购买能量，并正确处理请求签名、幂等订单号、超时和 API 响应。其他接口也可以复用 `sendRequest()`，只需传入对应的 HTTP 方法、路径和查询参数。
