# 供应商入驻协议授权

> 入驻申请页协议弹窗：协议列表 / 注册回传协议版本、712 错误码处理；运营后台供应商详情【协议授权记录】及协议版本正文查看

- 类型：前端对接
- 分类：供应商
- 端：供应商小程序 / App / 运营后台
- 后端服务：supplier / admin
- 状态：已上测
- 需求单：XYLZ-3569
- 更新：2026-10-17
- 网页版：https://doc.xfun.fun/supplier-agreement-auth-api

## 总览

| 端 | 接口 | 变更 | 鉴权 |
|----|----|----|----|
| 供应商端 xm-bulky-supplier | `POST /login/agreement/list` | **新增** 入驻协议 + 隐私政策当前版本及正文 | 免登录 |
| 供应商端 xm-bulky-supplier | `POST /login/register` | `入参新增` `agreementVersionIds` | 免登录（同原接口） |
| 运营后台 xm-bulky-admin | `POST /supplier/agreementAuth/list` | **新增** 供应商详情右上角【协议授权记录】，返回入驻申请时的授权信息 | 需登录（同现有后台接口） |
| 运营后台 xm-bulky-admin | `POST /supplier/agreementAuth/versionContent` | **新增** 查看某次授权对应的协议版本正文 | 需登录 |

- 所有接口均为 `POST` + JSON，返回统一结构 `{ "code": 0, "msg": "success", "data": … }`，`code != 0` 时 `msg` 可直接 toast。
- 时间字段格式统一为 `yyyy-MM-dd HH:mm:ss`。
- 协议正文仍由运营后台「协议管理」维护（供应商端：入驻协议、隐私政策）。**版本由后端按正文自动生成**，运营改了正文，`versionId` 就会变。
- 「我已阅读并同意」不调接口，注册时直接提交版本 id。

## 入驻申请页前端流程

进入入驻申请页 → ① `/login/agreement/list` 拉协议，自动弹窗 → 3 秒倒计时后点「我已阅读并同意」（不调接口，记下全部 versionId） → 勾选复选框，第一步「下一步」 → ② `/login/register` 带上 agreementVersionIds

#### 纯前端交互（后端无接口）

- **进入页面**：调 ① 后自动弹窗，底部复选框默认未勾选。弹窗标题「请阅读并同意以下协议」，副标题「请仔细阅读以下协议，确认同意后继续入驻。」；tab 按 ① 返回的数组顺序（平台入驻协议在前、隐私政策在后），tab 文字直接用 `name`（不带书名号）；正文区可上下滚动，底部提示「协议内容可上下滚动查看」。
- **倒计时**：按钮显示「我已阅读并同意（3s）」，倒计时期间置灰不可点；结束后变为「我已阅读并同意」。每次打开弹窗都从 3 秒重新开始。
- **我已阅读并同意**：关闭弹窗、自动勾选复选框，在页面内存里记下 ① 返回的 **全部** `versionId`（不调接口）。
- **× / 暂不同意**：关闭弹窗，复选框保持未勾选；用户 **可以继续填写入驻资料**，不锁表单、不跳转。
- **点击未勾选的复选框**：不能直接勾上，重新打开协议弹窗，倒计时重新开始，只能通过「我已阅读并同意」完成勾选。
- **点击协议名称**（复选框后的《平台入驻协议》《隐私政策》链接，书名号由前端拼接）：打开弹窗并切到对应 tab 查看正文，直接用 ① 已拿到的 `content`，不用再调接口。
- **「下一步」按钮**：放在页面底部右侧（与复选框同一行），使用缩小后的蓝色按钮；**始终可点击**，不因未勾选或资料不全而置灰，点击后按下表反馈。

| 当前情况 | 点击「下一步」后的处理 |
|----|----|
| 未同意协议 | toast「请先阅读并同意平台入驻协议和隐私政策」，打开协议弹窗（倒计时重新开始） |
| 已同意，资料未填写完整或格式错误 | 提示具体问题（如「请填写卖家名称」），并 **滚动定位到第一个错误字段**（按表单从上到下的顺序取第一个） |
| 已同意，资料校验通过 | 在页面内保留当前资料，进入第二步「提交申请」（第一步不调后端，第二步提交时才调 ②） |

- **手动取消勾选**：恢复未同意状态并 **丢弃记下的 versionId**；再次勾选或点「下一步」都要重新走弹窗（重新调 ① 拿最新版本）。
- **第二步返回第一步**：保留已填写资料、勾选状态和 versionId，不重复弹窗。
- **① 返回空数组**（运营关闭了两份协议）：不弹窗、不校验勾选，注册时 `agreementVersionIds` 不传或传 `[]`。
- **收到 712**：见接口 `POST /login/register` 的失败处理。

## 供应商端接口

### POST /login/agreement/list

**新增**

入驻协议列表。进入入驻申请页时调用；也在 ② 注册返回 712 时重新调用刷新。

#### 请求参数

无，传 `{}` 即可。

#### 响应 data（数组）

| 字段 | 类型 | 说明 |
|----|----|----|
| `agreementId` | Long | 协议 id |
| `versionId` | Long | 协议版本快照 id，**② 注册时放进 `agreementVersionIds` 回传** |
| `version` | Integer | 版本号（同一协议从 1 递增，可选展示） |
| `type` | Integer | 4 = 平台入驻协议；2 = 隐私政策 |
| `name` | String | 协议名称，不带书名号，如「平台入驻协议」；直接作 tab 标题，复选框后的链接文字由前端加书名号 |
| `content` | String | 协议正文（富文本 HTML，与现有接口 `/home/agreement/content` 格式一致） |

#### 响应示例

```json
{
  "code": 0,
  "msg": "success",
  "data": [
    { "agreementId": 13, "versionId": 1, "version": 1, "type": 4, "name": "平台入驻协议", "content": "<p>…</p>" },
    { "agreementId": 15, "versionId": 2, "version": 1, "type": 2, "name": "隐私政策",     "content": "<p>…</p>" }
  ]
}
```

### POST /login/register

**入参新增**

提交入驻申请（原接口，第二步「提交申请」时调用），原有字段不变，新增一个字段。

#### 新增请求参数

| 字段 | 类型 | 必填 | 说明 |
|----|----|----|----|
| `agreementVersionIds` | Long\[\] | 否 | ① 返回的 **全部** `versionId`（顺序无所谓，最多 10 个）。**传了**：须与当前版本集合完全一致（缺、多、过期都不行），否则返回 712。**不传 / `null` / `[]`**：后端按当前协议版本写授权记录，等同于传了 ① 此刻返回的全部 versionId；版本快照尚不存在时按 ① 同样规则生成；运营关闭了全部协议时不写记录。新版入驻页用户同意后应传，以便协议在填写期间更新时能拦截（712） |

```json
{
  "...原有字段...": "...",
  "agreementVersionIds": [1, 2]
}
```

#### 失败处理

- `code = 712`「协议内容已更新，请重新阅读并同意」：传的版本与当前版本不一致（漏传 / 多传 / 传了旧版本 / 用户停留期间运营改了协议）。**只要传了就会校验**。
- 收到 712 时前端依次：
  1.  toast 该文案；
  2.  从第二步回到第一步，**保留已填写资料**（含第二步已填内容）；
  3.  取消勾选，丢弃记下的 versionId；
  4.  重新调 ① 刷新正文，自动打开弹窗，倒计时从 3 秒重新开始；
  5.  用户同意后再次提交 ②，带新的 versionId（验证码未被消耗，可沿用；若已过期按原逻辑重新获取）。
- 712 在校验短信验证码 **之前**返回，不会消耗验证码次数、不会生成供应商。

> [!NOTE]
> 注册成功后，后端以 **入驻时间**作为授权同意时间写入授权记录，同时保存协议版本、IP 和 deviceType（不记录弹窗点击时间）。

## 错误码速查（供应商端）

| code | msg | 接口 | 前端处理 |
|----|----|----|----|
| `712` | 协议内容已更新，请重新阅读并同意 | `/login/register` | toast + 回到第一步（保留资料）+ 取消勾选 + 重新调 ① 刷新并弹窗，倒计时重来 |

## 运营后台接口

### POST /supplier/agreementAuth/list

**新增**

供应商详情页右上角【协议授权记录】按钮，点击打开右侧抽屉「协议授权信息」。返回 **单个对象**：供应商入驻申请时授权的平台入驻协议和隐私政策。

#### 请求参数

| 字段 | 类型 | 必填 | 说明      |
|------|------|------|-----------|
| `id` | Long | 是   | 供应商 id |

#### 响应 data（对象，可能为 null）

| 字段 | 类型 | 说明 |
|----|----|----|
| `authStatus` | String | 授权状态，固定「已授权」，直接展示为绿色标签 |
| `authTime` | String | 授权时间 = 入驻（注册）时间；历史已入驻供应商为补录记录的授权时间 |
| `authStage` | String | 授权环节，固定「入驻申请」（含历史已入驻供应商的补录记录） |
| `agreements` | Array | 授权协议：平台入驻协议在前、隐私政策在后 |
|   `agreementId` | Long | 协议 id |
|   `agreementType` | Integer | 4 = 入驻协议；2 = 隐私政策 |
|   `agreementName` | String | 协议名称，不带书名号，直接作卡片标题 |
|   `versionId` | Long | 协议版本快照 id；**非空时卡片右侧显示「查看协议 \>」**，点击调 [versionContent](#supplier-agreementauth-versioncontent) |
|   `version` | Integer | 版本号（可不展示） |

#### 响应示例

```json
{
  "code": 0,
  "msg": "success",
  "data": {
    "authStatus": "已授权",
    "authTime": "2026-10-16 09:15:32",
    "authStage": "入驻申请",
    "agreements": [
      { "agreementId": 13, "agreementType": 4, "agreementName": "平台入驻协议", "versionId": 1, "version": 1 },
      { "agreementId": 15, "agreementType": 2, "agreementName": "隐私政策",     "versionId": 2, "version": 1 }
    ]
  }
}
```

无授权记录时：

```json
{ "code": 0, "msg": "success", "data": null }
```

#### 抽屉布局（对照原型图）

- 标题「协议授权信息」，右上角 ×，底部「关闭」按钮；标题下方「供应商：*供应商名称*」（名称取详情页已有数据）。
- 「授权信息」表格三行：授权状态（`authStatus`，绿点 + 绿色标签）、授权时间（`authTime`）、授权环节（`authStage`）。
- 「授权协议」：`agreements` 每项一张卡片，左侧文件图标 + `agreementName`，右侧「查看协议 \>」（`versionId` 非空时显示）。
- 卡片下方灰字「点击查看授权时对应版本的协议正文。」
- `data` 为 null（补录脚本执行前、或协议关闭期间注册的供应商）：显示空状态「暂无授权记录」。

#### 失败处理

- 供应商不存在：`code != 0`，msg「信息不存在」。

### POST /supplier/agreementAuth/versionContent

**新增**

点击协议卡片的「查看协议」，展示该次授权对应版本的协议正文（快照，不随运营后续修改变化），可用弹窗或新抽屉展示，标题用 `name`。

#### 请求参数

| 字段 | 类型 | 必填 | 说明                   |
|------|------|------|------------------------|
| `id` | Long | 是   | 协议卡片的 `versionId` |

#### 响应 data

| 字段          | 类型    | 说明                             |
|---------------|---------|----------------------------------|
| `versionId`   | Long    | 版本快照 id                      |
| `agreementId` | Long    | 协议 id                          |
| `name`        | String  | 协议名称，不带书名号             |
| `version`     | Integer | 版本号                           |
| `content`     | String  | 正文（富文本 HTML）              |
| `createTime`  | String  | 该版本生成时间（即首次下发时间） |

```json
{
  "code": 0,
  "msg": "success",
  "data": { "versionId": 1, "agreementId": 13, "name": "平台入驻协议", "version": 1,
            "content": "<p>…</p>", "createTime": "2026-10-20 09:58:11" }
}
```

## 联调注意

- 注册失败（如验证码错误）重试时继续带同一组 `agreementVersionIds` 即可；返回 712 时才需要重新拉 ①。
- 现有接口 `/home/agreement/content` 不受影响，其他页面可以继续使用。
- 历史已入驻供应商由补录脚本按入驻时间各补一条入驻协议、隐私政策的授权记录，挂当前协议版本，运营端显示为「入驻申请」，同样可「查看协议」。补录脚本在代码部署后执行，执行前列表为空。
- 登录页（手机号登录）上的协议勾选不在本次范围内，不产生授权记录。
