This commit is contained in:
@@ -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 和佣金任务,重复任务由状态条件与订单幂等规则阻断 |
|
||||
|
||||
@@ -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 企业微信审核流转**:核对企微应用、模板、成员绑定、代理固定代提交人、回调、主动同步和异常恢复;系统内不得提供本地同意或拒绝入口。
|
||||
|
||||
@@ -12366,6 +12366,7 @@ paths:
|
||||
- 企业微信审批
|
||||
/api/admin/agent-recharges:
|
||||
get:
|
||||
description: 平台账号可查看全部;代理账号仅返回自己店铺及下级店铺的充值订单。
|
||||
parameters:
|
||||
- description: 页码,默认1
|
||||
in: query
|
||||
|
||||
@@ -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, "无权限操作该资源或资源不存在")
|
||||
}
|
||||
|
||||
@@ -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{
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user