更新一下
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
# 需求15/16/18/19/20/21 技术方案
|
||||
|
||||
> 状态:原需求来源稿;其中本地审批流、审批页面和操作密码方案已废弃,最终以企微审批方案和标准评审稿为准。
|
||||
> 状态:原需求来源稿;其中本地审批流、审批页面、操作密码以及基于原退款单 `resubmit` 的方案均已废弃,最终以企微审批方案和标准评审稿为准。
|
||||
> 评审建议:需求 15/16、需求 18/20/21、需求 19 分三组评审,不在一次会议中混合确认。
|
||||
|
||||
---
|
||||
@@ -166,15 +166,15 @@ sequenceDiagram
|
||||
|
||||
评审结论:支付方式按**整批统一**设计:
|
||||
|
||||
- 页面选择 `offline` 或 `agent_wallet`,Excel 不再重复填写支付方式。
|
||||
- 页面选择 `offline` 或 `agent_wallet`,CSV 不再重复填写支付方式。
|
||||
- 混合支付拆成两个批次,避免一份凭证对应多种支付语义。
|
||||
- Excel 不包含支付方式列,后端拒绝同一批次混合支付。
|
||||
- CSV 不包含支付方式列,后端拒绝同一批次混合支付。
|
||||
|
||||
### 业务流程
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
Start[员工选择代理和支付方式] --> Upload[上传 Excel]
|
||||
Start[员工选择整批支付方式] --> Upload[上传可含不同代理资产的 CSV]
|
||||
Upload --> Parse[解析并持久化逐行明细]
|
||||
Parse --> Validate[校验资产、套餐、归属和重复行]
|
||||
Validate --> Item{处理下一条有效明细}
|
||||
@@ -193,6 +193,10 @@ flowchart TD
|
||||
|
||||
任务允许部分成功。每一行是独立、可重试、可审计的业务单元,不能只保存一段失败 JSON。
|
||||
|
||||
同一 CSV 内按“资产类型 + 标准化资产标识 + 套餐编码”判断重复:首次出现的行正常处理,后续重复行失败并记录首次出现的行号;同一资产订购不同套餐不算重复。本期不把重复行解释为购买多份,未来如有多份订购需求,应增加明确数量字段。
|
||||
|
||||
本页面和全部批量订购 API 只允许超级管理员,或具备独立“批量订购套餐”权限的平台账号访问;代理和企业账号一律禁止。平台权限不能替代逐行的资产归属、套餐授权、成本价和钱包业务校验。
|
||||
|
||||
### 数据库变更
|
||||
|
||||
```sql
|
||||
@@ -205,7 +209,6 @@ CREATE TABLE tb_bulk_purchase_task (
|
||||
updater BIGINT NOT NULL DEFAULT 0,
|
||||
task_no VARCHAR(30) NOT NULL,
|
||||
source_file_key VARCHAR(500) NOT NULL,
|
||||
shop_id BIGINT NOT NULL,
|
||||
operator_id BIGINT NOT NULL,
|
||||
payment_method VARCHAR(20) NOT NULL,
|
||||
voucher_keys JSONB NOT NULL DEFAULT '[]',
|
||||
@@ -232,6 +235,8 @@ CREATE TABLE tb_bulk_purchase_item (
|
||||
asset_type VARCHAR(20) NOT NULL,
|
||||
asset_identifier VARCHAR(100) NOT NULL,
|
||||
package_code VARCHAR(50) NOT NULL,
|
||||
resolved_shop_id BIGINT,
|
||||
resolved_shop_name VARCHAR(200) NOT NULL DEFAULT '',
|
||||
package_name_snapshot VARCHAR(200) NOT NULL DEFAULT '',
|
||||
package_id BIGINT,
|
||||
amount BIGINT NOT NULL DEFAULT 0,
|
||||
@@ -260,10 +265,10 @@ CREATE INDEX idx_bulk_purchase_item_task_status
|
||||
|
||||
### 处理与幂等
|
||||
|
||||
1. API 校验文件、代理、支付方式和凭证,将 Excel 保存到对象存储并创建任务,返回 `task_id`。
|
||||
2. Worker 根据 `source_file_key` 下载并解析 Excel,将每一行先写入明细表,再开始业务处理。
|
||||
3. 同一任务按行顺序处理,避免对同一代理钱包制造不必要的乐观锁冲突。
|
||||
4. 每行使用独立事务。代理钱包支付时锁定钱包记录,校验 `balance - frozen_balance + credit_limit` 后,在同一事务扣款、创建订单、资金流水并更新明细。
|
||||
1. 前端通过对象存储预签名地址直传 CSV 和线下凭证,再由 API 校验 `file_key`、支付方式和 `voucher_keys` 并创建任务,返回 `task_id`;业务接口不接收 `shop_id` 或文件字节。
|
||||
2. Worker 根据 `source_file_key` 下载并解析 UTF-8 CSV(允许 BOM),将每一行先写入明细表,再开始业务处理;不接受 Excel。
|
||||
3. 同一任务按行顺序处理;每行按资产当前归属解析结算代理,同一任务可以依次处理不同代理的钱包。
|
||||
4. 每行使用独立事务。代理钱包支付时锁定该行结算代理的主钱包,按有效信用额度校验总可用金额后,在同一事务扣款、创建订单、资金流水并更新明细;无归属、归属异常、无主钱包或余额不足只失败该行。
|
||||
5. `idempotency_key` 使用 `bulk_purchase:{task_id}:{row_no}`。Worker 重试时,已存在成功订单的明细直接跳过。
|
||||
6. 单行失败不回滚已成功行;失败原因写结构化错误码和用户可见中文原因。
|
||||
7. 任务汇总从明细表计算,不信任内存计数。
|
||||
@@ -274,7 +279,7 @@ Asynq 载荷只传任务 ID,不传文件字节或临时路径。源文件保
|
||||
|
||||
### API 设计
|
||||
|
||||
**前端静态 Excel 模板**:
|
||||
**前端静态 CSV 模板**:
|
||||
|
||||
模板由前端项目随版本发布,后端不提供下载接口。按已确认的“整批统一支付方式”,模板字段为:
|
||||
|
||||
@@ -288,15 +293,17 @@ Asynq 载荷只传任务 ID,不传文件字节或临时路径。源文件保
|
||||
|
||||
```text
|
||||
POST /api/admin/bulk-purchases
|
||||
Content-Type: multipart/form-data
|
||||
Content-Type: application/json
|
||||
|
||||
shop_id: 123
|
||||
payment_method: offline | agent_wallet
|
||||
voucher_keys: ["key1","key2"]
|
||||
file: <Excel文件>
|
||||
{
|
||||
"request_id": "01J...",
|
||||
"payment_method": "offline",
|
||||
"file_key": "bulk-purchase/2026/07/xxx.csv",
|
||||
"voucher_keys": ["attachment/2026/07/key1"]
|
||||
}
|
||||
```
|
||||
|
||||
`offline` 时 `voucher_keys` 必填,`agent_wallet` 时忽略该字段。
|
||||
CSV 先调用 `POST /api/admin/storage/upload-url` 并使用独立 `purpose=bulk_purchase` 获取预签名地址后直传;线下凭证使用附件用途直传。`offline` 时 `voucher_keys` 必填,`agent_wallet` 时必须为空。创建任务前校验对象存在、归属当前上传主体、CSV 类型和 10MB 大小限制。
|
||||
|
||||
**查询任务状态**:
|
||||
|
||||
@@ -313,7 +320,7 @@ GET /api/admin/bulk-purchases/{task_id}/items?status=4&page=1&page_size=50
|
||||
### 前端技术方案
|
||||
|
||||
- 页面分为“参数确认 → 文件上传 → 处理中 → 结果”四个稳定步骤,刷新页面后可根据任务 ID 恢复进度。
|
||||
- 提交前展示代理、支付方式、凭证数量和文件名的二次确认;钱包支付额外展示当前可用余额,但最终以 Worker 扣款时校验为准。
|
||||
- 提交前展示支付方式、凭证数量和文件名的二次确认;不展示整批目标代理或单一钱包余额。任务结果按行展示后端解析的结算代理及金额。
|
||||
- 任务处理中展示总数、已处理数、成功数和失败数,轮询规则复用统一异步任务方案。
|
||||
- 结果页默认显示失败明细,可切换全部/成功/失败,并可按资产标识搜索。
|
||||
- 部分成功使用明确状态,不弹“全部成功”提示;再次上传失败行会创建新任务,不修改旧任务历史。
|
||||
@@ -413,7 +420,9 @@ POST /api/admin/refunds/{id}/manual-complete
|
||||
|
||||
请求包含 `request_id`、可选 `remark` 和最多 5 个完成凭证。后端仅允许具备财务确认权限的账号对 `status=2 AND processing_status IN (0,3)` 的非代理钱包退款操作;实际金额沿用审批金额,不允许在确认时再次改价。确认记录操作人、时间、备注和凭证后将处理状态置为已完成。
|
||||
|
||||
### 退回后重新提交
|
||||
### 退回后重新提交(已废弃,禁止实施)
|
||||
|
||||
> 本节是旧本地审批方案的历史记录。七月最终方案下线该接口;企微拒绝会终结原退款单,后续仍需退款时重新创建新退款单。
|
||||
|
||||
```
|
||||
POST /api/admin/refunds/{id}/resubmit
|
||||
|
||||
@@ -14,10 +14,10 @@
|
||||
6. 回调是主通道,每 2 分钟查询审批详情作为待审批单兜底。
|
||||
7. 回调与轮询必须进入同一个状态同步用例,业务终态处理只能执行一次。
|
||||
8. 企微模板 ID 和控件 ID 都可能因管理员编辑模板而变化,必须使用稳定业务场景码、不可变模板版本和控件映射快照。
|
||||
9. 本次触碰的退款、线下充值审批逻辑迁移到 Domain/Application,旧业务单级通过、驳回、退回和确认入账接口下线,不保留两套审批入口。
|
||||
9. 本次触碰的退款、线下充值审批逻辑迁移到 Domain/Application,旧业务单级通过、驳回、退回、重新提交和确认入账接口下线,不保留两套审批入口。
|
||||
10. 系统无法从旧模板 ID 自动发现编辑后生成的新模板 ID;模板变更必须先暂停业务场景,再发布新映射并原子切换,避免继续向旧模板提交。
|
||||
11. 退款仍为财务人工退款,本系统不调用微信、支付宝等支付渠道退款接口;只有代理钱包支付订单自动回溯原扣款代理主钱包,不向个人客户或资产钱包自动回款。
|
||||
12. 系统账号与企微成员使用 Web 登录二维码自助绑定,普通运营不手工查找或录入 `userid`;管理端只查看状态和强制解绑。
|
||||
12. 平台账号与企微成员使用 Web 登录二维码自助绑定,普通运营不手工查找或录入 `userid`;代理账号不绑定企微,统一使用部署配置中的固定企微账号代提交。两类路径都独立保存真实业务提交人。
|
||||
|
||||
## 二、系统边界
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
actor User as 平台员工
|
||||
actor User as 业务提交人
|
||||
participant API as Refund/Recharge Application
|
||||
participant DB as PostgreSQL
|
||||
participant Outbox as Outbox
|
||||
@@ -60,7 +60,7 @@ sequenceDiagram
|
||||
API->>Outbox: 同事务写 SubmissionRequested
|
||||
API-->>User: 返回业务单和提交中状态
|
||||
Outbox->>Worker: 投递提交任务
|
||||
Worker->>WeCom: 上传附件、applyevent
|
||||
Worker->>WeCom: 按账号类型选择企微身份并applyevent
|
||||
WeCom-->>Worker: sp_no
|
||||
Worker->>DB: 保存 sp_no,状态改为 pending
|
||||
|
||||
@@ -87,6 +87,7 @@ wecom:
|
||||
callback_encoding_aes_key: ""
|
||||
callback_path: "/api/callback/wecom/approval"
|
||||
account_binding_redirect_url: "https://后台域名/api/callback/wecom/account-binding"
|
||||
agent_approval_creator_userid: ""
|
||||
approval_poll_interval: 2m
|
||||
template_verify_interval: 10m
|
||||
request_timeout: 10s
|
||||
@@ -101,14 +102,15 @@ JUNHONG_WECOM_AGENT_SECRET
|
||||
JUNHONG_WECOM_CALLBACK_TOKEN
|
||||
JUNHONG_WECOM_CALLBACK_ENCODING_AES_KEY
|
||||
JUNHONG_WECOM_ACCOUNT_BINDING_REDIRECT_URL
|
||||
JUNHONG_WECOM_AGENT_APPROVAL_CREATOR_USERID
|
||||
```
|
||||
|
||||
后台只能查询“是否已配置、最近连通时间、最近错误”,不能返回 Secret、Token 或 EncodingAESKey。
|
||||
后台只能查询“是否已配置、最近连通时间、最近错误”以及代理固定代提交成员是否就绪和成员显示名,不能返回 Secret、Token、EncodingAESKey 或原始固定 `userid`。
|
||||
|
||||
企微自建应用还必须配置:
|
||||
|
||||
- Web 登录回调可信域名与 `account_binding_redirect_url` 域名一致。
|
||||
- 应用可见范围覆盖需要发起退款或线下充值审批的平台员工,否则扫码时企微会提示无权限。
|
||||
- 应用可见范围覆盖需要本人发起审批的平台/超级管理员以及代理固定代提交成员;代理业务账号本身不要求成为企微成员。
|
||||
- `CorpID`、`AgentID`、用于换取身份的 Access Token 必须属于同一个自建应用配置。
|
||||
|
||||
用户提供的 demo 中已经出现完整密钥,正式接入前必须在企业微信后台轮换,并禁止将新值写入代码、Markdown、日志或审计快照。
|
||||
@@ -277,15 +279,15 @@ shop_name, recharge_no, amount, remark, attachment, submitter
|
||||
|
||||
提交审批前,如果 `last_verified_at` 超过验证间隔,Worker 先同步验证一次。验证失败不调用 `applyevent`。
|
||||
|
||||
## 六、内部账号与企微成员绑定
|
||||
## 六、平台账号绑定、代理固定代提交与真实业务提交人
|
||||
|
||||
创建人必须有明确的企微 `userid`,不使用固定手机号冒充所有申请人,也不要求运营人员进入企微后台查找成员 ID。
|
||||
企微发起身份按账号类型解析:平台账号和超级管理员必须有本人企微 `userid`;代理账号发起退款时使用部署配置中的固定企微成员 `agent_approval_creator_userid`。两类路径都把当前登录账号作为真实业务提交人写入业务快照和模板 `submitter` 字段。
|
||||
|
||||
### 6.1 绑定流程
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
actor User as 已登录平台员工
|
||||
actor User as 已登录平台/超级管理员
|
||||
participant FE as 管理后台
|
||||
participant API as WeComIdentity Application
|
||||
participant Redis as Redis绑定会话
|
||||
@@ -351,7 +353,7 @@ value:
|
||||
created_at
|
||||
```
|
||||
|
||||
- 只有已登录且启用的超级管理员、平台用户可以为自己创建绑定会话。
|
||||
- 只有已登录且启用的超级管理员、平台用户可以为自己创建绑定会话;代理账号不提供绑定会话。
|
||||
- `state` 使用密码学安全随机数,回调时通过 Lua 原子读取并删除;后续成功或失败结果写入独立的 `session_id` 会话。
|
||||
- 回调不接受前端传入的 `account_id`,绑定目标只能来自服务端会话。
|
||||
- 同一账号再次创建会话时,旧会话立即失效。
|
||||
@@ -377,7 +379,7 @@ CREATE TABLE tb_account_wecom_mapping (
|
||||
);
|
||||
```
|
||||
|
||||
- 平台员工创建退款或线下充值时必须存在启用绑定,否则拒绝创建并返回可直接拉起绑定窗口的错误状态。
|
||||
- 平台/超级管理员创建退款或线下充值时必须存在启用绑定,否则拒绝创建并返回可直接拉起绑定窗口的错误状态;代理创建退款不检查个人绑定,改用固定代提交身份。
|
||||
- `account_id` 与 `wecom_userid` 都是一对一唯一;企微成员已绑定其他账号时拒绝覆盖,必须先由超级管理员解除旧绑定。
|
||||
- 重新绑定同一账号必须再次扫码,成功后在事务内替换旧身份并记录前后值审计。
|
||||
- 自助解绑不影响历史审批;历史实例继续使用提交时保存的 `creator_wecom_userid` 快照,新审批在重新绑定前禁止创建。
|
||||
@@ -387,6 +389,20 @@ CREATE TABLE tb_account_wecom_mapping (
|
||||
|
||||
普通运营界面不提供手工录入 `userid`。超级管理员仅能查看、强制解绑并要求员工重新扫码,不提供直接改写成员 ID 的入口。
|
||||
|
||||
代理固定代提交身份只通过部署配置维护。其 `userid` 必须属于当前 CorpID、处于启用状态且在自建应用可见范围内;后台只返回是否就绪及成员显示名,不返回或修改原始 `userid`。固定身份未配置或失效时,代理新申请必须在业务单、审批实例和 Outbox 落库前拒绝。固定身份变更只影响新审批,历史实例保留原发起身份快照。
|
||||
|
||||
### 6.4 权限边界
|
||||
|
||||
- 连接状态、场景状态、模板版本、模板读取/发布、场景暂停/恢复仅超级管理员可访问。
|
||||
- 平台账号只能查询、重新绑定和解绑自己的企微身份;绑定列表和强制解绑仅超级管理员可访问。
|
||||
- 审批运行列表、详情和“立即同步”仅超级管理员或具备独立“企微审批运营”权限的平台账号访问。
|
||||
- “企微审批运营”不包含场景/模板维护、账号绑定管理和提交结果未知恢复。
|
||||
- 提交结果未知恢复仅超级管理员或具备独立“企微审批异常恢复”权限的平台账号访问。
|
||||
- 代理不访问企微配置、绑定和审批运行接口,只能按现有店铺层级数据范围查看退款详情中的只读审批摘要。
|
||||
- 所有接口均执行后端鉴权,前端隐藏按钮或路由不构成权限控制。
|
||||
|
||||
审批内容继续按主体最小化投影:代理只读取真实业务提交人、审批状态、状态时间、业务处理结果,以及其业务范围内的退款资料和业务凭证;企微审批人、内部意见和审批人上传附件只允许具备对应业务查看权限的平台账号或超级管理员读取。审批详情、附件解析和导出必须共享同一权限判定,历史导出中的稳定附件引用也要在每次访问时重新校验当前权限。
|
||||
|
||||
## 七、审批实例
|
||||
|
||||
```sql
|
||||
@@ -395,13 +411,13 @@ CREATE TABLE tb_wecom_approval_instance (
|
||||
biz_type VARCHAR(64) NOT NULL,
|
||||
biz_id BIGINT NOT NULL,
|
||||
biz_no VARCHAR(64) NOT NULL DEFAULT '',
|
||||
round_no INT NOT NULL DEFAULT 1,
|
||||
scene_code VARCHAR(64) NOT NULL,
|
||||
template_version_id BIGINT NOT NULL,
|
||||
template_id_snapshot VARCHAR(128) NOT NULL,
|
||||
control_mapping_snapshot JSONB NOT NULL,
|
||||
creator_account_id BIGINT NOT NULL,
|
||||
creator_wecom_userid VARCHAR(128) NOT NULL,
|
||||
creator_identity_source VARCHAR(32) NOT NULL,
|
||||
sp_no VARCHAR(128),
|
||||
status INT NOT NULL DEFAULT 0,
|
||||
submitted_snapshot JSONB NOT NULL,
|
||||
@@ -418,7 +434,7 @@ CREATE TABLE tb_wecom_approval_instance (
|
||||
version BIGINT NOT NULL DEFAULT 0,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
UNIQUE (biz_type, biz_id, round_no)
|
||||
UNIQUE (biz_type, biz_id)
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX uq_wecom_approval_sp_no
|
||||
@@ -440,7 +456,7 @@ CREATE UNIQUE INDEX uq_wecom_approval_sp_no
|
||||
| 7 | 提交失败 | 明确未创建审批 |
|
||||
| 8 | 提交结果未知 | 请求超时,无法确认是否创建 |
|
||||
|
||||
`submitted_snapshot` 保存提交时的业务展示数据和本地附件 Key,不保存临时 `media_id`、签名 URL 或敏感密钥。
|
||||
每条实例表示一张业务单唯一的审批申请,不使用 `round_no`。业务表通过 `approval_instance_id` 明确引用该申请,`(biz_type, biz_id)` 唯一约束保证退款或线下充值业务单不能产生第二条审批申请。`creator_account_id` 是真实业务提交人;`creator_wecom_userid` 是本次实际调用企微的成员快照,`creator_identity_source` 固定为 `self_binding` 或 `agent_proxy`。`submitted_snapshot` 保存提交时的业务展示数据和本地附件 Key,不保存临时 `media_id`、签名 URL 或敏感密钥。
|
||||
|
||||
## 八、DDD 设计
|
||||
|
||||
@@ -459,7 +475,7 @@ internal/
|
||||
│ ├── sync_approval_status.go
|
||||
│ ├── handle_callback.go
|
||||
│ ├── poll_pending.go
|
||||
│ └── resubmit_approval.go
|
||||
│ └── recover_unknown_submission.go
|
||||
├── domain/wecomidentity/
|
||||
│ ├── binding.go 账号与企微成员一对一绑定规则
|
||||
│ └── repository.go
|
||||
@@ -512,7 +528,7 @@ Worker 处理:
|
||||
5. 逐个调用 `media/upload` 获取临时 `media_id`。
|
||||
6. 按控件映射构建 `apply_data`。
|
||||
7. 使用模板审批人配置,固定 `use_template_approver=1`。
|
||||
8. 调用 `applyevent`。
|
||||
8. 按实例身份来源使用平台账号绑定的本人 `userid` 或代理固定代提交 `userid` 调用 `applyevent`;不得在 Worker 执行时重新按账号当前状态选择身份。
|
||||
9. 保存 `sp_no`,状态变为审批中并释放租约。
|
||||
|
||||
附件的本地对象存储 Key 是权威记录;企微 `media_id` 只是提交期间使用的临时值。
|
||||
@@ -524,7 +540,7 @@ Worker 处理:
|
||||
- 收到明确 `errcode != 0`:状态改为提交失败,可修正配置后重新提交。
|
||||
- 建连失败且确认请求未发送:允许自动重试。
|
||||
- 请求已发送但响应超时/连接中断:状态改为提交结果未知,不自动再次调用 `applyevent`。
|
||||
- 提交结果未知进入异常页面,由管理员在企微核对后选择“确认未创建并重新提交”或“绑定已有 sp_no”。
|
||||
- 提交结果未知进入异常页面,由管理员在企微核对后选择“确认未创建并重新发送”或“绑定已有 sp_no”。重新发送只是同一审批申请的新技术尝试,不创建新的业务审批申请;旧尝试继续保留在 Integration Log。
|
||||
|
||||
## 十、回调和轮询
|
||||
|
||||
@@ -594,18 +610,12 @@ AND (last_polled_at IS NULL OR last_polled_at <= NOW() - INTERVAL '2 minutes')
|
||||
|---|---|
|
||||
| 通过 | 记录人工退款完成;代理钱包支付订单回溯原扣款代理主钱包;更新订单状态,随后异步佣金回扣和资产处理 |
|
||||
| 驳回 | 退款状态改为已拒绝,保存审批详情摘要 |
|
||||
| 撤销/删除 | 退款状态改为已撤销;允许申请人修改后创建下一轮审批 |
|
||||
| 撤销/删除 | 非正常业务结果,记录异常、停止自动业务处理并进入人工处置;不开放正常重新提交入口 |
|
||||
| 通过后撤销 | 已退款则不冲正,记录严重异常并通知财务;尚未执行则阻止退款 |
|
||||
|
||||
原 `POST /api/admin/refunds/{id}/approve`、`reject`、`return` 下线。
|
||||
原 `POST /api/admin/refunds/{id}/approve`、`reject`、`return`、`resubmit` 全部下线。
|
||||
|
||||
重新申请使用:
|
||||
|
||||
```http
|
||||
POST /api/admin/refunds/{id}/resubmit
|
||||
```
|
||||
|
||||
它只允许已驳回、已撤销或已删除且业务尚未退款的申请,修改业务资料后创建 `round_no + 1` 的企微审批。
|
||||
一张退款单只产生一条审批申请,正常业务结果只有同意或拒绝,任一结果产生后审批和退款单即同时完结。企微拒绝时保存拒绝原因和审批详情,原退款单进入不可编辑、不可重提的已拒绝终态。业务人员纠正问题后若仍需退款,必须重新调用 `POST /api/admin/refunds`,系统生成新的退款 ID、退款单号、业务快照、提交人快照和企微审批申请;新单不继承旧审批节点、意见、附件、状态或企微发起身份。旧单只作为历史与审计事实保留。创建校验不把已拒绝退款视为活跃退款,但存在其他审批中或处理未完成的退款时仍拒绝新建。撤销、删除和通过后撤销属于外部异常防御,不作为自动放行新退款的依据。
|
||||
|
||||
审批通过后的业务 Worker 处理失败时,审批实例保持“已通过”,`business_process_result=failed`,由任务重试;前端明确区分“审批已通过”和“退款终态处理失败”。只有退款终态事务成功后才写 `business_processed_at`。
|
||||
|
||||
@@ -633,10 +643,10 @@ POST /api/admin/refunds/{id}/resubmit
|
||||
### 12.1 基础配置状态
|
||||
|
||||
```http
|
||||
GET /api/admin/wecom/config/status
|
||||
GET /api/admin/wecom/status
|
||||
```
|
||||
|
||||
返回是否配置、Token 最近获取时间、回调最近成功时间、最近错误,不返回任何密钥。
|
||||
返回是否配置、Token 最近获取时间、回调最近成功时间、最近错误、代理固定代提交成员是否就绪及成员显示名,不返回任何密钥或原始固定 `userid`。
|
||||
|
||||
### 12.2 审批场景状态
|
||||
|
||||
@@ -735,9 +745,10 @@ GET /api/admin/wecom/approvals
|
||||
GET /api/admin/wecom/approvals/{id}
|
||||
POST /api/admin/wecom/approvals/{id}/sync
|
||||
POST /api/admin/wecom/approvals/{id}/bind-sp-no
|
||||
POST /api/admin/wecom/approvals/{id}/confirm-not-created-and-resend
|
||||
```
|
||||
|
||||
`bind-sp-no` 仅用于提交结果未知的人工恢复,必须写高等级审计日志。
|
||||
`bind-sp-no` 请求包含 `sp_no` 和必填恢复原因;后端调用 `getapprovaldetail` 完成企业、模板、发起身份、业务场景、真实业务提交人和业务快照核对后才能绑定。`confirm-not-created-and-resend` 请求包含必填恢复原因,只在操作人已在企微确认未创建审批时重新发送同一审批申请。两者仅用于提交结果未知的人工恢复,均必须二次确认并写高等级审计日志。
|
||||
|
||||
### 12.8 业务详情响应
|
||||
|
||||
@@ -752,7 +763,6 @@ POST /api/admin/wecom/approvals/{id}/bind-sp-no
|
||||
"status": 2,
|
||||
"status_name": "已通过",
|
||||
"template_name": "退款审批",
|
||||
"round_no": 1,
|
||||
"creator_name": "张三",
|
||||
"approvers": [],
|
||||
"submitted_at": "2026-07-15T10:00:00+08:00",
|
||||
@@ -773,16 +783,18 @@ POST /api/admin/wecom/approvals/{id}/bind-sp-no
|
||||
|
||||
### 13.2 业务详情
|
||||
|
||||
退款和充值详情增加只读“企业微信审批”区块:
|
||||
退款和充值详情增加只读“企业微信审批”区块。平台/超级管理员在具备业务查看权限时展示:
|
||||
|
||||
- 审批单号。
|
||||
- 当前状态和状态更新时间。
|
||||
- 模板名称和模板版本。
|
||||
- 申请人。
|
||||
- 真实业务提交人;代理代提交时可只读标识“企微由固定账号代提交”,但不能把固定账号展示为业务申请人。
|
||||
- 审批人、审批结果、意见和时间线。
|
||||
- 业务处理结果。
|
||||
- “立即同步”图标按钮,仅触发 `getapprovaldetail`,不提供审批按钮。
|
||||
|
||||
代理退款详情只展示真实业务提交人、审批状态、状态时间和业务处理结果,以及其原有业务权限允许查看的退款资料和业务凭证;接口不向代理返回审批人、内部审批意见和审批人上传附件。
|
||||
|
||||
通过后撤销且业务已执行时使用红色异常状态条,明确显示“资金动作未自动冲正,需人工处理”。
|
||||
|
||||
### 13.3 企微配置页
|
||||
@@ -793,7 +805,7 @@ Tab:
|
||||
|
||||
1. **连接状态**:只显示配置状态和最近连通结果。
|
||||
2. **审批模板**:按业务场景展示当前版本、模板 ID、验证状态和历史版本。
|
||||
3. **账号绑定**:查看平台员工绑定状态、企微名称和最近验证时间;不允许编辑 userid。
|
||||
3. **账号绑定**:查看平台/超级管理员绑定状态、企微名称和最近验证时间;不允许编辑 userid;代理固定代提交账号只在连接状态中展示就绪状态和成员显示名。
|
||||
4. **异常审批**:提交未知、模板失效、终态业务处理失败、通过后撤销。
|
||||
|
||||
模板发布交互:
|
||||
@@ -814,10 +826,10 @@ Tab:
|
||||
|
||||
账号绑定交互:
|
||||
|
||||
- 当前用户个人中心显示“未绑定/已绑定/已失效”、企微成员名称和最近验证时间。
|
||||
- 平台/超级管理员个人中心显示“未绑定/已绑定/已失效”、企微成员名称和最近验证时间;代理个人中心不展示企微绑定能力。
|
||||
- 点击“绑定企业微信”后打开官方企微二维码窗口,原页面显示 5 分钟倒计时。
|
||||
- 绑定成功后窗口自动关闭,原页面刷新绑定状态;失败时显示企微返回的可操作原因。
|
||||
- 创建退款或线下充值时发现未绑定,页面原地展示“绑定企业微信”按钮,绑定成功后继续填写,不要求用户先去配置页。
|
||||
- 平台/超级管理员创建退款或线下充值时发现未绑定,页面原地展示“绑定企业微信”按钮,绑定成功后继续填写,不要求用户先去配置页;代理创建退款不检查个人绑定。
|
||||
- 管理员的账号绑定列表只提供筛选、查看和强制解绑;不提供 userid 输入框。
|
||||
|
||||
### 13.4 审批运行页
|
||||
@@ -829,10 +841,10 @@ Tab:
|
||||
页面用于运行监控,不提供审批动作:
|
||||
|
||||
- 按业务类型、企微状态、提交状态和业务处理结果筛选。
|
||||
- 查询 `sp_no`、业务单号、申请人和模板版本。
|
||||
- 查询 `sp_no`、业务单号、真实业务提交人、企微发起身份来源和模板版本。
|
||||
- 查看企微审批详情快照和本地业务处理结果。
|
||||
- 对审批中记录执行“立即同步”。
|
||||
- 对提交结果未知记录执行“绑定已有 sp_no”或“确认未创建并重新提交”。
|
||||
- 对提交结果未知记录执行“绑定已有 sp_no”或“确认未创建并重新发送”;后者仍属于同一审批申请的技术恢复。
|
||||
- 对业务处理失败记录查看错误和任务重试状态。
|
||||
|
||||
## 十四、Token、日志和限流
|
||||
@@ -852,11 +864,11 @@ Tab:
|
||||
1. 在企微自建应用配置 Web 登录可信域名和平台员工可见范围。
|
||||
2. 创建企微模板版本、账号绑定和审批实例表。
|
||||
3. 发布企微身份 Adapter、账号绑定回调、审批回调、轮询 Worker 和业务终态用例。
|
||||
4. 要求会发起审批的平台员工完成扫码绑定。
|
||||
4. 要求会发起审批的平台/超级管理员完成扫码绑定,并验证代理固定代提交成员可用。
|
||||
5. 发布并验证退款、线下充值两个模板映射版本。
|
||||
6. 下线本地审批动作路由和前端按钮。
|
||||
7. 存量已通过/已拒绝记录保留原业务审批快照,展示 `approval.source=legacy`。
|
||||
8. 存量待审批退款和线下充值仅在创建人已绑定企微时提交;未绑定记录进入迁移待处理列表。
|
||||
8. 存量待审批记录按创建人类型处理:代理创建的退款使用固定代提交成员;平台/超级管理员创建的退款和线下充值仅在创建人已绑定企微时提交;未绑定或创建人缺失的记录进入迁移待处理列表。
|
||||
9. 提交失败的存量记录进入异常审批页面,不允许继续走旧接口处理。
|
||||
|
||||
## 十六、人工验证
|
||||
@@ -872,7 +884,8 @@ Tab:
|
||||
9. 回调和轮询同时到达时,只有一个处理器执行业务动作。
|
||||
10. 附件始终保留本地对象存储 Key,企微 media_id 失效不影响历史资料下载。
|
||||
11. Secret、Token、EncodingAESKey 和附件内容不出现在 API、日志和审计详情中。
|
||||
12. 已登录平台员工扫码后自动获得企微 userid,不需要人工查询或录入成员 ID。
|
||||
12. 已登录平台/超级管理员扫码后自动获得本人企微 userid,不需要人工查询或录入成员 ID;代理不展示绑定入口并使用固定账号代提交。
|
||||
13. 绑定 `state` 过期、重复回调、非企业成员扫码时均不会产生绑定。
|
||||
14. 同一企微 userid 尝试绑定第二个系统账号时被拒绝,原绑定不被覆盖。
|
||||
15. 自助解绑和管理员强制解绑不影响历史审批快照,但会阻止该账号发起新审批。
|
||||
16. 同一份退款分别由平台账号和代理账号创建时,前者使用本人绑定身份,后者使用固定代提交身份;两份审批单都正确展示真实业务提交人,通知和审计不会把固定账号当成代理本人。
|
||||
|
||||
Reference in New Issue
Block a user