# 售后申诉 — 小程序端新增接口

- **端类型**：小程序端
- **变更类型**：新增接口
- **日期**：2026-06-22
- **关联 Issue**：https://git.1814.love:8443/wx/HL/issues/4161
- **关联 PR**：#4185（小程序端接口）/ #4162 #4165 #4168 #4187（基础能力）
- **后端负责人**：腰苏图（yaosutu）

---

## 一、接口背景

v3 全新售后服务中心（com.hulalv.aftersale）上线。小程序端用户可发起投诉或申诉、查看自己的工单列表与详情、以及在工单未处置前随时撤回。

新增 4 个接口（含 1 个原有但归入本域）：发起工单、我的工单列表、工单详情、撤回工单。

本次为全新域，不影响任何已有接口。

---

## 二、变更清单

| 编号 | 方法 | 路径 | 说明 |
|------|------|------|------|
| 1 | POST | /v3/mp/aftersale/ticket | 发起投诉/申诉工单 |
| 2 | GET | /v3/mp/aftersale/ticket/my | 我的工单列表（按订单分页） |
| 3 | GET | /v3/mp/aftersale/ticket/{id} | 工单详情 |
| 4 | POST | /v3/mp/aftersale/ticket/{id}/withdraw | 撤回工单 |

---

## 三、接口详情

| 属性 | 说明 |
|------|------|
| 认证方式 | JWT Bearer Token（C 端 Token，Gateway 解析后注入 userId） |
| userId 传递 | Gateway 解析 JWT 后注入到请求属性，后端从 HttpServletRequest 取；前端无需传 userId 字段 |
| 限流 | Gateway 默认限流策略 |
| 路径前缀 | /v3/mp/aftersale/ticket |

---

## 四、接口入参

### 4.1 POST /v3/mp/aftersale/ticket — 发起工单

请求体（JSON）：

| 字段 | 类型 | 必填 | 约束 | 说明 |
|------|------|------|------|------|
| orderId | Long | 是 | — | 订单 ID |
| category | String | 是 | COMPLAINT / APPEAL | 大类：投诉/申诉 |
| type | String | 是 | 见 §六枚举 | 细分类型，必须与 category 匹配 |
| title | String | 是 | 最多 100 字 | 工单标题 |
| description | String | 是 | 最多 2000 字 | 诉求描述 |
| targetRefId | Long | 条件必填 | — | 申诉时必填：关联退款申请 ID；投诉时可不传（不传则用 orderId） |
| claimType | String | 否 | REFUND / RECTIFY / EXPLANATION | 用户诉求类型，仅供参考 |
| claimAmount | Number | 否 | — | 用户期望金额，仅供参考，无约束力 |
| attachments | Array<String> | 否 | 最多 10 个 | 附件 URL 列表（图片/视频） |

### 4.2 GET /v3/mp/aftersale/ticket/my — 我的工单列表

Query 参数：

| 字段 | 类型 | 必填 | 约束 | 说明 |
|------|------|------|------|------|
| orderId | Long | 是 | — | 订单 ID，只查该订单下的工单 |
| pageNo | Integer | 否 | >= 1 | 页码，默认 1 |
| pageSize | Integer | 否 | 1-50 | 每页条数，默认 20 |

### 4.3 GET /v3/mp/aftersale/ticket/{id} — 工单详情

路径参数：id（String/Long）必填，工单 ID。

注：后端校验工单归属，非本人工单返回 586014。

### 4.4 POST /v3/mp/aftersale/ticket/{id}/withdraw — 撤回工单

路径参数：id（String/Long）必填，工单 ID。

无请求体。后端校验：1. 工单归属（非本人返回 586014）；2. 状态（只有 SUBMITTED / PROCESSING 可撤回，否则返回 586013）。

---

## 五、出参字段

### 5.1 发起工单 — 返回工单 ID

```json
{"code": 200, "data": "1895000000000001"}
```

data 为 String（Long），即新建工单的 ID。

### 5.2 我的工单列表 — PageResult 结构

```json
{
  "code": 200,
  "data": {
    "list": [],
    "total": 5,
    "pageNo": 1,
    "pageSize": 20
  }
}
```

### 5.3 工单详情及列表行 — TicketMpRespVO 字段

| 字段 | 类型 | 说明 |
|------|------|------|
| id | String（Long） | 工单 ID |
| orderId | String（Long） | 关联订单 ID |
| category | String | 大类枚举值：COMPLAINT / APPEAL |
| categoryLabel | String | 大类中文名：投诉 / 申诉 |
| type | String | 细分类型枚举值，见 §六 |
| typeLabel | String | 细分类型中文名 |
| title | String | 工单标题 |
| description | String | 诉求描述 |
| claimType | String | 用户诉求类型（可选） |
| claimAmount | Number | 用户期望金额（可选） |
| attachments | Array<String> | 附件 URL 列表 |
| status | String | 工单状态枚举值，见 §六 |
| statusLabel | String | 工单状态中文名 |
| resolutionRemark | String | 处置说明（RESOLVED / CLOSED 后可见，用于展示给用户） |
| canWithdraw | Boolean | 是否可撤回（SUBMITTED / PROCESSING 且非终态时为 true） |
| createdAt | String（ISO 8601） | 创建时间 |
| updatedAt | String（ISO 8601） | 更新时间 |

注：C 端视图隐藏处置细节（decision / resolutionType / resolutionAmount / approvalNo / linkedRefundId），仅展示 status 和 resolutionRemark。

### 5.4 撤回工单

```json
{"code": 200, "data": null}
```

---

## 六、枚举 / 数据字典

### AftersaleCategory — 工单大类

| 枚举值 | 中文名 | 场景 |
|--------|--------|------|
| COMPLAINT | 投诉 | 对已发生的服务质量不满 |
| APPEAL | 申诉 | 对某个处理结果不服，求复核 |

### AftersaleTicketType — 细分类型（type 字段）

| 枚举值 | 中文名 | 归属大类 |
|--------|--------|----------|
| ITINERARY | 行程 | COMPLAINT |
| HOTEL | 酒店 | COMPLAINT |
| VEHICLE | 车 | COMPLAINT |
| GUIDE | 导游 | COMPLAINT |
| OTHER | 其他 | COMPLAINT |
| REFUND_REJECTED | 退款被拒 | APPEAL |
| REFUND_AMOUNT_DISPUTE | 退款金额异议 | APPEAL |

注：type 必须与 category 匹配，否则返回 586012。

### AftersaleTicketStatus — 工单状态

| 枚举值 | 中文名 | 终态 | 说明 |
|--------|--------|------|------|
| SUBMITTED | 已提交 | 否 | 等待受理 |
| PROCESSING | 处理中 | 否 | 客服受理中 |
| RESOLVED | 已处置 | 否 | 处置方案落定（退款类等待到账） |
| CLOSED | 已关闭 | 是 | 流程终结 |
| WITHDRAWN | 已撤回 | 是 | 用户主动撤回 |

canWithdraw = true 的条件：status 为 SUBMITTED 或 PROCESSING。

### claimType — 用户诉求类型（参考值，无强制约束）

| 常见值 | 中文名 |
|--------|--------|
| REFUND | 退款 |
| RECTIFY | 整改 |
| EXPLANATION | 要个说法 |

---

## 七、错误码

| 错误码 | 说明 | 触发场景 |
|--------|------|----------|
| 586009 | 工单不存在 | 传入 id 找不到工单 |
| 586011 | 同一对象已存在活跃工单 | 防重复提交（同订单下同 target 已有进行中工单） |
| 586012 | category 与 type 不匹配 | 如 COMPLAINT 传了 REFUND_REJECTED |
| 586013 | 撤回时状态非法 | 终态或 RESOLVED 工单不可撤回 |
| 586014 | 非本人工单 | 查看/撤回他人工单 |
| 586015 | 申诉必须关联退款申请 | APPEAL 类工单未传 targetRefId |
| 586016 | 申诉关联退款状态不合法 | 关联的退款申请状态不允许申诉 |

---

## 八、示例

### 8.1 典型成功 — 发起投诉工单

请求：
```
POST /v3/mp/aftersale/ticket
Authorization: Bearer {mp_token}
Content-Type: application/json

{
  "orderId": 1800000000000099,
  "category": "COMPLAINT",
  "type": "HOTEL",
  "title": "酒店降级投诉",
  "description": "订单挂四星酒店实际入住三星，要求退差价",
  "claimType": "REFUND",
  "claimAmount": 600.00,
  "attachments": ["https://oss.example.com/img1.jpg"]
}
```

响应：
```json
{"code": 200, "data": "1895000000000001"}
```

### 8.2 典型成功 — 发起申诉工单（退款被拒）

```
POST /v3/mp/aftersale/ticket
Authorization: Bearer {mp_token}
Content-Type: application/json

{
  "orderId": 1800000000000099,
  "category": "APPEAL",
  "type": "REFUND_REJECTED",
  "targetRefId": 9876543210,
  "title": "退款申请被拒申诉",
  "description": "退款申请被拒，但供应商确实未提供服务，要求重新审核"
}
```

响应：
```json
{"code": 200, "data": "1895000000000002"}
```

### 8.3 边界情况 — 查询工单详情（已处置，展示处置说明）

```json
{
  "code": 200,
  "data": {
    "id": "1895000000000001",
    "orderId": "1800000000000099",
    "category": "COMPLAINT",
    "categoryLabel": "投诉",
    "type": "HOTEL",
    "typeLabel": "酒店",
    "title": "酒店降级投诉",
    "description": "订单挂四星酒店实际入住三星，要求退差价",
    "claimType": "REFUND",
    "claimAmount": 600.00,
    "attachments": ["https://oss.example.com/img1.jpg"],
    "status": "CLOSED",
    "statusLabel": "已关闭",
    "resolutionRemark": "经核实酒店确实降级，已退差价 500 元",
    "canWithdraw": false,
    "createdAt": "2026-06-22T09:00:00",
    "updatedAt": "2026-06-22T15:30:00"
  }
}
```

### 8.4 业务失败 — 申诉未传 targetRefId

```json
{"code": 586015, "msg": "申诉必须关联一笔有效的退款申请"}
```

### 8.5 业务失败 — 撤回已 CLOSED 工单

```json
{"code": 586013, "msg": "撤回时工单状态非法（只有 SUBMITTED / PROCESSING 可撤回）"}
```

---

## 九、业务边界

适用：
- 同一订单下同一 targetRef 只能有一个活跃工单（SUBMITTED / PROCESSING / RESOLVED），否则返回 586011
- 申诉（APPEAL）类工单必须关联一笔有效的退款申请（targetRefId 必填）
- SUBMITTED / PROCESSING 状态的工单可撤回，canWithdraw=true

不适用：
- 终态工单（CLOSED / WITHDRAWN）不可撤回
- 非本人工单不可查看/撤回

特殊边界：
- 工单从 PROCESSING 撤回后，若该工单已提交企微 OA，后端会自动撤回 OA 审批（异步，不影响前端响应）
- attachments 最多 10 个，超出返回参数校验错误

---

## 十、修改前后对比

本次为全新域新增接口，无旧接口对比。

---

## 十一、影响评估 / 回滚

本次为全新域，不影响任何已有小程序接口，可独立上线。

回滚方案：回滚后端服务版本即可。

---

## 十二、注意事项

1. 发起工单返回值是 String（Long）：data 字段为新建工单的 ID，是字符串而非数字。
2. C 端视图隐藏处置细节：resolutionAmount / decision / approvalNo 等字段在 C 端不返回，不要在小程序页面展示。resolutionRemark 是给用户看的处置说明，可展示。
3. canWithdraw 字段已由后端计算：前端直接用该字段判断是否显示撤回按钮，无需自行判断状态。
4. 所有 ID 为字符串：id / orderId 均序列化为字符串，前端不要转 Number。
5. category=APPEAL 时：targetRefId 必填（关联退款申请 ID），targetRefType 后端自动设为 REFUND_APPLICATION。

---

## 十三、关联 / 联系人

- Issue：https://git.1814.love:8443/wx/HL/issues/4161
- PR #4185（小程序端接口）：https://git.1814.love:8443/wx/HL/pulls/4185
- PR #4162（售后基建）：https://git.1814.love:8443/wx/HL/pulls/4162
- PR #4165（受理/处置 + OA 接入）：https://git.1814.love:8443/wx/HL/pulls/4165
- PR #4168（OA 回调内部接口）：https://git.1814.love:8443/wx/HL/pulls/4168
- PR #4187（退款驱动联动）：https://git.1814.love:8443/wx/HL/pulls/4187
- 后端负责人：腰苏图（yaosutu）
