From dc080436bf78715eb1fd80d7894ce57630dfcfc2 Mon Sep 17 00:00:00 2001 From: break Date: Tue, 28 Jul 2026 16:57:27 +0800 Subject: [PATCH] =?UTF-8?q?=E8=B6=8A=E6=9D=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .scratch/tech-global-audit/审计覆盖基线.md | 1 + docs/7月迭代/七月迭代人工验收清单.md | 2 +- docs/admin-openapi.yaml | 1 + internal/query/agentrecharge/payment_status.go | 4 +++- internal/routes/agent_recharge.go | 11 ++++++----- internal/service/agent_recharge/service.go | 4 ++-- internal/store/postgres/agent_recharge_store.go | 4 +++- 7 files changed, 17 insertions(+), 10 deletions(-) diff --git a/.scratch/tech-global-audit/审计覆盖基线.md b/.scratch/tech-global-audit/审计覆盖基线.md index a55d9b3..b7e19f4 100644 --- a/.scratch/tech-global-audit/审计覆盖基线.md +++ b/.scratch/tech-global-audit/审计覆盖基线.md @@ -66,6 +66,7 @@ | 企业微信审批加密回调与详情终态同步 | 回调为外部自动事实,不伪造人工 Audit Event;验签、AES 解密、receiveid 校验失败只记录安全错误,不进入业务终态 | `tb_integration_log` 按应用和签名幂等保存入站密文哈希,`tb_wecom_approval_context.latest_detail_snapshot` 保存权威详情,通用审批实例及决策投递表保存标准终态 | 入站回调先写 Integration Log pending,详情任务完成后置 completed;每次 `getapprovaldetail` 写独立出站 Integration Log,不保存 access_token,但保存 sp_no、sp_status 和耗时摘要 | 回调只入队结构化 `wecom:approval:sync` 任务;权威终态通过现有 `SyncDecisionService` 原子写 `approval.terminal_decision.recorded`,不直接修改退款或钱包 | | 企业微信审批主动恢复、未终态轮询与审批人读取投影 | N/A(定时恢复和只读投影均为系统自动动作;不伪造人工操作者,映射不到账号不改变业务事实) | `tb_wecom_approval_context.submission_attempted_at/last_recovery_at/sp_no/latest_detail_snapshot` 与通用审批实例是恢复和展示的权威本地事实;只有唯一候选可从结果未知转为审批中 | 每次 `getapprovalinfo` 分页和 `getapprovaldetail` 均写独立出站 Integration Log,只保存应用、时间窗、模板、发起人、单号数量、sp_no、状态和耗时等安全摘要,不保存 Secret、access_token、media_id、附件正文或完整响应 | Scheduler 仅提交 `wecom:approval:recovery`;恢复和轮询仅提交结构化 `wecom:approval:sync`,不写审批提交 Outbox、不调用 `applyevent`,标准终态仍沿用既有终态 Outbox | | 员工线下代充值申请与企微终态入账 | 申请保存以真实提交人及明文业务快照留痕;资金成功 Audit Event 延期至既有统一钱包治理任务,企微自动终态不伪造人工审批人 | `tb_agent_recharge_record`、`tb_approval_instance` 和 `tb_wecom_approval_context` 同事务保存申请事实;approved 通过 `topup + recharge_record_id` 唯一成功钱包流水幂等入账,其他终态不修改钱包,通过后撤销不自动冲正;`tb_notification` 保存到账通知 | 申请创建本身不外呼;后续附件上传、applyevent、详情与恢复沿用企微 Integration Log,业务参数按用户确认保存明文,日志仍不记录 Secret、access_token、media_id 或附件正文 | 创建事务写 `approval.submission.requested`;标准终态写 `approval.terminal_decision.recorded`,approved 入账事务再写 `wallet.agent_main.credited` 和目标店铺的 `notification.admin.dynamic.requested`;在线充值复用同一入账接缝;新审批单禁止旧人工确认或驳回入口绕过 | +| 代理充值列表、详情与支付状态读取 | N/A(普通受权读取;请求进入 Access Log,不改变充值、支付或钱包事实) | 只读 `tb_agent_recharge_record`、相关支付单和店铺名称;平台账号不限制,代理账号统一按当前上下文的自身及下级店铺 ID 过滤 | N/A(不调用支付渠道或企微) | N/A(纯 Query,不产生 Outbox) | | 退款申请与企微终态处理 | 申请以真实提交人和订单/金额/凭证明文快照留痕;企微自动终态不伪造本地人工审批人,资金与佣金 Audit Event 继续沿用既有治理延期口径 | `tb_refund_request`、通用审批实例和企微上下文同事务保存;approved 条件更新订单与退款单,代理主钱包按 refund ID、资产钱包按退款单号复核成功回款;佣金按记录锁定并失效,套餐按订单及换货迁移关系幂等失效;`tb_notification` 保存退款完成通知 | 申请创建不外呼;附件、applyevent、详情、回调和恢复沿用企微 Integration Log,业务参数明文保存在业务/审批快照中但不复制到 Integration Log,Secret、access_token、media_id 和附件正文仍禁止记录 | 创建事务写 `approval.submission.requested`;终态写 `approval.terminal_decision.recorded`;退款事务幂等写目标店铺的 `notification.admin.dynamic.requested`;业务消费者只有在订单、钱包、佣金和资产后处理完成后才确认投递成功,失败释放租约重试 | | 退款与线下代充值旧审批入口发布切换 | N/A(部署环境开关控制旧入口是否可用,不新增业务操作;实际旧入口操作继续沿用各自既有审计口径) | `approval_instance_id IS NULL` 是存量旧 provider 的兼容边界,非空记录只接受企微标准终态;关闭开关不修改或删除任何业务事实 | N/A(开关判断不调用外部系统,也不得写 Integration Log 冒充发布审计) | N/A(开关判断不产生可靠副作用;企微 Worker 继续消费既有标准终态 Outbox) | | 单列 CSV 资产套餐批量订购 | 任务创建和逐行订单继续进入 Access Log、任务操作者快照与既有订单/钱包审计接缝;本 Change 不新建批量任务专用 Audit Writer | `tb_asset_package_batch_order_task` 保存输入参数和逐行结果,成功行以 `tb_order`、订单明细、套餐使用、支付记录及代理钱包成功流水为权威业务事实 | 对象存储上传和下载沿用现有存储日志,不把文件正文写入 Integration Log;本切片不新增外部支付或 Gateway 调用 | 创建接口提交结构化 `asset:package:batch_order` Asynq 任务;逐行钱包订单继续沿用既有钱包扣款 Outbox 和佣金任务,重复任务由状态条件与订单幂等规则阻断 | diff --git a/docs/7月迭代/七月迭代人工验收清单.md b/docs/7月迭代/七月迭代人工验收清单.md index 61cd1cd..2fece11 100644 --- a/docs/7月迭代/七月迭代人工验收清单.md +++ b/docs/7月迭代/七月迭代人工验收清单.md @@ -5,7 +5,7 @@ ## 一、业务需求 - [ ] **UR#33 套餐临期提醒**:核对 0~15 天临期列表、0~3 天置顶、Dashboard 数量、C 端续费入口,以及 15/7/3 天向全部有效店铺账号、有效绑定的平台业务员和资产绑定个人账号发送防重站内通知;正文明确资产标识、到期状态和到期日期。 -- [ ] **UR#34 充值审核流程**:核对代理在线扫码充值与平台线下代充值隔离;支付、企微审批、重复回调和入账补偿正确;到账后通知全部有效店铺账号及有效绑定的平台业务员,正文明确店铺名称和精确到分的充值金额。 +- [ ] **UR#34 充值审核流程**:核对代理在线扫码充值与平台线下代充值隔离;支付、企微审批、重复回调和入账补偿正确;代理充值列表、详情和支付状态只能读取自己及下级店铺数据;到账后通知全部有效店铺账号及有效绑定的平台业务员,正文明确店铺名称和精确到分的充值金额。 - [ ] **UR#35 退款审核**:核对企微审批、整单终结、钱包回充、套餐失效和佣金处理;重复终态不重复退款或通知;退款完成后通知全部有效店铺账号及有效绑定的平台业务员。 - [ ] **UR#36 批量订购套餐**:核对 CSV 上传、跨代理资产、统一支付方式、逐行结果、部分成功、失败原因、任务恢复及重复执行不重复扣款。 - [ ] **UR#37 企业微信审核流转**:核对企微应用、模板、成员绑定、代理固定代提交人、回调、主动同步和异常恢复;系统内不得提供本地同意或拒绝入口。 diff --git a/docs/admin-openapi.yaml b/docs/admin-openapi.yaml index 8d7fbfd..3674b76 100644 --- a/docs/admin-openapi.yaml +++ b/docs/admin-openapi.yaml @@ -12366,6 +12366,7 @@ paths: - 企业微信审批 /api/admin/agent-recharges: get: + description: 平台账号可查看全部;代理账号仅返回自己店铺及下级店铺的充值订单。 parameters: - description: 页码,默认1 in: query diff --git a/internal/query/agentrecharge/payment_status.go b/internal/query/agentrecharge/payment_status.go index 1d1eec3..a3c7783 100644 --- a/internal/query/agentrecharge/payment_status.go +++ b/internal/query/agentrecharge/payment_status.go @@ -10,6 +10,7 @@ import ( "github.com/break/junhong_cmp_fiber/internal/model/dto" "github.com/break/junhong_cmp_fiber/pkg/constants" "github.com/break/junhong_cmp_fiber/pkg/errors" + "github.com/break/junhong_cmp_fiber/pkg/middleware" ) // PaymentStatusQuery 查询代理充值本地支付与到账事实。 @@ -28,7 +29,8 @@ func (q *PaymentStatusQuery) Get(ctx context.Context, rechargeID uint) (*dto.Age return nil, errors.New(errors.CodeInvalidParam, "代理充值支付状态查询参数无效") } var recharge model.AgentRechargeRecord - if err := q.db.WithContext(ctx).Where("id = ?", rechargeID).First(&recharge).Error; err != nil { + rechargeQuery := middleware.ApplyShopFilter(ctx, q.db.WithContext(ctx).Model(&model.AgentRechargeRecord{})) + if err := rechargeQuery.Where("id = ?", rechargeID).First(&recharge).Error; err != nil { if err == gorm.ErrRecordNotFound { return nil, errors.New(errors.CodeForbidden, "无权限操作该资源或资源不存在") } diff --git a/internal/routes/agent_recharge.go b/internal/routes/agent_recharge.go index f9d8e76..206cdfd 100644 --- a/internal/routes/agent_recharge.go +++ b/internal/routes/agent_recharge.go @@ -30,11 +30,12 @@ func registerAgentRechargeRoutes(router fiber.Router, handler *admin.AgentRechar }) Register(group, doc, groupPath, "GET", "", handler.List, RouteSpec{ - Summary: "查询代理充值订单列表", - Tags: []string{"代理预充值"}, - Input: new(dto.AgentRechargeListRequest), - Output: new(dto.AgentRechargeListResponse), - Auth: true, + Summary: "查询代理充值订单列表", + Description: "平台账号可查看全部;代理账号仅返回自己店铺及下级店铺的充值订单。", + Tags: []string{"代理预充值"}, + Input: new(dto.AgentRechargeListRequest), + Output: new(dto.AgentRechargeListResponse), + Auth: true, }) Register(group, doc, groupPath, "GET", "/payment-methods", handler.PaymentMethods, RouteSpec{ diff --git a/internal/service/agent_recharge/service.go b/internal/service/agent_recharge/service.go index ed15422..417286b 100644 --- a/internal/service/agent_recharge/service.go +++ b/internal/service/agent_recharge/service.go @@ -411,7 +411,7 @@ func (s *Service) GetByID(ctx context.Context, id uint) (*dto.AgentRechargeRespo record, err := s.agentRechargeStore.GetByID(ctx, id) if err != nil { if err == gorm.ErrRecordNotFound { - return nil, errors.New(errors.CodeNotFound, "充值记录不存在") + return nil, errors.New(errors.CodeForbidden, "无权限操作该资源或资源不存在") } return nil, errors.Wrap(errors.CodeDatabaseError, err, "查询充值记录失败") } @@ -444,7 +444,7 @@ func (s *Service) List(ctx context.Context, req *dto.AgentRechargeListRequest) ( pageSize = constants.DefaultPageSize } - query := s.db.WithContext(ctx).Model(&model.AgentRechargeRecord{}) + query := middleware.ApplyShopFilter(ctx, s.db.WithContext(ctx).Model(&model.AgentRechargeRecord{})) if req.ShopID != nil { query = query.Where("shop_id = ?", *req.ShopID) diff --git a/internal/store/postgres/agent_recharge_store.go b/internal/store/postgres/agent_recharge_store.go index 1907d50..73573d5 100644 --- a/internal/store/postgres/agent_recharge_store.go +++ b/internal/store/postgres/agent_recharge_store.go @@ -5,6 +5,7 @@ import ( "github.com/break/junhong_cmp_fiber/internal/model" "github.com/break/junhong_cmp_fiber/pkg/constants" + "github.com/break/junhong_cmp_fiber/pkg/middleware" "github.com/redis/go-redis/v9" "gorm.io/gorm" ) @@ -48,7 +49,8 @@ func (s *AgentRechargeStore) GetByRechargeNo(ctx context.Context, rechargeNo str // GetByID 根据 ID 查询 func (s *AgentRechargeStore) GetByID(ctx context.Context, id uint) (*model.AgentRechargeRecord, error) { var record model.AgentRechargeRecord - if err := s.db.WithContext(ctx).First(&record, id).Error; err != nil { + query := middleware.ApplyShopFilter(ctx, s.db.WithContext(ctx).Model(&model.AgentRechargeRecord{})) + if err := query.First(&record, id).Error; err != nil { return nil, err } return &record, nil