From 841ed1ceb04f5dc08ff95659119771fd22a21fe2 Mon Sep 17 00:00:00 2001 From: break Date: Wed, 22 Jul 2026 11:34:20 +0900 Subject: [PATCH] =?UTF-8?q?=E5=90=8C=E6=AD=A5=E7=9B=B8=E5=85=B3=E5=86=85?= =?UTF-8?q?=E5=AE=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CONTEXT.md | 2 +- docs/7月迭代/7月迭代技术方案-标准评审稿.md | 16 ++- docs/7月迭代/7月迭代禅道研发需求拆分表.md | 2 +- docs/7月迭代/7月迭代禅道研发需求逐条录入稿.md | 18 +-- .../原需求/需求15-16-18-19-20-21-复杂需求.md | 131 +++--------------- .../独立方案/新增需求/05-代理钱包扫码充值.md | 92 ++++++------ 6 files changed, 94 insertions(+), 167 deletions(-) diff --git a/CONTEXT.md b/CONTEXT.md index d9d4969..d5c5b2a 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -68,7 +68,7 @@ **代理充值两阶段入账 / Two-stage Agent Recharge Posting**:在线支付回调事务只固化真实收款事实:支付单变为已支付、充值单变为 `2=已支付`、`processing_status=1`,并可靠写入入账 Outbox;随后由 Worker 在独立事务中更新代理主钱包余额和版本、创建唯一充值流水、把充值单改为 `3=已完成`与 `processing_status=2`,并写资金 Audit Event。Worker 失败不回滚支付事实,改为 `processing_status=3`并可靠重试;支付回调在收款事实成功落库后即可向渠道返回成功,不等待钱包入账。 -**代理充值到账通知 / Agent Recharge Posted Notification**:无论代理在线扫码充值还是平台线下代充值,只要目标代理主钱包实际入账成功,都必须向目标代理发送一条站内“充值到账”通知。线下充值的企微审批结果仍由企微公共能力另行通知真实业务提交人,审批结果与实际到账是两类不同通知;通知投递失败不回滚资金事务,并按充值单与接收人防重。入账持续失败、通过后撤销等内部异常只通知平台财务或运维,不向代理暴露内部错误。 +**代理充值到账通知 / Agent Recharge Posted Notification**:无论代理在线扫码充值还是平台线下代充值,只要目标代理主钱包实际入账成功,都必须向目标代理主账号发送一条站内“充值到账”通知;在线充值的实际提交账号与目标代理主账号不同时,实际提交账号也接收一条。线下充值的真实业务提交人只接收企微公共能力的审批结果通知,除非其本身也是到账通知接收人。审批结果与实际到账是两类不同通知;通知投递失败不回滚资金事务,并按充值单与接收人防重。入账持续失败、通过后撤销等内部异常只通知平台财务或运维,不向代理暴露内部错误。 **代理充值查看范围 / Agent Recharge Visibility**:充值记录不按创建账号隔离。代理账号在具备充值查看权限时,按既有店铺层级数据范围查看本店及有权管理的下级店铺充值,可读取充值金额、支付方式、付款凭证、真实提交人、审批状态和钱包入账结果,但不能读取企微审批人、内部意见、审批人附件或支付配置等内部信息。平台和超级管理员沿用既有数据范围,企业账号不可访问;列表、详情和支付状态接口使用完全相同的数据范围。 diff --git a/docs/7月迭代/7月迭代技术方案-标准评审稿.md b/docs/7月迭代/7月迭代技术方案-标准评审稿.md index 54136d2..6822f81 100644 --- a/docs/7月迭代/7月迭代技术方案-标准评审稿.md +++ b/docs/7月迭代/7月迭代技术方案-标准评审稿.md @@ -1,6 +1,6 @@ # 7月迭代技术方案(标准评审稿) -> 状态:待评审 +> 状态:已评审 > 最后更新:2026-07-20 > 分支:`Iteration/7-11` > 系统:`junhong_cmp_fiber` 及配套后台、代理端、C 端前端 @@ -1350,10 +1350,12 @@ sequenceDiagram - 代理只能为当前店铺主钱包充值,最低 `10000` 分(100 元)。 - 支持微信 Native 和支付宝 `alipay.trade.precreate`,创建本地充值单和 `tb_payment(order_type=agent_recharge)` 后再预下单。 -- 后端统一返回 `qr_content` 和过期时间,前端使用二维码组件渲染,不生成后端图片文件。 +- 每次主动创建都生成新的充值单和支付单;`request_id` 只防同一次 HTTP 提交重试,不按金额或已有待支付单复用。后端原样返回第三方 `qr_content`,前端使用二维码组件渲染,不生成后端图片文件。 +- 不返回 `expires_at`,也不展示本地推算的精确倒计时;支付是否成功、关闭或失效以第三方回调和受控查单结果为准。前端每 3 秒轮询的轻量接口只读取本地状态,不直接触发第三方查单。 - 微信/支付宝回调按支付单类型分发,校验渠道、支付配置、金额、第三方交易号和业务单关联。 -- 支付单、充值单、代理主钱包、钱包版本、唯一钱包流水和 Audit Event 在同一事务推进;重复回调只返回渠道成功,不重复加钱。 -- 支付成功但钱包事务失败时保持已支付并由可靠任务补齐;二维码过期后关闭原充值单,重新生成必须创建新支付单。 +- 支付确认和钱包入账分成两个可靠事务:第一阶段固化支付单已支付、充值单已支付、处理状态和入账 Outbox;第二阶段由 Worker 原子更新代理主钱包、版本、唯一流水、充值完成状态和资金 Audit Event。重复回调或任务不重复加钱。 +- 支付成功但钱包事务失败时保持已支付并由可靠任务补齐;第三方明确关闭或失效时支付单沿用 `2=已失败`、充值单改为已关闭,不新增支付“已关闭”状态。已关闭后收到可核验的迟到成功回调仍按真实收款事实幂等入账。 +- 钱包实际入账成功后向目标代理店铺主账号发送站内到账通知;在线真实提交人不是主账号时也接收,按充值单和接收人防重。 ```text GET /api/admin/agent-recharges/payment-methods @@ -1361,7 +1363,7 @@ POST /api/admin/agent-recharges GET /api/admin/agent-recharges/{id}/payment-status ``` -代理在线充值详情固定 `approval_source=none`,前端每 3 秒轮询轻量支付状态,页面不可见时暂停。 +代理在线充值详情固定 `approval_source=none`。前端每 3 秒轮询本地轻量支付状态,页面不可见时暂停;响应分别返回支付状态和统一 `processing_status`,不提供同义字段 `wallet_posting_status`。 #### 平台员工线下代充值 @@ -1399,11 +1401,13 @@ sequenceDiagram 关键规则: - 企微通过后自动增加代理主钱包余额并写钱包流水,无操作密码。 +- 线下金额只要求大于 0且不超过 100 万元;目标店铺、固定金额和 1~5 个结构化付款凭证必填,备注选填且最多 500 字。企微只能同意或拒绝,不能修改金额。 - Worker 使用 `recharge:{recharge_no}` 幂等键和处理租约。 - 钱包余额、版本、充值单和资金流水必须在同一事务更新。 - 若资金流水已存在但充值单状态未完成,重试只补齐状态,不再次入账。 -- 驳回改为已驳回;撤销/删除改为已关闭,可重新创建充值申请。 +- 驳回改为已驳回并终结原单;不增加已退回状态或原单重提接口,修正后必须重新创建。撤销/删除改为已关闭,可重新创建充值申请。 - 通过后撤销且已经入账时不自动扣回,记录严重异常并通知财务;尚未入账时终止任务。 +- 钱包实际入账成功后向目标代理店铺主账号发送站内到账通知;企微审批结果另行通知真实业务提交人。 旧接口在停机发布后不再注册: diff --git a/docs/7月迭代/7月迭代禅道研发需求拆分表.md b/docs/7月迭代/7月迭代禅道研发需求拆分表.md index 92082f4..d305cbd 100644 --- a/docs/7月迭代/7月迭代禅道研发需求拆分表.md +++ b/docs/7月迭代/7月迭代禅道研发需求拆分表.md @@ -114,7 +114,7 @@ FE/BE 研发需求开发完成 | #37 企微审核流转 | **标题:**企微配置、账号绑定和审批详情。
**页面:**企微配置页、个人扫码绑定、审批运行列表;业务详情只读展示意见和附件。 | **标题:**企业微信审批接入。
**接口:**`GET /api/admin/wecom/status`、`POST /api/admin/wecom/account-binding/sessions`、`GET /api/admin/wecom/approvals`、`POST /api/admin/wecom/approvals/{id}/sync`。
**业务详情:**统一返回 `approval` 对象。 | | #36 批量订购套餐 | **标题:**批量订购上传、支付、进度和失败明细。
**页面:**沿用现有前端入口可见性;选择单个套餐和整批支付方式,CSV及线下凭证先直传对象存储;不选择代理,同一CSV可含不同代理资产,不接受Excel。 | **标题:**批量订购任务。
**接口:**`POST /api/admin/storage/upload-url`、`POST /api/admin/bulk-purchases`、`GET /api/admin/bulk-purchases/{task_id}`、`GET /api/admin/bulk-purchases/{task_id}/items`。
**权限:**后端仅复用现有后台认证,不新增权限码、账号类型拦截或任务创建人隔离。
**入参:**单个`package_id`、`payment_method=wallet|offline`、`file_key`、`voucher_keys`,无`shop_id`。`wallet`按CSV行号逐行扣结算代理主钱包,余额不足只失败当前行并继续。
**文件:**CSV仅一列“资产标识”;创建接口校验套餐和对象/类型/10MB,Worker校验编码、表头、语法、空文件及1000行,文件级错误整批失败且不下单。
**资产:**复用统一资产解析能力识别类型和ID,当前支持ICCID、卡`virtual_no`、MSISDN、设备`virtual_no`、IMEI和SN。
**判重:**按解析后的资产类型和资产ID判重,同一资产不同标识也仅首行处理。
**状态:**统一五态,部分成功由计数表达。
**返回:**任务和逐行结算代理结果。 | | #35 退款审核 | **标题:**退款企微审批和整单处理状态。
**页面:**创建只提交订单、固定申请金额、必填原因、可选备注及1~5个结构化附件,不提交实收金额或套餐使用记录;详情分区展示退款、审批和处理结果;拒绝后重新退款必须新建退款单;代理隐藏审批人、内部意见和审批附件。 | **标题:**退款企微终态与整单终结。
**接口:**`POST /api/admin/refunds`、`GET /api/admin/refunds`、`GET /api/admin/refunds/{id}`;下线原 `approve/reject/return/resubmit` 及任何 `manual-complete`。
**规则:**后端读取实收并校验固定申请金额;无论是否全额,通过后订单、该订单套餐和佣金均整单终结且不可再退差额。只有代理主钱包按原扣款流水自动回溯;其他方式系统外退款,资产钱包不回充。佣金结构化失效并可靠回扣,处理状态独立且可重试。代理按店铺层级权限查看。
**门禁:**可编程Adapter自动化和真实企微代理同意/平台拒绝两条链路均必过。 | -| #34 充值审核流程 | **标题:**代理扫码充值与员工线下充值审批。
**页面:**在线充值展示支付方式、二维码和支付状态;线下充值展示只读企微审批状态。 | **标题:**代理在线充值和员工线下审批。
**接口:**`GET /api/admin/agent-recharges/payment-methods`、`POST /api/admin/agent-recharges`、`GET /api/admin/agent-recharges/{id}/payment-status`、`GET /api/admin/agent-recharges/{id}`。
**返回:**二维码、过期时间、支付/审批/入账状态。 | +| #34 充值审核流程 | **标题:**代理扫码充值与员工线下充值审批。
**页面:**在线充值展示支付方式、二维码、支付状态和钱包入账状态,不展示本地推算倒计时;线下充值展示只读企微审批状态。 | **标题:**代理在线充值和员工线下审批。
**接口:**`GET /api/admin/agent-recharges/payment-methods`、`POST /api/admin/agent-recharges`、`GET /api/admin/agent-recharges/{id}/payment-status`、`GET /api/admin/agent-recharges/{id}`。
**返回:**`qr_content`、支付/审批/统一处理状态,不返回支付 `expires_at`。支付与钱包两阶段可靠推进,到账后发送代理站内通知。 | | #33 套餐临期提醒 | **标题:**临期列表、各端高亮和续费入口。
**页面:**临期页3天内置顶;普通资产列表只高亮;代理首页显示数量;C端显示续费按钮。 | **标题:**预计最终到期临期Query和站内通知。
**接口:**`GET /api/admin/expiring-assets`、资产列表/详情增加临期字段、`GET /api/c/v1/asset/info`、通知接口。
**返回:**最终到期、剩余天数、颜色节点;15/7/3天通知防重。 | ## 五、前端Mock公共样例 diff --git a/docs/7月迭代/7月迭代禅道研发需求逐条录入稿.md b/docs/7月迭代/7月迭代禅道研发需求逐条录入稿.md index 61a8ef5..1252fed 100644 --- a/docs/7月迭代/7月迭代禅道研发需求逐条录入稿.md +++ b/docs/7月迭代/7月迭代禅道研发需求逐条录入稿.md @@ -1480,22 +1480,22 @@ 页面入口:代理资金充值页、后台线下代充值创建页、充值列表和详情。 页面结构: -1. 在线充值:金额输入最低100元、支付方式选择、二维码、过期倒计时和支付状态;不显示审批区域。 +1. 在线充值:金额输入最低100元、支付方式选择、二维码、支付状态和钱包入账状态;不显示审批区域,也不展示本地推算的精确过期倒计时。 2. 线下代充值:选择店铺、金额、备注、附件;提交后显示企微审批和入账处理状态。 3. 充值详情按approval_source显示:none隐藏审批区,wecom显示只读企微详情。 4. 不显示本地确认入账、驳回按钮和操作密码输入框。 接口约定: -- GET /api/admin/agent-recharges/payment-methods 返回 methods:string[]、min_amount:int64。 +- GET /api/admin/agent-recharges/payment-methods 返回真正可用的 methods:string[]、min_amount:int64、max_amount:int64,不暴露支付通道或配置。 - POST /api/admin/agent-recharges,在线入参 amount、payment_method、request_id;线下入参 shop_id、amount、payment_method=offline、remark、attachments、request_id。 -- 在线返回 recharge_id、recharge_no、qr_content、expires_at、payment_status、approval_source=none。 -- GET /api/admin/agent-recharges/{id}/payment-status 返回 payment_status、payment_status_name、wallet_posting_status。 +- 在线返回 recharge_id、recharge_no、qr_content、payment_status、processing_status、approval_source=none,不返回 expires_at。 +- GET /api/admin/agent-recharges/{id}/payment-status 只读取本地状态,返回 payment_status、payment_status_name、processing_status、processing_status_name。 - GET /api/admin/agent-recharges/{id} 返回充值详情、approval和processing_status。 - 线下attachments使用现有对象存储上传结果,结构为 {file_key,file_name,file_size}[]。 -交互规则:在线状态每3秒轮询,页面不可见暂停;二维码过期后重新创建新支付单;线下提交失败保留表单。 +交互规则:在线状态每3秒轮询本地状态,页面不可见暂停;每次用户主动创建支付都使用新的request_id创建全新充值单和支付单,旧单等待回调或后端查单自然收敛;线下提交失败保留表单。 -完成标准:微信、支付宝、二维码过期、重复回调后的最终状态、线下审批通过入账和驳回状态均可展示。 +完成标准:微信、支付宝、第三方关闭/迟到成功、重复回调后的最终状态、支付成功但入账失败补偿、线下审批通过入账和驳回状态均可展示;在线和线下实际到账后均发送代理站内到账通知。 ``` ### 后端研发需求 @@ -1515,11 +1515,11 @@ 接口:GET /api/admin/agent-recharges/payment-methods;POST /api/admin/agent-recharges;GET /api/admin/agent-recharges/{id}/payment-status;GET /api/admin/agent-recharges/{id};下线offline-pay和reject旧接口。 -在线规则:最低10000分;支持微信Native和支付宝预下单;统一返回qr_content;支付回调校验渠道、金额、配置、交易号和业务单;支付单、充值单、主钱包、版本、唯一流水和审计同事务推进;重复回调不重复入账。 +在线规则:代理只充当前店铺且不提交shop_id,最低10000分;支持微信Native和支付宝PreCreate;每次主动创建都是全新充值单和支付单;统一返回qr_content且不返回expires_at。支付回调和受控查单进入同一幂等确认用例并校验金额、配置、交易号和业务单;先固化支付成功与入账Outbox,再由可靠Worker独立事务更新主钱包、版本、唯一流水、充值完成状态和审计。重复回调或任务不重复入账,迟到成功不能吞掉已付资金。 -线下规则:创建后提交企微;通过后自动增加代理主钱包并写流水,无操作密码;recharge:{recharge_no}防重;驳回为已驳回,撤销/删除为已关闭;通过后撤销且已入账不自动扣回。 +线下规则:仅平台/超管创建,金额大于0,目标店铺和1~5个结构化付款凭证必填,金额提交后固定;创建后提交企微,审批只能同意或拒绝。通过后自动增加代理主钱包并写流水,无操作密码;recharge:{recharge_no}防重;驳回终结原单且不支持退回/重提,撤销/删除为已关闭;通过后撤销且已入账不自动扣回。 -完成标准:两条路径状态隔离,支付成功但入账失败可补偿,企微终态重复同步不重复加钱。 +完成标准:两条路径和权限严格隔离,支付/审批/钱包处理状态独立,支付成功但入账失败可补偿,支付查单与企微终态重复同步不重复加钱;钱包到账后向目标代理发送防重站内通知。本地支付网络使用可控Adapter,真实微信/支付宝在测试环境手工验收,真实企微线下充值为实现门禁。 ``` ## UR#33 套餐临期提醒 diff --git a/docs/7月迭代/独立方案/原需求/需求15-16-18-19-20-21-复杂需求.md b/docs/7月迭代/独立方案/原需求/需求15-16-18-19-20-21-复杂需求.md index d07751f..3e919dc 100644 --- a/docs/7月迭代/独立方案/原需求/需求15-16-18-19-20-21-复杂需求.md +++ b/docs/7月迭代/独立方案/原需求/需求15-16-18-19-20-21-复杂需求.md @@ -414,120 +414,33 @@ GET /api/admin/bulk-purchases/{task_id}/items?status=4&page=1&page_size=50 ## 需求21:充值审核流程 -### 充值单现有状态 +> 本节已按 UR#34 最终评审结论重写。完整可执行契约见 [UR#34 PRD](../../../../.scratch/ur34-agent-recharge/PRD.md),如有细节差异以该 PRD 和标准评审稿为准。 -``` -tb_agent_recharge_record:1=待支付 2=已支付 3=已完成 4=已关闭 5=已退款 -``` +### 业务分流 -代码中已经存在 `6=已驳回`,不能改写其含义。员工线下充值走审批流时,在现有状态基础上追加 `7=已退回`: +- 代理只能为当前登录店铺主钱包创建 `wechat` 或 `alipay` 在线充值,请求不接受 `shop_id`;平台、超级管理员和企业账号不能替代理创建在线支付。 +- 平台和超级管理员只能为指定 `shop_id` 创建 `offline` 线下代充值,必须携带固定金额、1~5 个结构化付款凭证和可选备注,并进入真实企业微信审批;代理和企业账号不能创建线下代充值。 +- 在线充值金额为 `10000~100000000` 分,线下代充值为 `1~100000000` 分。 +- 平台和超级管理员的线下提交人使用本人绑定的企微账号;审批人只能同意或拒绝,不能修改金额。拒绝后原充值单以 `6=已驳回`终结,若要修正资料必须新建充值单和企微审批;不新增 `7=已退回`,也不提供原单重提接口。 -```sql --- 现有:1=待支付 2=已支付 3=已完成 4=已关闭 5=已退款 6=已驳回 --- 新增:7=已退回 +### 在线支付与入账 -ALTER TABLE tb_agent_recharge_record - ADD COLUMN approval_instance_id BIGINT, - ADD COLUMN processing_status INT NOT NULL DEFAULT 0, - ADD COLUMN processing_error TEXT NOT NULL DEFAULT '', - ADD COLUMN processing_started_at TIMESTAMPTZ, - ADD COLUMN processing_completed_at TIMESTAMPTZ, - ADD COLUMN return_reason VARCHAR(500) NOT NULL DEFAULT ''; +- 继续使用现有支付配置,只按 `wechat` 或 `alipay` 选择当前可用配置,不建设多通道自动路由、优先级或故障转移。 +- 微信使用 Native,支付宝使用 `alipay.trade.precreate`。后端把第三方返回的字符串或 HTTPS URL 原样映射为 `qr_content`,前端渲染二维码;后端不生成二维码图片或新增二维码生成接口。 +- 创建接口不返回 `expires_at`,前端不展示本地推算的精确倒计时。本地时间不能判定第三方支付单是否失效,支付成功或关闭以回调和后端受控查单为准。 +- `request_id` 只防止同一次提交重试。代理每次主动创建或再次拉起支付都使用新 `request_id` 并产生新的充值单和支付单,旧单等待第三方自然收敛,不复用、不主动取消。 +- 支付回调或查单先在事务中固化真实收款事实:支付单已支付、充值单 `2=已支付`、`processing_status=1`并可靠写入钱包入账 Outbox;随后 Worker 在独立事务中更新钱包和版本、创建唯一流水、将充值单改为 `3=已完成`和 `processing_status=2`并写资金审计。入账失败使用 `processing_status=3`可靠重试,不回滚支付事实。 +- 支付状态继续使用 `0=待支付, 1=已支付, 2=已失败, 3=已退款`,不新增“已关闭”;第三方明确关闭或失效时支付记为已失败、充值记为 `4=已关闭`。有效的迟到成功回调仍必须恢复支付事实并幂等入账。 -CREATE INDEX idx_agent_recharge_approval_instance - ON tb_agent_recharge_record(approval_instance_id) - WHERE approval_instance_id IS NOT NULL; -``` +### 线下审批终态 -充值业务的状态语义: -- `1=待支付`:创建未支付(线下充值等待审批时也停在这里,由 `approval_instance_id` 查询审批状态) -- `2=已支付`:在线支付已确认,或线下充值审批通过后正在执行钱包入账 -- `3=已完成`:充值到账 -- `4=已关闭`:取消/超时 -- `5=已退款`:退款 -- `6=已驳回`:审批流程拒绝 -- `7=已退回`:审批人退回给提交人修改 +- 企微同意后按与在线充值相同的钱包入账 Worker 幂等入账;企微拒绝时终结为已驳回,不触发钱包。 +- 企微在通过前撤销或删除时关闭充值单;通过后、入账前撤销时终止入账并关闭;已经到账后再撤销时不得自动扣款,保持已完成并写严重 Audit Event、通知财务人工处理。 +- 停机发布时,下线旧 `offline-pay` 和本地 `reject` 路由。历史终态只读保留;历史待处理线下单只有在真实提交人已绑定企微且申请资料完整时才迁移为真实企微审批,其余进入异常清单,禁止猜测入账。 -`rejection_reason` 只保存驳回原因,新增 `return_reason` 保存退回修改原因,禁止复用一个字段导致前端无法区分终止和可重提。 +### 查询、通知和测试门禁 -充值与退款处理状态统一为:`0=未触发, 1=处理中, 2=处理成功, 3=处理失败`,共用同一组中文状态语义,业务页面再结合审批和支付方式解释当前动作。 - -### 流程 - -**代理自行充值(不走审批)**: - -```mermaid -flowchart LR - A[代理提交充值申请] --> B[系统生成收款码] - B --> C[代理扫码支付] - C --> D[支付回调幂等入账] -``` - -**员工线下代充值(走审批)**: - -现有 `offline-pay` 的全局操作密码校验必须保留。通用审批详情在最后一个审批节点返回 `operation_password` 动作字段;审批动作适配器调用现有 `OperationPasswordService` 校验通过后才允许流程完成。密码只在内存中参与本次校验,不落库、不写审批日志、不进入 Outbox。 - -```mermaid -sequenceDiagram - actor Staff as 平台员工 - actor Approver as 审批人 - participant Recharge as Recharge Application - participant Approval as Approval Application - participant DB as PostgreSQL - participant Worker as RechargeApprovalHandler - - Staff->>Recharge: POST /api/admin/agent-recharges(payment_method=offline) - Recharge->>DB: 同事务创建充值单(status=1) - Recharge->>Approval: StartProcess(recharge, recharge_id) - Approval->>DB: 创建实例、首任务、审批人、Outbox - Recharge->>DB: 回写 approval_instance_id - Approver->>Approval: 按 task_id 审批 - DB-->>Worker: 投递流程结果事件 - alt 审批通过 - Worker->>DB: status 从 1 更新为 2,processing_status=1 - Worker->>DB: 幂等增加钱包余额并写流水 - Worker->>DB: status 从 2 更新为 3,processing_status=2 - else 审批拒绝 - Worker->>DB: status 从 1 更新为 6,写 rejection_reason - else 退回修改 - Worker->>DB: status 从 1 更新为 7,写 return_reason - end -``` - -充值接口独立返回审批状态和业务处理状态。`RechargeApprovalHandler` 使用 `recharge:{recharge_no}` 作为幂等键;钱包余额、版本、充值单和交易流水必须在同一事务更新。 - -充值处理同样使用 `processing_started_at` 作为可恢复租约。重复事件不能再次增加余额;若钱包流水已经存在而充值单状态未完成,重试只补齐充值单状态。 - -### 退回后重新提交 - -``` -POST /api/admin/agent-recharges/{id}/resubmit - → 校验 status=7 - → 请求体可修改 amount、payment_voucher_key、remark - → 在同一事务新建 ProcessInstance - → 更新 approval_instance_id,status 回到 1(待支付/待审批) - → processing_status 重置为 0,清空处理错误和 return_reason - → 旧审批实例保留为历史记录 -``` - -新增 `resubmit` 路由时沿用现有 `/api/admin/agent-recharges` 资源名,不另建 `/agent-recharge-records` 路径。店铺、支付方式和提交人不可修改;编辑与新流程创建必须同事务完成。 - -### 停机切换 - -现有 `POST /api/admin/agent-recharges/{id}/offline-pay` 和 `POST /api/admin/agent-recharges/{id}/reject` 都会绕过通用审批任务,本次不保留兼容窗口: - -1. 发布前进入维护模式,停止创建和处理线下充值。 -2. 执行审批关联字段迁移,同时发布新 API、Worker 和前端。 -3. 初始化并启用 `recharge → recharge_approval` 绑定。 -4. 为存量“平台员工创建 + 线下支付 + 尚未入账”的充值记录幂等创建流程实例,代理在线充值不回填审批。 -5. 新前端创建线下充值后直接进入审批详情,不再展示“确认线下充值”按钮。 -6. 新版本不注册 `offline-pay` 和业务单级 `reject` 路由;线下充值只能由 `ProcessApproved` 事件触发幂等入账,驳回统一由任务级审批接口产生 `ProcessRejected`。 -7. 验证审批通过、驳回、退回、处理失败重试和钱包流水后再解除维护模式。 - -### 前端技术方案 - -- 代理自行充值保留现有收款码和支付状态页面,不显示审批信息。 -- 平台员工选择 `offline` 时,提交成功进入充值详情并展示审批时间线。 -- `status=6` 展示“已驳回”,`status=7` 展示“已退回”;两者按钮不同,只有已退回可编辑和重新提交。 -- 审批通过但 `processing_status=1` 时显示“充值处理中”;状态为 3 且处理成功后才显示最新钱包余额。 -- `processing_status=3` 时不允许前端再次点击入账,只展示系统重试状态和管理员排查入口。 +- `payment_status` 与 `processing_status` 分离;处理状态统一为 `0=未触发, 1=处理中, 2=处理成功, 3=处理失败`,不提供同义字段 `wallet_posting_status`。 +- 代理按既有充值业务权限和店铺层级范围查看本店及有权管理的下级店铺记录,不按创建账号隔离;平台和超级管理员也受业务权限与数据范围约束,企业账号不可访问。 +- 主钱包实际入账后,必须向目标代理主账号发送“充值到账”站内通知;在线实际提交账号不同于主账号时也接收一条。通知按充值单与接收人防重,失败不回滚资金。 +- 本地自动化使用真实 PostgreSQL、Redis DB 7、真实 S3 和可编程支付 Adapter;测试环境 Redis 保持 DB 6,微信/支付宝真实扫码由人工验证。真实企微审批链路是实现完成门禁,回调可经用户中转应用原样转发到本地。 diff --git a/docs/7月迭代/独立方案/新增需求/05-代理钱包扫码充值.md b/docs/7月迭代/独立方案/新增需求/05-代理钱包扫码充值.md index 23a46c4..fb44d1b 100644 --- a/docs/7月迭代/独立方案/新增需求/05-代理钱包扫码充值.md +++ b/docs/7月迭代/独立方案/新增需求/05-代理钱包扫码充值.md @@ -1,7 +1,8 @@ # 新增需求 05:代理钱包扫码充值 -> 状态:已合并至标准评审稿,本文保留为实施明细。 +> 状态:已冻结,本文保留为实施明细;如有冲突,以标准评审稿和 UR#34 PRD 为准。 > 评审主文档:`../../7月迭代技术方案-标准评审稿.md` +> 实施 PRD:`../../../../.scratch/ur34-agent-recharge/PRD.md` > 范围:代理在后台使用微信或支付宝扫码充值代理主钱包。 ## 一、已确认决策 @@ -9,7 +10,7 @@ 1. 代理在后台为自己的店铺主钱包充值。 2. 支付方式支持微信扫码和支付宝扫码。 3. 单笔最低充值金额为 100 元,即 `10000` 分。 -4. 代理在线充值不进入企业微信审批,支付成功后直接幂等增加代理主钱包余额。 +4. 代理在线充值不进入企业微信审批;支付成功事实先落库,再由可靠 Worker 幂等增加代理主钱包余额。 5. 平台员工线下代充值仍按 `02-企业微信审批接入.md` 走企微审批,与本方案隔离。 6. 后端返回支付二维码内容,前端使用现有二维码组件渲染,不由后端生成或保存二维码图片文件。 7. 微信使用 Native 支付,支付宝使用 `alipay.trade.precreate` 当面付预创建。 @@ -26,7 +27,7 @@ - 微信支付仅保存当前生效支付配置,响应中没有 `code_url`。 - 支付宝回调只分发套餐订单和客户资产钱包充值,没有代理充值订单类型。 - `HandlePaymentCallback` 在旧 Service 中直接更新充值单、钱包和流水,业务状态和幂等边界没有收口为独立用例。 -- 前端技术方案只写了“保留收款码和支付状态页面”,没有支付方式选择、最低金额、二维码过期和支付成功状态细节。 +- 前端技术方案只写了“保留收款码和支付状态页面”,没有支付方式选择、最低金额、第三方状态收敛和支付/入账状态细节。 现有评审中“代理在线充值不审批”的结论保持不变,本次补齐的是扫码支付和最低金额的完整技术方案。 @@ -47,15 +48,15 @@ sequenceDiagram API->>DB: 创建充值单和支付单 API->>Pay: Native/PreCreate 预下单 Pay-->>API: 二维码内容 - API-->>Web: 返回二维码和过期时间 + API-->>Web: 原样返回 qr_content Web-->>Agent: 展示二维码并轮询支付状态 Agent->>Pay: 扫码完成支付 Pay->>Callback: 异步支付通知 Callback->>API: ConfirmAgentRechargePayment - API->>DB: 校验支付单、金额和状态 - API->>Wallet: CreditRecharge - Wallet->>DB: 同事务增加余额、写流水、完成充值单 + API->>DB: 固化支付成功并写入账 Outbox API-->>Pay: 返回成功 + DB-->>Wallet: Worker 消费入账任务 + Wallet->>DB: 独立事务增加余额、写流水、完成充值单 Web->>API: 查询到已完成 Web-->>Agent: 展示最新钱包余额 ``` @@ -82,8 +83,9 @@ internal/ │ └── repository.go 钱包与充值聚合仓储接口 ├── application/agentrecharge/ │ ├── create_qr_recharge.go 创建充值单和扫码支付 -│ ├── confirm_payment.go 支付回调确认并入账 -│ ├── close_expired.go 关闭过期未支付充值单 +│ ├── confirm_payment.go 支付回调或查单确认支付事实 +│ ├── post_wallet.go 可靠任务执行钱包入账 +│ ├── sync_pending.go 受控查询待支付第三方订单 │ └── get_payment_status.go 轻量支付状态查询 ├── infrastructure/adapter/payment/ │ ├── wechat_native.go 微信 Native 预下单 @@ -104,11 +106,11 @@ internal/ | 1 | 待支付 | 已创建二维码,等待扫码 | | 2 | 已支付 | 支付已确认,钱包入账事务处理中 | | 3 | 已完成 | 钱包余额和流水已完成 | -| 4 | 已关闭 | 超时未支付或主动取消 | +| 4 | 已关闭 | 第三方明确未支付且已关闭/失效,或线下审批撤销/删除 | | 5 | 已退款 | 历史或后续人工退款结果 | | 6 | 已驳回 | 仅旧数据或线下审批兼容,在线充值不产生 | -`status=2` 是短暂业务处理状态。支付回调事务正常完成时直接推进到 `3`;若钱包入账发生可恢复错误,则保持 `2` 并由可靠任务继续处理。 +`status=2` 是支付事实或审批通过已经固化、钱包正在处理的业务状态。只有独立钱包入账事务成功后才推进到 `3`;发生可恢复错误时保持 `2`,使用 `processing_status=3`表达失败并由可靠任务继续处理。 ### 4.3 钱包入账不变量 @@ -117,7 +119,7 @@ internal/ - 只允许向目标店铺的 `wallet_type=main` 钱包入账。 - 入账金额必须等于充值单金额和支付单金额。 - 同一充值单只能生成一条成功钱包流水。 -- 钱包余额、`version`、充值单状态和钱包流水在同一数据库事务更新。 +- Worker 在同一数据库事务中更新钱包余额、`version`、充值单状态和钱包流水;支付事实由更早的独立事务固化,钱包失败不得回滚真实收款。 - 钱包乐观锁冲突时由 Application 重新加载后有限重试,不能重复创建流水。 - 支付渠道成功不等于业务已经完成;只有钱包事务成功后充值单才变为已完成。 @@ -196,15 +198,20 @@ ali_notify_url "payment_method": "wechat", "amount": 10000, "qr_content": "weixin://wxpay/bizpayurl?...", - "expires_at": "2026-07-15T15:00:00+08:00", "status": 1, - "status_name": "待支付" + "status_name": "待支付", + "payment_status": 0, + "payment_status_name": "待支付", + "processing_status": 0, + "processing_status_name": "未触发" } ``` 微信返回 `code_url`、支付宝返回 `qr_code`,Application 统一映射为 `qr_content`。 -## 六、支付回调与直接入账 +本地无法准确知道第三方订单的真实失效时间,因此接口不返回 `expires_at`,前端不展示本地推算的精确倒计时。第三方支付成功或关闭以回调和后端受控查单为准。 + +## 六、支付确认与可靠入账 ### 6.1 回调校验 @@ -225,16 +232,20 @@ payment.order_type = agent_recharge ### 6.2 回调事务 -```text -1. 按 payment_no 加载支付单 -2. 支付单 pending -> paid 条件更新 -3. 充值单 1 -> 2 条件更新 -4. 加载代理主钱包并执行 CreditRecharge -5. 钱包余额和 version 更新 -6. 创建唯一钱包流水 -7. 充值单 2 -> 3,写 paid_at/completed_at -8. 写 Audit Event -``` +支付事实事务: + +1. 按 `payment_no` 加载并核验支付单。 +2. 支付单从待支付条件更新为已支付。 +3. 充值单从 `1=待支付` 更新为 `2=已支付`,`processing_status=1`。 +4. 可靠写入钱包入账 Outbox 后向支付渠道返回成功。 + +钱包入账 Worker 的独立事务: + +1. 加载代理主钱包并执行 `CreditRecharge`。 +2. 更新钱包余额和 `version`。 +3. 创建唯一钱包流水。 +4. 充值单从 `2=已支付` 更新为 `3=已完成`,`processing_status=2`。 +5. 写资金 Audit Event 和到账通知事件。 幂等键: @@ -247,7 +258,7 @@ agent_recharge:{recharge_id}:credit ### 6.3 回调失败恢复 - 第三方校验失败:拒绝回调,不改变业务状态。 -- 支付状态已成功、钱包事务失败:充值单保持已支付,写 Outbox/恢复任务继续入账。 +- 支付状态已成功、钱包事务失败:充值单保持已支付,`processing_status=3`,由可靠任务继续入账。 - 钱包流水已存在但充值单未完成:恢复任务只补齐充值单状态。 - 重复回调:查询到支付单或充值单已完成后直接返回渠道成功报文。 - 本功能不引入审批,也不会因为支付金额较大转入审批。 @@ -263,16 +274,16 @@ const AgentRechargeMinAmount int64 = 10000 - 单位固定为分。 - DTO 使用 `min=10000`,Service/Application 必须再次校验。 - 前端输入单位为元,提交前转换为分。 -- 最大金额继续沿用现有系统上限,后续调整单独配置。 +- 最大金额固定为 `100000000` 分。 - 禁止前端通过浮点数直接计算金额,元转分使用字符串或十进制定点处理。 ### 7.2 权限 - 代理只能为当前登录账号所属店铺的主钱包充值。 -- 后端从登录上下文校验 `shop_id`,不能只相信请求参数。 -- 平台和超级管理员可以查看全部充值记录;是否允许代代理发起在线扫码充值保持现有权限。 +- 后端从登录上下文确定当前代理店铺,在线请求不接受 `shop_id`。 +- 平台和超级管理员不能替代理发起在线扫码充值,只能为明确的目标 `shop_id` 发起 `offline` 线下代充值。 - 企业账号无权访问代理充值接口。 -- 代理不能查看其他店铺的充值单、支付状态或二维码。 +- 创建权限与查看权限分离。代理在具备充值查看权限时,按既有店铺层级数据范围查看本店及有权管理的下级店铺充值;列表、详情和支付状态使用同一范围。 ## 八、API 设计 @@ -307,7 +318,6 @@ POST /api/admin/agent-recharges ```json { - "shop_id": 101, "amount": 10000, "payment_method": "wechat", "request_id": "01J2RECHARGE..." @@ -322,7 +332,7 @@ wechat / alipay / offline 其中 `offline` 仅平台员工线下代充值使用,并继续走企微审批;代理用户只能选择 `wechat/alipay`。 -`request_id` 用于防止前端重复点击创建多个二维码订单,同一店铺下建立业务唯一约束或 Redis 防重键。 +`request_id` 只用于防止同一次提交的重试重复创建。代理每次主动创建或再次拉起支付都必须使用新的 `request_id`,并创建新的充值单和支付单;此前未付款订单继续等待第三方自然收敛,不按金额复用旧单。 ### 8.3 查询支付状态 @@ -338,7 +348,8 @@ GET /api/admin/agent-recharges/{id}/payment-status "payment_status_name": "已支付", "paid_at": "2026-07-15T14:35:00+08:00", "completed_at": "2026-07-15T14:35:01+08:00", - "wallet_balance": 510000 + "processing_status": 2, + "processing_status_name": "处理成功" } ``` @@ -355,7 +366,7 @@ GET /api/admin/agent-recharges/{id}/payment-status - 金额使用数字输入框,单位为元,明确最低 100 元。 - 支付方式使用微信/支付宝分段控件,带对应图标。 - 不可用渠道禁用并显示简短原因。 -- 主按钮为“生成支付二维码”。 +- 主按钮为“立即充值”。按钮提交创建充值请求,后端不提供独立的二维码生成接口。 ### 9.2 二维码状态 @@ -365,16 +376,15 @@ GET /api/admin/agent-recharges/{id}/payment-status 充值金额 支付方式 二维码 -二维码剩余有效时间 支付状态 -取消/重新生成 +钱包入账状态 ``` - 前端使用 `qr_content` 生成二维码,不请求后端图片文件。 - 页面可见时每 3 秒查询一次轻量支付状态。 - 页面隐藏时暂停轮询,恢复可见时立即查询。 - 状态变为已完成、已关闭或离开页面时停止轮询。 -- 二维码过期后禁用原二维码,提供“重新生成”命令,重新创建充值单和支付单。 +- 不显示本地推算的精确过期时间,也不提供后端“取消/重新生成二维码”接口;用户再次主动拉起时按一次全新的充值创建处理。 - 支付完成后关闭二维码区域,刷新钱包余额并展示充值成功结果。 - 全流程不展示审批状态或企微审批区块。 @@ -398,12 +408,12 @@ GET /api/admin/agent-recharges/{id}/payment-status 微信/支付宝支付回调成功/失败 钱包入账成功/失败 重复回调被幂等忽略 -充值单超时关闭 +第三方查单确认支付关闭或失效 ``` 支付渠道交互写 `tb_integration_log`,钱包余额变化写关键 `Audit Event`,并关联充值单、支付单、代理钱包和钱包流水。 -在线充值不产生审批通知。充值成功后可以生成普通资金结果站内通知,但不能显示“审批通过”。 +在线充值不产生审批通知。目标代理主钱包实际入账后必须生成“充值到账”站内通知;在线实际提交账号与目标代理主账号不同时,两者分别通知并按充值单与接收人防重。平台线下代充值的真实提交人只接收 UR#37 的审批结果通知,除非其本身也是到账通知接收人。 ## 十一、代码迁移范围 @@ -438,9 +448,9 @@ GET /api/admin/agent-recharges/{id}/payment-status 4. 验证支付宝 PreCreate 返回有效 `qr_code`,前端能够扫码支付。 5. 验证支付回调通过支付单类型分发到代理充值用例。 6. 验证微信、支付宝回调金额不一致时不会增加钱包余额。 -7. 验证支付成功后不创建企微审批实例,直接完成钱包入账。 +7. 验证支付成功后不创建企微审批实例,先固化支付事实,再由可靠 Worker 完成钱包入账。 8. 验证重复回调只产生一条钱包流水,余额只增加一次。 9. 验证支付成功但钱包事务暂时失败时能够恢复完成,不需要代理重复支付。 -10. 验证二维码过期后充值单关闭,旧二维码不能继续显示为有效。 +10. 验证接口不返回 `expires_at`,前端不展示本地倒计时;第三方明确关闭后充值单才关闭,迟到成功回调仍能幂等入账。 11. 验证代理充值详情固定返回 `approval_source=none`,不展示审批区域。 12. 验证平台员工线下代充值仍按原企微审批方案执行,不受在线充值改造影响。