# CardCtrl 销售系统 API 对接文档

面向下游系统（91卡券仓库、鱼店长、自研发货程序等）通过 HTTP API 向 CardCtrl 采购卡密。

协议与 [有鱼网销售系统 API](https://es.youyuwang.cn/api-doc/) 保持一致，可直接按有鱼文档对接。

---

## 1. 简介

数字产品销售开放平台，为接入方提供商品查询、下单购买、订单查询等 API。

### 协议架构

1. 传输采用 HTTP 协议
2. 使用 **POST** 提交参数到接口地址，不要使用 URL Query 传参
3. 传输数据采用 UTF-8 编码，中文无需 URL 编码

### 接口地址

```
(http|https)://[域名]/api/gateway
```

生产示例：

```
https://api.shayuka.com/api/gateway
```

### 在线文档（免登录）

```
https://api.shayuka.com/api/public/docs
```

总后台登录后也可在左侧菜单 **对接文档** 中阅读，并复制公开链接发给对接方。

### 凭证获取

登录商家后台 → **安全设置**，获取：

| 项 | 说明 |
|----|------|
| `customer_id` | 商家编号（整数） |
| `api_key` / key | 接口密钥（勿通过网络明文泄露） |

---

## 2. 公共参数

每次请求需传入：

| 参数名称 | 类型 | 必须 | 说明 |
|----------|------|------|------|
| method | string | 是 | API 方法名，如 `esales.product.get` |
| customer_id | int | 是 | 商家编号 |
| timestamp | int | 是 | 当前时间戳（**秒**） |
| sign | string | 是 | 签名 |

Body 支持：

- `application/x-www-form-urlencoded`
- `application/json`

---

## 3. 签名算法

```
sign = md5( key + 参数名1 + 参数值1 + 参数名2 + 参数值2 + ... )
```

规则：

1. 参与签名的参数为**本次请求全部业务参数 + 公共参数**，**不含** `sign` 本身
2. 按参数名 **字典升序** 排列后拼接
3. 最前面拼接 `key`（商家密钥）
4. MD5 后取 **32 位小写** hex

示例：参数 `a=1, b=3, z=2` 以及公共参数时：

```text
md5({key}a1b3customer_id{customer_id}method{method}timestamp{timestamp}z2)
```

### Python 示例

```python
import hashlib
import time
import httpx

CUSTOMER_ID = 3
API_KEY = "你的密钥"
BASE = "https://api.shayuka.com/api/gateway"

def call(method: str, extra: dict | None = None) -> dict:
    params = {
        "method": method,
        "customer_id": str(CUSTOMER_ID),
        "timestamp": str(int(time.time())),
        **{k: str(v) for k, v in (extra or {}).items()},
    }
    items = sorted((k, v) for k, v in params.items())
    raw = API_KEY + "".join(k + v for k, v in items)
    params["sign"] = hashlib.md5(raw.encode()).hexdigest()
    return httpx.post(BASE, data=params, timeout=30.0).json()

# 查余额
print(call("esales.customer.info"))

# 查商品
print(call("esales.product.get", {"product_id": 25}))
```

---

## 4. 通用返回格式

```json
{
  "code": 0,
  "message": "请求成功",
  "data": {}
}
```

- `code = 0`：成功
- `code = 500`：服务端未知错误，**结果不确定**，勿按失败处理（需人工核实）
- 其它：失败，见状态码表

---

## 5. 状态码

| 状态码 | 说明 |
|--------|------|
| 0 | 成功 |
| 1 | 通用错误 |
| 100 | 参数错误 |
| 101 | method 错误 |
| 102 | sign 错误 |
| 104 | 时间戳格式错误 |
| 105 | timestamp 时间误差超过 10 分钟 |
| 202 | 商家余额不足 |
| 203 | 商家不存在 |
| 204 | 商家被禁用 |
| 401 | IP 访问受限 |
| 403 | 权限不足 |
| 500 | 服务端发生未知错误 |
| 701 | 当前接口限流 |
| 702 | 当前接口暂时不可用 |
| 703 | 调用过于频繁 |
| 1101 | 外部订单号重复 |
| 1303 | 商品不存在 |
| 1304 | 商品已下架 |
| 1401 | 库存不足 |

---

## 6. 接口一览

| 方法 | 说明 |
|------|------|
| `esales.customer.info` | 获取商家信息（余额） |
| `esales.product.all` | 获取全部在售商品 |
| `esales.product.get` | 获取单个商品 |
| `esales.product.charge_params` | 获取直充商品充值参数模板 |
| `esales.order.purchase` | 购买商品 |
| `esales.order.get` | 查询订单（成功后取卡密） |

---

## 7. 接口详情

### 7.1 获取商家信息 `esales.customer.info`

**传入参数**：无（仅公共参数）

**返回 data**：

| 字段 | 类型 | 说明 |
|------|------|------|
| id | int | 商家编号 |
| name | string | 商家名称 |
| balance | decimal | 余额 |

```json
{
  "code": 0,
  "message": "",
  "data": {
    "id": 3,
    "name": "飞扬贸易88",
    "balance": "1000.0000"
  }
}
```

---

### 7.2 获取所有商品 `esales.product.all`

**传入参数**：无

**返回 data**：商品数组

| 字段 | 类型 | 说明 |
|------|------|------|
| id | int | 商品编号 |
| name | string | 商品名称 |
| type | int | 1 直充，2 卡密 |
| face_value | decimal | 面值 |
| price | decimal | 售价 |
| purchase_quantity_rule | string | 合法购买数量，如 `1-100` 或 `1,2,3` |
| status | int | 1 销售中，2 停售 |

---

### 7.3 获取单个商品 `esales.product.get`

| 参数 | 类型 | 必须 | 说明 |
|------|------|------|------|
| product_id | int | 是 | 商品编号 |

返回字段同 `esales.product.all` 单条。

---

### 7.4 充值参数模板 `esales.product.charge_params`

仅直充类商品需要。

| 参数 | 类型 | 必须 | 说明 |
|------|------|------|------|
| product_id | int | 是 | 商品编号 |

返回 `data` 为自定义模板数组，或预置模板对象（与有鱼文档一致）。

---

### 7.5 购买商品 `esales.order.purchase`

| 参数 | 类型 | 必须 | 说明 |
|------|------|------|------|
| out_order_id | string | 是 | 商家订单号（同一商家下唯一） |
| product_id | int | 是 | 商品编号 |
| quantity | int | 是 | 购买数量 |
| charge_params | json | 否 | 直充参数 JSON 字符串 |
| notify_url | string | 否 | 异步通知地址 |
| end_customer_ip | string | 否 | 终端客户 IP |
| end_customer_region | string | 否 | 终端客户区域 |
| end_total_price | decimal | 否 | 终端客户总价 |

**返回 data**：

| 字段 | 类型 | 说明 |
|------|------|------|
| order_id | int | 平台订单号 |
| status | int | 100 等待发货 / 200 成功 / 500 失败 / 300 可疑 |
| status_info | string | 状态说明 |
| total_price | decimal | 实付金额 |
| cards | array | 卡密列表（卡密商品且成功时返回） |

`cards` 元素：

| 字段 | 说明 |
|------|------|
| card_no | 卡号 |
| card_password | 卡密 |
| expiration_date | 有效期 |

```json
{
  "code": 0,
  "message": "",
  "data": {
    "order_id": 10001,
    "status": 200,
    "status_info": "交易成功",
    "total_price": "3.0000",
    "cards": [
      {
        "card_no": "vip009",
        "card_password": "DDd2kpCxZosyy7d",
        "expiration_date": "2025-05-28"
      }
    ]
  }
}
```

---

### 7.6 查询订单 `esales.order.get`

| 参数 | 类型 | 必须 | 说明 |
|------|------|------|------|
| order_id | int | 否 | 平台订单号（与 out_order_id 二选一） |
| out_order_id | string | 否 | 商家订单号 |

**返回 data**：

| 字段 | 类型 | 说明 |
|------|------|------|
| id | int | 平台订单号 |
| product_id | int | 商品编号 |
| product_name | string | 商品名称 |
| product_type | int | 1 直充，2 卡密 |
| product_price | decimal | 单价 |
| quantity | int | 数量 |
| total_price | decimal | 总价 |
| status | int | 订单状态 |
| status_info | string | 状态说明 |
| created_at | string | 下单时间 |
| cards | array | 卡密（卡密商品成功时） |

---

### 7.7 订单异步通知

购买时传入 `notify_url` 后，订单终态会 POST 通知商户：

| 参数 | 类型 | 必须 | 说明 |
|------|------|------|------|
| order_id | int | 是 | 平台订单号 |
| out_order_id | string | 是 | 商家订单号 |
| status | int | 是 | 200 成功 / 500 失败 |
| status_info | string | 否 | 状态信息 |
| cards | json | 否 | 卡密信息 |
| sign | string | 是 | 签名（算法同请求签名） |

商户处理成功后请返回纯文本：`ok`

---

## 8. 给阿奇索 / 91卡券对接说明

请按**有鱼网销售系统（esales gateway）**协议接入，不要使用「标准货源 agisoAcprSupplierApi」探测协议。

建议配置：

| 配置项 | 值 |
|--------|-----|
| 接口地址 | `http://你的域名/api/gateway` |
| 商家编号 customer_id | 商家后台「安全设置」中的商家编号（整数） |
| 密钥 key | 商家 ApiKey |
| 签名 | MD5，秒级 timestamp |
| 协议参考 | https://es.youyuwang.cn/api-doc/ |

联调顺序建议：

1. `esales.customer.info` 验签与余额
2. `esales.product.get` 验证商品 ID
3. `esales.order.purchase` 小额测试单
4. `esales.order.get` 取卡密

---

## 9. 参考

- 有鱼原文档：https://es.youyuwang.cn/api-doc/
- 有鱼状态码：https://es.youyuwang.cn/api-doc/code-list.html
