# 91卡券接入当前项目接口配置

## 1. 对接目标

- `91卡券` 作为外部售卖与自动发货通道。
- 当前项目负责：
  - 接收 `91卡券` 下单请求；
  - 在系统内生成订单与快手履约任务；
  - 生成当前项目自己的领取链接；
  - 由 `91卡券` 通过自动发货，将该领取链接展示给买家。
- 已确认：**允许将链接作为最终卡密内容展示给买家**。

## 2. 对接结论

本次对接采用独立开放接口链路：

1. `91卡券` 调用我方异步下单接口；
2. 我方创建内部订单，并生成领取链接；
3. `91卡券` 调用我方查询订单接口；
4. 我方在查询结果的 `cards` 中返回领取链接；
5. `91卡券` 自动发货给买家。

## 3. 接口地址建议

基础前缀建议：

```text
https://你的域名/api/v1/open/91
```

接口列表：

- 异步卡密下单：`POST /api/v1/open/91/orders/create`
- 查询订单接口：`POST /api/v1/open/91/orders/query`

## 4. 签名配置

签名规则采用当前文档里的示例规则：

- 仅使用请求 JSON body 中实际传入的参数参与签名；
- 除 `sign` 外，所有 body 参数按字段名 ASCII 升序排序；
- 使用 `key=value&key=value` 方式拼接；
- 前后拼接商户密钥；
- 取 `MD5`，输出 32 位大写字符串。

建议固定配置：

- `signType`: `MD5`
- `charset`: `UTF-8`
- `timestamp`: 10 位秒级 Unix 时间戳
- `version`: `1.0`
- 已传入的空值参数是否参与签名：`参与`
- 不参与签名：`sign`、未传入的可选字段、`userId`、商户号、内部配置项

## 5. 业务字段映射

### 5.1 下单请求 -> 当前项目

| 91字段 | 当前项目用途 | 说明 |
| --- | --- | --- |
| `orderNo` | 外部订单号 / 幂等键 | 建议映射为内部 `platformOrderId` |
| `productNo` | 商品和店铺映射 | 默认按商品名匹配 cloudtentacles 商品；可用 `商品名----店铺编号` 携带快手小店 `shopId` |
| `buyNum` | 购买数量 | 生成对应数量的履约任务 |
| `maxAmount` | 成本上限校验 | 可选，超限返回失败 |
| `callbackUrl` | 备用回调地址 | 先保留，不作为主流程依赖 |
| `timestamp` | 防重放 | 校验请求时效 |
| `version` | 版本 | 固定 `1.0` |
| `sign` | 验签 | 必填 |

### 5.2 当前项目内部建议映射

建议新增一个独立来源：

- `provider = '91kaquan'`
- `platform = 'kuaishou'`

商品绑定建议：

- `productNo` 对应当前项目内部 `skuCode`
- 再由现有快手履约配置，匹配到 `kuaishou_ct_assisted` 履约链路
- 若 `productNo` 暂未配置，订单会先进入后台“91卡券待补全订单”队列，不直接丢弃。

## 6. 异步下单接口配置

### 6.1 请求方向

- `91卡券 -> 当前项目`

### 6.2 请求地址

```text
POST /api/v1/open/91/orders/create
Content-Type: application/json;charset=utf-8
```

### 6.3 请求参数

| 参数名 | 必填 | 类型 | 说明 |
| --- | --- | --- | --- |
| `orderNo` | 是 | string | 91 商家订单号，唯一 |
| `productNo` | 是 | string | 我方商品编号 |
| `buyNum` | 是 | int | 购买数量 |
| `maxAmount` | 否 | string | 可接受最大成本金额 |
| `callbackUrl` | 否 | string | 91 提供的回调地址 |
| `timestamp` | 是 | long | 10 位秒级时间戳 |
| `version` | 是 | string | 固定 `1.0` |
| `sign` | 是 | string | 签名 |

### 6.4 请求示例

```json
{
  "orderNo": "P91KS202605040001",
  "productNo": "KS-CLOUD-SKU-001",
  "buyNum": 1,
  "maxAmount": "0.0000",
  "callbackUrl": "https://cb.example.com/notify/91/order",
  "timestamp": 1777867200,
  "version": "1.0",
  "sign": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
}
```

### 6.5 下单处理规则

- 验签失败：直接返回失败。
- `orderNo` 已存在：按幂等处理，返回已有订单状态。
- `productNo` 未配置：保存为待补全订单，返回处理中。
- 成本超限：返回失败，错误码可用 `1220`。
- 下单成功后：
  - 创建内部订单；
  - 创建快手履约任务；
  - 生成领取链接；
  - 异步商品首次响应返回 `10`，表示处理中。

### 6.6 响应字段约定

| 参数名 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| `orderNo` | string | 是 | 原样返回 |
| `outTradeNo` | string | 是 | 我方内部订单号 |
| `orderStatus` | int | 是 | 异步商品固定返回 `10` 或 `30` |
| `orderCost` | decimal | 否 | 成功受理时可返回 |
| `cards` | string | 否 | 异步下单阶段建议为空 |

### 6.7 响应示例

```json
{
  "code": 200,
  "message": "接口调用成功",
  "data": {
    "orderNo": "P91KS202605040001",
    "outTradeNo": "OS202605040001",
    "orderStatus": 10,
    "orderCost": 0.0000,
    "cards": ""
  }
}
```

## 7. 查询订单接口配置

### 7.1 请求方向

- `91卡券 -> 当前项目`

### 7.2 请求地址

```text
POST /api/v1/open/91/orders/query
Content-Type: application/json;charset=utf-8
```

### 7.3 请求参数

| 参数名 | 必填 | 类型 | 说明 |
| --- | --- | --- | --- |
| `orderNo` | 是 | string | 91 商家订单号 |
| `timestamp` | 是 | long | 10 位秒级时间戳 |
| `version` | 是 | string | 固定 `1.0` |
| `sign` | 是 | string | 签名 |

### 7.4 查询处理规则

- 找不到订单：返回失败。
- 订单已创建但履约配置尚未补齐：返回 `orderStatus = 10`。
- 订单已创建但领取链接未准备好：返回 `orderStatus = 10`。
- 领取链接已生成，可交付：返回 `orderStatus = 20`。
- 订单无法履约：返回 `orderStatus = 30`，并带失败原因。

### 7.5 响应字段约定

| 参数名 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| `orderNo` | string | 是 | 原样返回 |
| `outTradeNo` | string | 是 | 我方内部订单号 |
| `orderStatus` | int | 是 | `10`处理中，`20`成功，`30`失败 |
| `failCode` | int | 否 | 失败时返回 |
| `failReason` | string | 否 | 失败时返回 |
| `orderCost` | decimal | 否 | 成功时返回 |
| `cards` | string | 成功时必须 | 卡密加密串 |

## 8. cards 字段配置

### 8.1 推荐方案

既然已确认允许将链接作为最终卡密内容展示给买家，建议：

- `cardNo` 直接放领取链接；
- `cardPwd` 留空；
- `expireTime` 可放领取链接过期时间；
- `jumpLink` 可与 `cardNo` 保持一致，作为兼容字段。

这样做的好处是：

- `91卡券` 自动发货可直接展示链接；
- 复杂文案、快手操作说明、核销码校验、角色确认，全部放在我方领取页完成；
- 降低 91 展示层格式差异带来的风险。

### 8.2 单卡结构建议

```json
[
  {
    "cardNo": "https://你的域名/claim/abc123xyz",
    "cardPwd": "",
    "expireTime": "2026-05-05 12:00:00",
    "jumpLink": "https://你的域名/claim/abc123xyz"
  }
]
```

### 8.3 多数量建议

如果 `buyNum > 1`：

- 每个数量生成一个独立领取链接；
- `cards` 中放多个卡项；
- 每个 `cardNo` 对应一个独立链接。

## 9. 查询成功响应示例

```json
{
  "code": 200,
  "message": "接口调用成功",
  "data": {
    "orderNo": "P91KS202605040001",
    "outTradeNo": "OS202605040001",
    "orderStatus": 20,
    "failCode": 0,
    "failReason": "",
    "orderCost": 0.0000,
    "cards": "加密后的cards字符串"
  }
}
```

`cards` 解密前原始结构建议为：

```json
[
  {
    "cardNo": "https://你的域名/claim/abc123xyz",
    "cardPwd": "",
    "expireTime": "2026-05-05 12:00:00",
    "jumpLink": "https://你的域名/claim/abc123xyz"
  }
]
```

## 10. 状态流转建议

### 10.1 91侧状态

- `10`: 已下单，当前项目正在补齐履约配置或生成领取链接
- `20`: 已可交付，91 可自动发货给买家
- `30`: 无法履约

### 10.2 当前项目侧状态建议

- 创建订单成功：若履约配置已命中则进入待履约；若未命中则进入后台待补全
- 已生成 `claimUrl`：视为可交付
- 查询接口检测到 `claimUrl` 已存在：返回 `20`
- 商家后台手动标记无法履约、履约初始化失败：返回 `30`

## 11. 商品配置建议

建议在你方给 91 的商品配置中约定：

- `productNo`：对应当前项目内部的快手履约 SKU
- 商品名称：明确标注“自动发链接”
- 发货类型：异步卡密
- 买家收到内容：领取链接

后台补全路径：

1. 进入“平台配置 -> 91卡券接入”查看待补全订单；
2. 根据订单中的 `productNo` 到“履约配置中心 -> 快手 Cloud 新履约”新增或补齐规则；
3. 规则保存后回到“91卡券接入”，点击“重试生成任务”；
4. 任务生成后，查询接口会在领取链接准备好时返回 `orderStatus = 20`。

## 12. 对接方需确认的固定项

发给 `91卡券` 客服/对接方时，建议一次性确认以下配置：

- 异步卡密下单 URL
- 查询订单 URL
- 商户密钥
- 签名算法是否按本文档固定
- `cards` 中 `cardNo` 直接展示链接是否按预期显示
- `jumpLink` 是否会同步展示或仅作兼容字段
- 查询频率与超时要求

## 13. 最终建议

这次对接的最稳方案是：

- `91` 负责卖货与自动发货；
- 当前项目负责生成 `claimUrl`；
- `claimUrl` 作为最终卡密内容，通过 `cards.cardNo` 返回；
- 所有快手专属引导和后续交互，都留在当前项目领取页中完成。

这样对当前项目改动最小，也最符合现在快手 Cloud 履约链路的设计。
