feat: 增加门店身份注销流程

This commit is contained in:
2026-08-31 09:43:14 +08:00
parent 396597a160
commit e529bb5942
35 changed files with 6062 additions and 45 deletions
@@ -0,0 +1,477 @@
# 门店身份注销旧接口返回数据说明
整理日期:2026-08-28。依据旧接口文档及当天测试环境的真实响应。
**注销范围:当前 `store_user` 对应的门店用户身份,不是手机号主账号,不影响同手机号的其他身份。**
示例中的 `store_user_id`、`finance_identity_id` 和注销记录 `id` 均脱敏为 `0`,实际为正整数;脱敏值不能用于请求或业务判断。本文不包含 Token、手机号或姓名。
## 1. 请求信息与实测范围
- 测试环境:`https://api-test.zhifly.cn`
- 统一路径前缀:`/api/yf-handset-app/account-deregister`
- 请求头:`token: <当前身份Token>`、`X-APP-VERSION: 1.3.1`、`X-OS-TYPE: iOS`、JSON Accept/Content-Type。
| 方法 | 路径后缀 | 用途 | 真实响应覆盖情况 |
| --- | --- | --- | --- |
| GET | `/eligibility` | 查询注销条件 | 已实测:阻断、两项确认、允许申请、冷静期、撤销后 |
| GET | `/status` | 查询注销记录 | 已实测:无记录、待确认草稿、冷静期、已撤销 |
| POST | `/waivers/wallet` | 确认放弃现金 | 已实测:零余额确认成功 |
| POST | `/waivers/points` | 确认放弃积分 | 已实测:零积分确认成功 |
| POST | `/send-sms` | 发送注销验证码 | 真机发送并收到短信;未保存原始响应体 |
| POST | `/apply` | 提交注销申请 | 真机提交后 GET 确认冷静期;未保存 POST 原始响应体 |
| POST | `/cancel` | 撤销注销申请 | 已实测:撤销成功,并 GET 复核 |
已按用户分次授权执行两项零资产确认,以及一次“短信 → 提交 → 查询 → 立即撤销”。申请时间为 14:23:40,14:24:04 撤销成功,14:24:05 查询确认已撤销;未保留待注销申请,未执行最终注销。正文不记录验证码。
**此前申请状态(15:56复核):15:27:24创建的申请已于15:55:36因重新登录自动撤销(9 / CANCELLED_BY_LOGIN)。没有保留这次待注销申请,旧截止时间不再代表有效冷静期。**
17:01补充实测:当前真机身份查询为未提交草稿0,两项资产已确认,尚未进入冷静期。按用户授权分别尝试`accepted: false`,两条接口均拒绝,确认标记没有重置,见4.4节。
## 2. 公共响应与业务码
| 字段 | 已观测 JSON 类型 | 含义 |
| --- | --- | --- |
| `code` | number(整数) | 业务码,成功为 `100000` |
| `msg` | string | 响应说明,成功可为 `"success"` 或 `"注销申请已撤销"`,不能固定匹配文案 |
| `data` | object;未登录时为 array | 具体业务数据 |
| `time` | string | 成功样例中的服务端时间,格式 `yyyy-MM-dd HH:mm:ss`;时区待确认 |
**HTTP 200 不等于业务成功,必须检查 `code`。**
| 业务码 | 含义 | 验证情况 |
| --- | --- | --- |
| `100000` | 请求成功 | 两个 GET、两个资产确认及撤销响应均观察到 |
| `100090` | 未登录 | 不带 Token 查询两个 GET 时实测 |
| `100099` | 本次表示未明确同意放弃资产 | 两项确认接口传`accepted: false`时实测,见4.4 |
| `150015` | 冷静期身份访问普通业务接口受限 | 15:29使用申请中的身份 GET `/api/yf-handset-app/userinfo` 实测,见5.7 |
两个 GET 的未登录响应均为 HTTP 200,正文如下,没有 `time` 字段:
```json
{"code":100090,"msg":"未登录","data":[]}
```
## 3. 注销条件:GET `/eligibility`
### 3.1 存在阻断时的真实响应
HTTP 200,以下为服务端 `2026-08-28 11:39:51` 的响应:
```json
{
"code": 100000,
"msg": "success",
"data": {
"can_apply": false,
"store_user_id": 0,
"finance_identity_id": 0,
"wallet_balance_fen": 0,
"wallet_balance": "0.00",
"points_balance": 0,
"wallet_waived": false,
"points_waived": false,
"unfulfilled_count": 37,
"fulfillment_in_progress_count": 0,
"risk_end_at": "2026-08-27 15:47:32",
"eligible_at": "2026-09-03 15:47:32",
"risk_window_hours": 168,
"blockers": [
{
"code": "ORDER_UNFULFILLED",
"message": "账户仍有未履约订单或带单",
"action": "complete_orders"
},
{
"code": "RISK_WINDOW_NOT_EXPIRED",
"message": "最后一笔业务的风险结束时间尚未超过7天",
"action": "wait_risk_window"
},
{
"code": "WALLET_WAIVER_MISSING",
"message": "请先确认放弃现金余额",
"action": "confirm_wallet_waiver"
},
{
"code": "POINTS_WAIVER_MISSING",
"message": "请先确认放弃积分",
"action": "confirm_points_waiver"
}
],
"deregister": null
},
"time": "2026-08-28 11:39:51"
}
```
### 3.2 `data` 字段说明
| 字段 | 已观测 JSON 类型 | 数据信息 |
| --- | --- | --- |
| `can_apply` | boolean | 服务端是否允许提交申请 |
| `store_user_id` | number(整数) | 门店用户身份 ID,不是门店实体 ID |
| `finance_identity_id` | number(整数) | 财务身份 ID |
| `wallet_balance_fen` | number(整数) | 现金余额,单位分,适合金额计算 |
| `wallet_balance` | string | 现金金额展示值,如 `"0.00"`,不是 JSON 数字 |
| `points_balance` | number(整数) | 积分余额 |
| `wallet_waived` | boolean | 当前现金余额快照是否已确认放弃 |
| `points_waived` | boolean | 当前积分余额快照是否已确认放弃 |
| `unfulfilled_count` | number(整数) | 未履约记录数量;本次分别观察到 37 和 0 |
| `fulfillment_in_progress_count` | number(整数) | 处理中记录数量,本次为 0;具体统计范围待后端确认 |
| `risk_end_at` | string / null | 最后业务风险结束时间 |
| `eligible_at` | string / null | 业务风险等待截止时间,**不是注销冷静期截止** |
| `risk_window_hours` | number(整数) | 业务风险等待时长,本次为 168 小时 |
| `blockers` | array<object> | 全部阻断项;无阻断时为 `[]` |
| `deregister` | object / null | 注销记录;草稿结构见第 5 节 |
时间字符串本次采用 `yyyy-MM-dd HH:mm:ss`。另一个测试身份的两个风险时间均为 null,不能把 null 转成当前时间或自行追加 168 小时等待。
### 3.3 `blockers[]` 字段与取值
每项包含三个字符串:`code` 为业务标识,`message` 为展示提示,`action` 为建议动作。应展示全部阻断项。
| 已实测 `code` | 含义 | 已实测 `action` |
| --- | --- | --- |
| `ORDER_UNFULFILLED` | 存在未履约订单或带单 | `complete_orders` |
| `RISK_WINDOW_NOT_EXPIRED` | 风险等待期未结束 | `wait_risk_window` |
| `WALLET_WAIVER_MISSING` | 未确认放弃现金 | `confirm_wallet_waiver` |
| `POINTS_WAIVER_MISSING` | 未确认放弃积分 | `confirm_points_waiver` |
旧文档另列出以下代码,但未实测其完整对象和 `action`:
| 仅文档列出的代码 | 文档含义 |
| --- | --- |
| `SHARED_FINANCE_IDENTITY` | 与其他启用身份共用财务账本,需人工处理 |
| `NEGATIVE_ASSET` | 负余额或负积分 |
| `FINANCE_IN_FLIGHT` | 财务流程尚未收口 |
| `FULFILLMENT_IN_PROGRESS` | 退款、分账或交付任务处理中 |
| `WAIVER_STALE` | 确认后余额变化,需重新确认 |
### 3.4 另一个身份的连续数据变化
以下身份现金、积分均为 0,未履约及处理中数量为 0,两个风险时间均为 null。它与 3.1 中有 37 项未履约记录的身份不同。
| 阶段 | `wallet_waived` | `points_waived` | `can_apply` | 阻断 | `deregister` |
| --- | --- | --- | --- | --- | --- |
| 确认前 | false | false | false | 缺两项确认 | null |
| 现金确认后 | true | false | false | 仅缺积分确认 | `status: 0` 草稿 |
| 积分确认后 | true | true | true | `[]` | 仍为 `status: 0` 草稿 |
| 申请后的冷静期 | false | false | false | 缺两项确认 | `status: 1` 冷静期中 |
| 主动撤销后 | false | false | false | 缺两项确认 | `status: 9` 已撤销 |
**第一次资产确认就会产生非空草稿。两项确认后可以申请,但尚未提交申请,也未进入冷静期。**
冷静期中两项确认已变回 false,不应按阻断建议再次确认资产。撤销后重新申请需重新核验条件和确认资产;尚未真实测试再次申请。
## 4. 两项资产确认接口
现金和积分分别请求,均使用以下 JSON 请求体:
```json
{"accepted":true}
```
### 4.1 POST `/waivers/wallet`
HTTP 200,现金确认成功的真实响应:
```json
{
"code": 100000,
"msg": "success",
"data": {
"asset_type": "wallet",
"wallet_balance_fen": 0,
"wallet_balance": "0.00",
"points_balance": 0,
"wallet_waived_at": "2026-08-28 13:45:57",
"points_waived_at": null
},
"time": "2026-08-28 13:45:57"
}
```
### 4.2 POST `/waivers/points`
HTTP 200,积分确认成功的真实响应:
```json
{
"code": 100000,
"msg": "success",
"data": {
"asset_type": "points",
"wallet_balance_fen": 0,
"wallet_balance": "0.00",
"points_balance": 0,
"wallet_waived_at": "2026-08-28 13:45:57",
"points_waived_at": "2026-08-28 13:46:42"
},
"time": "2026-08-28 13:46:42"
}
```
### 4.3 确认响应字段
| 字段 | 已观测 JSON 类型 | 含义 |
| --- | --- | --- |
| `asset_type` | string | 本次确认的资产:`wallet` 或 `points` |
| `wallet_balance_fen` | number(整数) | 当前现金余额,分 |
| `wallet_balance` | string | 当前现金金额展示值 |
| `points_balance` | number(整数) | 当前积分余额 |
| `wallet_waived_at` | string(本次均非空) | 现金确认时间;其他场景是否可为 null 尚未实测 |
| `points_waived_at` | string / null | 积分确认时间;现金确认后仍为 null,积分确认后为时间字符串 |
零余额、零积分也需分别确认。确认成功后重新查询条件和状态,以最新标记为准。旧文档规定确认绑定余额快照;确认本身不等于提交注销或立即清零资产。
### 4.4 `accepted: false` 不支持撤回确认(真实测试)
2026-08-28 17:01,使用iPhone 11当前已登录门店身份,在测试环境分别发送一次:
```json
{"accepted":false}
```
前置状态:`status: 0`未提交草稿,`wallet_waived: true`、`points_waived: true`,现金与积分均为0;`apply_time`、`cooling_until`、`completed_at`均为null。
| 接口 | 服务端响应时间 | HTTP状态 | 业务码 | 结果 |
| --- | --- | --- | --- | --- |
| POST `/waivers/wallet` | 2026-08-28 17:01:14 | 200 | 100099 | 拒绝false,确认标记未变化 |
| POST `/waivers/points` | 2026-08-28 17:01:32 | 200 | 100099 | 拒绝false,确认标记未变化 |
现金接口完整响应如下;积分接口除`time`为`2026-08-28 17:01:32`外,其余字段相同:
```json
{
"code": 100099,
"msg": "请明确确认自愿放弃对应资产",
"data": [],
"time": "2026-08-28 17:01:14"
}
```
每次POST后均再次查询`/status`和`/eligibility`:两项确认仍为true,草稿仍为0,金额未变化,`can_apply`仍为true。**当前接口不能通过传false把已确认状态重置为未确认。** 本轮没有发送true、短信、申请或撤销请求,也没有重新登录。`/cancel`能否取消未提交草稿仍未验证,不能由本次结果推断。
脱敏证据:`/private/tmp/suixinkan-waiver-false-20260828/device-wallet-response.json`、`device-points-response.json`及同目录各自的`before/after-status`、`before/after-eligibility`文件。Token未输出或保存;读取真机会话时的临时副本已删除。
## 5. 注销状态:GET `/status`
### 5.1 无记录
HTTP 200,真实响应:
```json
{
"code": 100000,
"msg": "success",
"data": {
"deregister": null
},
"time": "2026-08-28 11:39:51"
}
```
### 5.2 已确认资产、尚未提交的草稿
HTTP 200,真实响应:
```json
{
"code": 100000,
"msg": "success",
"data": {
"deregister": {
"id": 0,
"status": 0,
"status_label": "待确认",
"reason": "",
"apply_time": null,
"cooling_until": null,
"remaining_seconds": 0,
"cancel_time": null,
"blocked_code": "",
"blocked_reason": "",
"completed_at": null
}
},
"time": "2026-08-28 13:46:42"
}
```
### 5.3 `data.deregister` 字段
| 字段 | 本次实际类型/值 | 含义与待确认事项 |
| --- | --- | --- |
| `id` | number(正整数,示例脱敏为 0) | 注销记录 ID,不是门店用户 ID |
| `status` | number(整数),`0` / `1` / `9` | 分别为待确认、冷静期中、已撤销;其他值未知 |
| `status_label` | string | 服务端状态文案,如 `"冷静期中"`、`"已撤销"` |
| `reason` | string | 草稿为空;提交后保留用户输入的原因 |
| `apply_time` | string / null | 申请时间,本次 `2026-08-28 14:23:40` |
| `cooling_until` | string / null | 冷静期截止,本次 `2026-09-04 14:23:40`;撤销后仍保留 |
| `remaining_seconds` | number(整数) | 查询时剩余秒数;草稿、已撤销都为 0,不能据此认定已完成 |
| `cancel_time` | string / null | 撤销时间,本次 `2026-08-28 14:24:04` |
| `blocked_code` | string | 草稿/冷静期为空;主动撤销为 `CANCELLED_BY_USER` |
| `blocked_reason` | string | 草稿/冷静期为空;主动撤销为 `用户主动撤销注销` |
| `completed_at` | null | 完成时间;非空类型和格式未实测 |
同一草稿结构也出现在 `eligibility` 的 `data.deregister` 内。两个 GET 的 `cooling_until`、`remaining_seconds` 位于该记录对象中;不能据此推断 `/apply` 响应的嵌套结构。
### 5.4 冷静期的真实响应
GET `/status`,HTTP 200。该结果确认申请已受理,但不是 `/apply` 的原始响应体。
```json
{
"code": 100000,
"msg": "success",
"data": {
"deregister": {
"id": 0,
"status": 1,
"status_label": "冷静期中",
"reason": "API test; cancel immediately",
"apply_time": "2026-08-28 14:23:40",
"cooling_until": "2026-09-04 14:23:40",
"remaining_seconds": 604776,
"cancel_time": null,
"blocked_code": "",
"blocked_reason": "",
"completed_at": null
}
},
"time": "2026-08-28 14:24:03"
}
```
### 5.5 主动撤销的真实响应
POST `/cancel`,HTTP 200,无业务请求字段。撤销后再次 GET `/status` 返回相同记录,`msg: "success"`、`time: "2026-08-28 14:24:05"`。
```json
{
"code": 100000,
"msg": "注销申请已撤销",
"data": {
"deregister": {
"id": 0,
"status": 9,
"status_label": "已撤销",
"reason": "API test; cancel immediately",
"apply_time": "2026-08-28 14:23:40",
"cooling_until": "2026-09-04 14:23:40",
"remaining_seconds": 0,
"cancel_time": "2026-08-28 14:24:04",
"blocked_code": "CANCELLED_BY_USER",
"blocked_reason": "用户主动撤销注销",
"completed_at": null
}
},
"time": "2026-08-28 14:24:04"
}
```
`status: 9` 表示已撤销;保留旧 `apply_time`、`cooling_until` 不代表仍在冷静期。这里的 `CANCELLED_BY_USER` 是撤销原因,不是终审阻断状态。两个 GET 均确认两项资产确认标记变为 false;冷静期时已经是 false,不能断言由撤销动作单独导致。
### 5.6 新申请保留在冷静期的真实响应
用户最终确认后,于模拟器点击一次提交;以下为后续 GET `/status` 的真实响应(HTTP 200),不是 POST `/apply` 的响应体:
```json
{
"code": 100000,
"msg": "success",
"data": {
"deregister": {
"id": 0,
"status": 1,
"status_label": "冷静期中",
"reason": "Test account deregistration",
"apply_time": "2026-08-28 15:27:24",
"cooling_until": "2026-09-04 15:27:24",
"remaining_seconds": 604736,
"cancel_time": null,
"blocked_code": "",
"blocked_reason": "",
"completed_at": null
}
},
"time": "2026-08-28 15:28:27"
}
```
同时 GET `/eligibility` 返回现金0、积分0、两项确认 false,以及缺少资产确认的两个阻断;其 `deregister` 仍为上述冷静期记录。不能因此重新确认资产或重复申请。
### 5.7 冷静期普通业务限制的真实响应
15:29以同一身份的现有 Token 只读请求 GET `/api/yf-handset-app/userinfo`,HTTP 200:
```json
{
"code": 150015,
"msg": "账号处于注销冷静期,请先撤销注销后再继续使用",
"data": {
"status": "cooling",
"cooling_until": "2026-09-04 15:27:24",
"remaining_seconds": 604681
}
}
```
该响应没有 `time`,且 `data.status` 是字符串 `"cooling"`;与 `/status` 中 `data.deregister.status` 的数字1不是同一层级或类型,不能共用数字状态 DTO。
请求前后 GET `/status` 均成功且保持同一冷静期申请,`cancel_time`、`completed_at` 均为 null。这证明本次普通信息查询受限,但当前 Token 仍可查询注销状态;不代表正式完成后的鉴权规则已验证,也不能外推为全部普通业务接口已逐一验证。
上述150015和随后status响应已整理为测试夹具(仅记录ID替换为301),并在iPhone 11重放验证业务API、限制通知、状态查询和冷静期页面之间的衔接。此项为Mock集成验证,不是再次请求真实账号或重新申请。
### 5.8 重新登录自动撤销的真实响应
用户重新进入后,15:56只读GET确认之前的申请已撤销,HTTP 200:
```json
{
"code": 100000,
"msg": "success",
"data": {
"deregister": {
"id": 0,
"status": 9,
"status_label": "已撤销",
"reason": "Test account deregistration",
"apply_time": "2026-08-28 15:27:24",
"cooling_until": "2026-09-04 15:27:24",
"remaining_seconds": 0,
"cancel_time": "2026-08-28 15:55:36",
"blocked_code": "CANCELLED_BY_LOGIN",
"blocked_reason": "用户重新登录,自动撤销注销",
"completed_at": null
}
},
"time": "2026-08-28 15:56:34"
}
```
CANCELLED_BY_LOGIN与主动撤销的CANCELLED_BY_USER不同,但同为已撤销9。本次未捕获触发撤销的登录请求,不能确定是哪个登录或身份选择接口触发。并未完成正式注销。
## 6. 尚缺的响应与验证
| 接口 | 旧文档已知信息 | 尚缺信息 |
| --- | --- | --- |
| POST `/send-sms` | 已通过真机发送并收到短信 | 原始成功/失败响应、频率限制及业务码 |
| POST `/apply` | 请求字段 `sms_code`、`reason`;提交后查到冷静期1 | POST 完整响应层级、验证码错误及重复申请响应 |
| POST `/cancel` | 成功体及撤销后9状态已实测 | 失败、重复撤销和终态撤销响应 |
尚未获得阻断、正式完成的 `/status` 响应,不能猜测数字枚举。时间字符串的时区仍待确认,`completed_at` 非空类型和格式仍未实测。
已获得登录自动撤销后的真实状态9和CANCELLED_BY_LOGIN原因,但具体触发接口仍未捕获。到期复核、正式完成后的鉴权响应及全部身份注销后的登录响应仍未验证。此前两次申请现均已撤销。
## 7. 接入注意
1. 按当前门店用户身份隔离状态,不能按手机号共用状态。
2. 展示全部阻断项,不能只使用 `can_apply` 或 HTTP 状态码判断流程。
3. 完整的未提交草稿不应阻止第二项资产确认或手机号验证,非空记录不等于已提交。
4. `eligible_at` 是提交前的业务风险等待截止;`cooling_until` 是提交后的冷静期截止,两者不能混用。
5. `remaining_seconds: 0`、本机时间到期、`can_apply: true` 都不能单独作为注销完成依据。
6. 未知状态、字段缺失或请求失败不能默认解释为“没有申请”。
7. 已撤销9仍含 `cooling_until`,且 `blocked_code` 非空;不能误显示为仍在冷静期或终审阻断。
历史探测过程见 [接口实测记录](门店身份注销接口实测.md),客户端进度见 [接入说明](门店身份注销接入说明.md)。