# 店铺供货 API 开发文档

版本：`1.0.0` · 契约修订：`2026-10-28.2`

本文用于店铺自有商品订单、供货订单及售后的经营对接。商品查询、采购下单等对接另见 [采购 OpenAPI 文档](/api.md)；管理员接入见 [后台管理 API](./admin-api-guide.md)。机器可读契约见 [management-openapi-v1.yaml](./management-openapi-v1.yaml)。

## 1. 开始接入

接入需要三个参数：主站 HTTPS 域名、店主的前台用户 ID、该用户当前的 OpenAPI Key。店铺须具备经营权限、审核通过且处于启用状态。店铺身份由认证用户确定，不传 `storeId`。

基础地址：

```text
https://{merchant-domain}/openapi/v1/seller
```

请求头：

```http
X-KYS-User-Id: <USER_ID>
X-KYS-Key: <OPENAPI_KEY>
Content-Type: application/json
```

- 仅使用主站域名。店铺可以获取在分站成交的本店订单，但经营接口不改用分站域名。
- Key 与前台“接口管理”中的 Key 共用，支持 `AK_` 加 16 位或 32 位小写十六进制字符。重置后旧 Key 立即失效。
- 沿用该用户的 OpenAPI 开关、账户状态和 IP 白名单，不需要另申请供货密钥。
- 不传 Cookie、登录密码、API Secret、时间戳或签名。密钥不得放入 URL、浏览器代码或日志。

## 2. 订单范围与操作权限

可见订单包括：

1. 该店铺销售的自有商品订单。
2. 该店铺作为当前有效供货方的订单。
3. 由该店铺人工供货、没有履约尝试记录的订单。

范围包括卡密、充值、实物及合法分站成交订单。历史上曾供货、现在已不由本店履约的订单，不因此获得供货权限。售后沿关联订单的店铺归属判断可见性。

| 返回信息 | 用途 |
| --- | --- |
| isSeller / role | 是否销售方；`role=seller` 或 `supplier` |
| canFulfill | 是否由本店履约且已付款；具体动作还须符合当前状态 |
| canProcessRecharge | 当前能否处理人工充值 |
| canUploadRechargeProof | 当前是否具备人工充值凭证上传资格 |
| canRefund / refundPending | 是否可申请退款、是否正在退款 |
| physical.canShip / canUpdateLogistics | 实物发货、修改运单资格 |
| canReply | 当前是否可向买家回复或提交意见 |
| canHandleExchange | 当前是否由本店处理换货；还须匹配实物售后服务状态 |

销售方与实际履约方可能不同。销售方可按资格回复买家，供货方可查看相关售后，但供货身份本身不授予面向买家的回复或售后裁决权限。每次写入仍以服务端当时的状态和权限检查为准。

## 3. 通用约定

- HTTPS、UTF-8。除凭证上传外，请求体为 `application/json`。
- 路径 `{id}` 为 1～10 位正整数，订单 ID、售后 ID 与其单号分别保存。
- 时间是 Unix 秒级整数；金额单位为人民币元。`totalPrice`、`refundedAmount` 为两位金额，`supplyAmount` 可保留四位小数。
- 列表 `page` 从 1 开始，`pageSize` 默认 20，范围 1～100。
- GET 订单详情的 `cards.list`、GET 售后详情的 `messages.list` 返回全部记录；不要用 `cardPage` 或 `messagePage` 截断它们。
- POST/PUT 返回的是操作后的详情快照，其中卡密、消息可能按每页 100/50 条返回；要获取完整记录，再调用对应 GET 详情。

成功响应：

```json
{"code": 0, "message": "success", "data": {}, "requestId": "req_example"}
```

列表 `data`：

```json
{"list": [], "total": 0, "page": 1, "pageSize": 20}
```

同时检查 HTTP 状态、`code` 及返回的业务状态。退款受理成功不表示资金已经退回。

## 4. 接口清单

以下路径均接在 `/openapi/v1/seller` 后。

| 方法 | 路径 | 用途 |
| --- | --- | --- |
| GET | `/orders` | 分页获取自有商品与供货订单 |
| GET | `/orders/{id}` | 获取订单完整详情 |
| POST | `/orders/{id}/recharge-result` | 处理人工充值，或上传充值凭证 |
| POST | `/orders/{id}/deliver` | 本地卡密出库、实物发货 |
| PUT | `/orders/{id}/logistics` | 更新实物运单 |
| POST | `/orders/{id}/refund` | 全额退还剩余可退金额 |
| GET | `/aftersales` | 分页获取可见售后 |
| GET | `/aftersales/{id}` | 获取售后完整详情 |
| POST | `/aftersales/{id}/messages` | 回复或举证 |
| POST | `/aftersales/{id}/decision` | 提交商家处理意见 |
| POST | `/aftersales/{id}/exchange` | 处理已批准换货的物流 |

## 5. 订单接口

### 5.1 获取订单列表

```http
GET /openapi/v1/seller/orders?page=1&pageSize=20&status=pending
```

| 查询参数 | 类型 | 说明 |
| --- | --- | --- |
| page / pageSize | integer | 默认 1/20，最大每页 100 |
| status | string | 按订单状态精确筛选；不传时不限定 |
| productType | string | `card`、`recharge`、`physical`；不传时不限定 |
| orderNo | string | 订单号精确筛选 |

查询全部时省略对应参数；`all` 不属于这两个筛选字段的约定值。

```json
{
  "code": 0,
  "message": "success",
  "data": {
    "list": [{
      "id": 12345,
      "orderNo": "D26102812345678901",
      "goodsName": "充值商品",
      "skuName": "",
      "productType": "recharge",
      "quantity": 1,
      "status": "pending",
      "fulfillmentStatus": "pending",
      "isPay": true,
      "isSeller": true,
      "role": "seller",
      "totalPrice": 10,
      "createdAt": 1793145600,
      "paymentTime": 1793145601,
      "completedAt": 0
    }],
    "total": 1,
    "page": 1,
    "pageSize": 20
  },
  "requestId": "req_example"
}
```

对于 `role=supplier` 的纯供货订单，不返回买家的 `totalPrice`、`refundedAmount`，返回本店的 `supplyAmount`。缺少历史供货报价时 `supplyAmount=null`，不能当成零元或用当前商品价格反推。

### 5.2 获取订单详情

```http
GET /openapi/v1/seller/orders/12345
```

`data` 为订单对象。详情包含操作资格、全部卡密；实际履约方还会取得履约所需充值资料、输入模板及验证码信息。实物订单返回 `physical`。

```json
{
  "code": 0,
  "message": "success",
  "data": {
    "id": 12345,
    "orderNo": "D26102812345678901",
    "goodsName": "卡密商品",
    "skuName": "",
    "productType": "card",
    "quantity": 1,
    "status": "success",
    "fulfillmentStatus": "success",
    "isPay": true,
    "isSeller": true,
    "role": "seller",
    "totalPrice": 10,
    "refundedAmount": 0,
    "createdAt": 1793145600,
    "paymentTime": 1793145601,
    "completedAt": 1793145602,
    "canFulfill": true,
    "canProcessRecharge": false,
    "canUploadRechargeProof": false,
    "canRefund": true,
    "refundPending": false,
    "rechargeProof": null,
    "cards": {
      "list": [{"id": 10001, "cardNumber": "EXAMPLE_CARD_NUMBER", "cardSecret": "EXAMPLE_CARD_SECRET", "expireTime": 0}],
      "total": 1,
      "page": 1,
      "pageSize": 1
    }
  },
  "requestId": "req_example"
}
```

`cards.pageSize` 在 GET 详情中随完整条数变化，不固定为 100。非卡密订单也可返回空 `cards.list`。

### 5.3 处理人工充值

```http
POST /openapi/v1/seller/orders/12345/recharge-result
```

| 参数 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| status | string | 是 | `processing` 处理中、`success` 成功、`canceled` 取消人工充值并进入原退款流程 |
| message | string | 否 | 处理说明，最多 255 字符 |

```json
{"status": "success", "message": "充值已完成"}
```

该操作要求本店是实际履约方。成功、处理中仅用于人工充值；远程货源订单不能用该接口人工写成成功。取消仅适用于当前可取消的人工充值订单，结果通过详情查询确认。

成功 `data` 为订单详情快照。该请求不支持按 `requestNo` 建立新的通用幂等语义；重复操作受订单状态约束。

#### 上传充值凭证

同一路径使用 `multipart/form-data`：

| 字段 | 类型 | 说明 |
| --- | --- | --- |
| processData | string | JSON 字符串，固定示例 `{"action":"saveRechargeProof"}` |
| rechargeProofImage | file | JPEG、PNG、WebP 图片，大小不超过 2 MiB，像素总量不超过 4000 万 |

```bash
curl "${KYS_BASE_URL}/openapi/v1/seller/orders/12345/recharge-result" \
  -H "X-KYS-User-Id: ${KYS_USER_ID}" \
  -H "X-KYS-Key: ${KYS_OPENAPI_KEY}" \
  --form 'processData={"action":"saveRechargeProof"}' \
  --form "rechargeProofImage=@${KYS_PROOF_FILE}"
```

上传凭证与提交充值成功分两次调用。上传成功返回：

```json
{
  "code": 0,
  "message": "success",
  "data": {
    "rechargeProof": {
      "url": "/frontApi/order/recharge-proof/12345?key=EXAMPLE_ACCESS_KEY",
      "uploadedAt": 1793145600,
      "expiresAt": 1793404800
    }
  },
  "requestId": "req_example"
}
```

凭证链接可能为相对地址，请接在当前商户域名后访问；链接携带访问凭证，应保密，按 `expiresAt` 判断有效期。

### 5.4 本地卡密出库、实物发货

```http
POST /openapi/v1/seller/orders/12345/deliver
```

本地卡密出库提交空 JSON 对象，由平台从该订单的本地货源库存出库，接入方不上传卡密正文：

```json
{}
```

实物发货提交：

```json
{"carrierCode": "sf_express", "trackingNo": "SFEXAMPLE0001"}
```

| 参数 | 类型 | 说明 |
| --- | --- | --- |
| carrierCode | string | 实物必填，取 `physical.carrierOptions[].code` |
| trackingNo | string | 实物必填，运单号，最长 64 字符，字母、数字或连字符；保存时转为大写 |

本店须是实际履约方。实物还需 `physical.canShip=true`；上游供货的实物订单由实际供货方处理。快递编码是平台代码，例如 `sf_express`，不使用显示名称或第三方的 `SF` 编码。

成功 `data` 为订单详情快照。本地卡密重放不会重复出库；已发货订单填写相同现存运单可复用结果，修改运单请使用下一接口。

### 5.5 更新实物运单

```http
PUT /openapi/v1/seller/orders/12345/logistics
```

```json
{"carrierCode": "sf_express", "trackingNo": "SFEXAMPLE0002"}
```

参数与实物发货相同。要求本店具备履约权限，且详情中的 `physical.canUpdateLogistics=true`。成功 `data` 为订单详情快照。

### 5.6 订单退款

```http
POST /openapi/v1/seller/orders/12345/refund
```

| 参数 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| message | string | 否 | 退款说明，最多 255 字符 |
| requestNo | string | 是 | 8～48 位字母、数字、下划线或连字符，固定表示本次退款请求 |

```json
{"message": "同意退款", "requestNo": "seller_refund_20261028_0001"}
```

按当前权限，对交易成功或部分退款订单退还全部剩余可退金额，不传金额。兑换订单不支持原支付退款。相同 `requestNo` 重试必须保持说明等请求内容一致；不要为一次超时退款重新生成编号。

成功 `data` 为订单详情快照。退款异步处理，可能先返回原成功状态或 `refundPending=true`；继续查询，最终以 `status`、`refundedAmount`、`refundPending` 确认结果。纯供货方不返回买家退款金额。

## 6. 售后接口

### 6.1 获取售后列表

```http
GET /openapi/v1/seller/aftersales?page=1&pageSize=20&status=pending
```

| 查询参数 | 类型 | 说明 |
| --- | --- | --- |
| page / pageSize | integer | 默认 1/20，最大每页 100 |
| status | string | 售后状态精确筛选，不限定时省略 |
| orderNo | string | 关联订单号精确筛选 |

`data` 是分页容器，项字段见第 8 节。可见范围包括本店销售订单及有效供货订单的售后，具体回复资格通过 `canReply` 判断。

### 6.2 获取售后详情

```http
GET /openapi/v1/seller/aftersales/5001
```

```json
{
  "code": 0,
  "message": "success",
  "data": {
    "id": 5001,
    "complainNo": "CP26102812345678901",
    "orderId": 12345,
    "orderNo": "D26102812345678901",
    "goodsName": "充值商品",
    "productType": "recharge",
    "topic": "充值未到账",
    "status": "pending",
    "serviceType": "",
    "serviceStatus": "",
    "createdAt": 1793145600,
    "updatedAt": 1793145600,
    "canReply": true,
    "messages": {
      "list": [{"id": 10001, "sender": "user", "senderLabel": "买家", "content": "充值尚未到账", "messageFormat": "text", "timestamp": 1793145600}],
      "total": 1,
      "page": 1,
      "pageSize": 1
    }
  },
  "requestId": "req_example"
}
```

GET 详情一次返回全部 `messages.list`，`page=1`，`pageSize=total`。实物售后还可能返回 `physical`、`canHandleExchange`。

### 6.3 回复、举证

```http
POST /openapi/v1/seller/aftersales/5001/messages
```

| 参数 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| content | string | 条件必填 | 回复正文，最多 255 字符；正文、附件至少一项非空 |
| attachments | string[] | 否 | 最多 5 个图片 URL，每项为 HTTP/HTTPS 地址，最长 255 字符 |
| requestNo | string | 是 | 8～48 位字母、数字、下划线或连字符 |

```json
{"content": "已收到，正在核实", "attachments": [], "requestNo": "seller_reply_20261028_0001"}
```

只有销售店铺能向买家回复或举证；仅供货身份可以读相关售后，但不能据此向买家发消息。成功 `data` 为售后详情快照。

### 6.4 提交商家处理意见

```http
POST /openapi/v1/seller/aftersales/5001/decision
```

参数与回复相同，但 `content` 最多 245 字符。平台在消息前标明“商家处理意见”。

```json
{"content": "建议核实充值账号后处理", "attachments": [], "requestNo": "seller_decision_20261028_0001"}
```

该接口记录处理意见，不直接完成售后、批准退款或裁决纠纷。微信投诉同样只按回复处理。成功 `data` 为售后详情快照。

同一售后下回复、意见须各自使用不同的 `requestNo`。相同编号重试保持正文、附件、动作一致；同编号提交不同内容返回冲突。售后结束后不能追加新的回复或意见。

### 6.5 处理换货物流

```http
POST /openapi/v1/seller/aftersales/5001/exchange
```

仅限本店作为履约方处理已批准的实物换货，且当前状态允许该动作。不会因供货身份开放售后批准、拒绝或退款裁决。

| 参数 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| action | string | 是 | `receive_return` 确认收到退货；`replacement_ship` 寄出换货 |
| message | string | 否 | 说明，最多 255 字符 |
| carrierCode | string | 寄出换货时 | 快递编码，取 `physical.carrierOptions[].code` |
| trackingNo | string | 寄出换货时 | 换货运单号 |

```json
{"action": "receive_return", "message": "已收到退回商品"}
```

```json
{"action": "replacement_ship", "carrierCode": "sf_express", "trackingNo": "SFEXAMPLE0003", "message": "换货已寄出"}
```

通常在 `serviceStatus=return_shipped` 时确认收货，随后变为 `awaiting_replacement`；寄出换货成功后售后变为 `completed`。成功 `data` 为售后详情快照，换货物流在 `physical.replacementLogistics`。

## 7. 错误、限流与重试

| 类别 | 配额 |
| --- | --- |
| 订单 GET 接口 | 每用户每分钟 120 次 |
| 售后 GET 接口 | 每用户每分钟 120 次 |
| 订单写接口 | 每用户每分钟 30 次 |
| 售后写接口 | 每用户每分钟 30 次 |
| 用户总额度 | 每用户每分钟 600 次，瞬时容量 30 |
| 认证尝试 | 同一商户、同一来源 IP 每分钟 200 次 |

店铺接口与该用户的采购 OpenAPI 共享账户和对应分组额度。响应可携带 `X-RateLimit-Limit`、`X-RateLimit-Remaining`、`X-RateLimit-Reset`，以 `Retry-After` 的秒数退避。

| HTTP | 常见 code | 处理方式 |
| --- | --- | --- |
| 400 | 400001 | 参数错误、状态不允许、订单或售后不属于本店、店铺未通过审核或停用 |
| 401 | 401001 | 用户 ID、Key 不匹配，账户或 API 未启用 |
| 403 | 403001、403002 | 权限不足、IP 不在白名单或经营资格不满足 |
| 404 | 404001、404002 | 域名、路径或资源不存在 |
| 409 | 409001 | 请求编号与已有内容冲突，核对原请求 |
| 429 | 429001 | 请求频率超限，按 `Retry-After` 等待 |
| 503 | 503001 | 暂时不可用；读取可退避重试，写入先确认结果 |
| 500 | 500001 | 保存 `requestId` 联系商户；写入结果未知时先查详情 |

域名或入口直接被网关拒绝时，可能使用其他错误正文，先检查 HTTP 状态和响应类型。

重试规则：

- GET 可在退避后重试，列表按订单 ID、消息 ID 去重。
- 退款、回复、处理意见保留同一 `requestNo` 和相同内容重试。
- `requestNo` 不是所有写接口的通用幂等参数。充值处理、运单、换货先查询当前状态及已有结果，再决定是否继续操作。
- 微信投诉远端结果待确认时，先核对详情与消息，不换编号盲重发。
- 本分组不新增管理事件订阅，订单与售后采集采用分页查询；涉及采购方的通知仍按原订单回调约定由平台发送。

## 8. 返回字段

### 8.1 订单

| 字段 | JSON 类型 | 说明 |
| --- | --- | --- |
| id / orderNo | integer / string | 订单 ID、平台单号 |
| goodsName / skuName / goodsImage | string 或 null | 商品、规格显示信息 |
| productType | string | `card`、`recharge`、`physical` |
| quantity / isPay | integer / boolean | 数量、是否付款 |
| status | string | `pending`、`processing`、`success`、`canceled`、`partially_refunded`、`refunded` |
| fulfillmentStatus | string | 履约状态代码，用于辅助展示；最终交易结果仍读取 status |
| isSeller / role | boolean / string | 是否销售方，角色 `seller/supplier` |
| totalPrice | number | 销售方可见的买家实付金额 |
| refundedAmount | number | 销售方详情可见的累计退款金额 |
| supplyAmount | number 或 null | 纯供货方可见的本店供货金额，可能四位小数；无历史报价时为 null |
| createdAt / paymentTime / completedAt | integer | 创建、付款、完成时间；未发生可为 0 |
| remark / message | string 或 null | 备注、处理说明，详情字段 |
| processStart / processCurrent / processTarget | integer | 处理进度，详情字段 |
| canFulfill / canProcessRecharge / canUploadRechargeProof / canRefund / refundPending | boolean | 详情的操作资格及退款进行状态 |
| inputTemplateSnapshot | object 或 array | 履约输入模板；无模板时可为 `[]` |
| normalizedInputValues | array | 履约资料，按模板 fieldKey 读取；仅按履约权限返回 |
| verificationCodeRequired / verificationCodeInfo | boolean / object 或 null | 验证码要求与当前状态；只返回履约所需信息 |
| rechargeProof | object 或 null | 凭证 url、uploadedAt、expiresAt |
| cards | object | `list,total,page,pageSize`；卡密项为 `id,cardNumber,cardSecret,expireTime` |
| physical | object | 实物地址、物流、快递选项与可执行操作 |

非适用字段可能省略或为 null，未知附加字段请忽略。纯供货方不返回买家实付和退款金额，不用 `supplyAmount` 替代买家订单总额。

### 8.2 售后与物流

售后项字段：`id`、`complainNo`、`orderId`、`orderNo`、`goodsName`、`productType`、`topic`、`status`、`serviceType`、`serviceStatus`、`createdAt`、`updatedAt`、`canReply`。

售后 `status`：`pending` 待处理、`processing` 处理中、`completed` 已完成、`terminated` 已终止。

消息在 `messages.list`，包括 `id`、`sender`、`senderLabel`、`content`、`messageFormat`、`timestamp`。`sender` 为 `user` 或 `service`，展示时优先使用 `senderLabel`；`messageFormat` 常见 `text`、`image`。

实物售后的 `serviceType` 为 `refund_only`、`return_refund`、`exchange`，普通售后可为空。`physical` 包括 `id`、`serviceType`、`serviceStatus`、`returnAddress`、`returnLogistics`、`replacementLogistics`、`carrierOptions`。未形成的物流为 null，地址未提供时可为 `[]`。

订单 `physical` 包括 `shippingAddress`、`logistics`、`carrierOptions`、`canShip`、`canUpdateLogistics`。地址及物流按返回内容展示，不自行拼接其他接口编号。

## 9. 请求示例

```bash
curl --get "${KYS_BASE_URL}/openapi/v1/seller/orders" \
  -H "X-KYS-User-Id: ${KYS_USER_ID}" \
  -H "X-KYS-Key: ${KYS_OPENAPI_KEY}" \
  --data-urlencode 'page=1' \
  --data-urlencode 'pageSize=20' \
  --data-urlencode 'status=pending'
```

```bash
curl "${KYS_BASE_URL}/openapi/v1/seller/aftersales/5001/messages" \
  -H "X-KYS-User-Id: ${KYS_USER_ID}" \
  -H "X-KYS-Key: ${KYS_OPENAPI_KEY}" \
  -H 'Content-Type: application/json' \
  --data '{"content":"已收到，正在核实","attachments":[],"requestNo":"seller_reply_20261028_0001"}'
```

`KYS_BASE_URL` 形如 `https://merchant.example.com`，不包含 `/openapi/v1`。示例编号、卡密及凭证均为演示值；成功响应中的敏感资料也应按密钥同等要求保护。
