Files
junhong_cmp_fiber/openspec/specs/agent-distribution-withdrawal/spec.md
break e8ab1f471e
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 12m54s
fix(代理分销注册): 验证码改为落库成功后消费并细化失败原因
公开扫码注册原先在审批准备前就消费短信验证码,落库前的任何失败都会烧掉验证码,
客户重试只能得到统一的“分销码不可用”,掩盖了真实失败原因。

- 验证码校验与消费拆分为 CheckCode 与 ConsumeCode,注册记录与审批实例落库成功后才原子消费;
  校验不消费、消费一次性,落库前失败时同一验证码可直接重试
- 分销码无效、所属店铺已停用、上级店铺缺少启用的主账号、验证码错误分别返回各自错误码与提示
- 注册必填字段缺失与请求参数校验失败提示定位到具体字段
- 同步公开接口描述与 agent-distribution-withdrawal 主 Spec 行为契约
2026-09-17 18:01:47 +08:00

12 KiB
Raw Blame History

agent-distribution-withdrawal Specification

Purpose

使代理下级注册和佣金提现均以企业微信审批、资格有效性和不可变业务快照为准,避免代理层级或资金事实因资料变更、重复回调而漂移。

Requirements

Requirement: 分销码与待审批代理注册

系统 SHALL 在每个代理店铺创建时生成全局唯一、不可修改的随机分销码;二维码仅编码 H5 注册入口和该码,二维码渲染与 H5 页面不属于本能力。分销码 MUST NOT 支持人工指定或编辑。

系统 SHALL 提供公开后端接口 POST /api/c/v1/agent-distribution-registrations不要求登录、JWT、角色或权限。请求 MUST 携带有效 distribution_code、短信已验证手机号、密码及既有注册必填资料;系统 MUST 复用既有短信验证码校验与限流规则,并 MUST 保证同一验证码至多被消费一次。系统 MUST 为该次申请创建唯一的待审批注册记录,并 MUST NOT 在审批通过前创建店铺、代理账号、钱包或上下级归属。

分销码无效、分销码所属店铺已停用、上级店铺缺少启用的主账号、短信验证码无效或已被消费时,系统 MUST NOT 创建注册记录或审批实例,且 MUST 按失败原因返回各自可定位的错误码与提示MUST NOT 统一为同一不可用结果。短信验证码 MUST 在注册记录与审批实例落库成功后才消费;落库前的任何失败 MUST NOT 消费验证码,客户 MUST 能用同一验证码直接重试。分销码所属店铺停用 MUST NOT 级联变更既有下级与既有佣金关系。

注册审批 MUST 使用业务类型 agent_distribution_approval,其业务标识 MUST 为待审批注册记录主键。企业微信最终通过时,系统 MUST 在同一事务内创建启用店铺、代理账号、所需钱包,写入上级店铺、初始业务员快照,标记注册记录已通过并记录审计;最终驳回时 MUST NOT 创建店铺、账号、钱包或层级,仅保留注册记录与审批结果。

同一手机号在驳回后再次扫码 MUST 形成新的注册记录与新的审批实例;重复或乱序回调 MUST NOT 重复创建账号、层级或钱包。

Scenario: 扫码注册进入待审批

  • WHEN 客户使用有效分销码提交已验证手机号、密码与注册必填资料
  • THEN 系统仅创建一条待审批注册记录及企业微信审批实例,不创建店铺、账号或钱包

Scenario: 停用代理码注册

  • WHEN 客户使用已停用代理所属店铺的分销码注册
  • THEN 系统拒绝创建注册记录与审批实例,且不产生任何店铺或账号

Scenario: 验证码无效或已消费

  • WHEN 请求携带的短信验证码无效、过期或已被消费
  • THEN 系统拒绝创建注册记录,且不消耗该分销码的注册名额

Scenario: 落库前失败不消耗验证码

  • WHEN 短信验证码校验通过后,注册记录或审批实例创建失败
  • THEN 系统保留该验证码,客户可用同一验证码重试,重试得到的仍是同一失败原因而非验证码失效

Scenario: 审批通过建立层级与业务员快照

  • WHEN 企业微信最终通过一条待审批注册记录
  • THEN 系统在同一事务内创建启用店铺与代理账号、写入分销码所属店铺为直接上级、复制上级当时业务员为初始业务员,并标记注册记录已通过

Scenario: 审批驳回不产生实体

  • WHEN 企业微信最终驳回一条待审批注册记录
  • THEN 系统不创建店铺、账号、钱包或层级,且同一手机号可再次扫码形成新的注册记录

Scenario: 重复回调不重复建层级

  • WHEN 同一注册审批终态被重复投递
  • THEN 系统至多创建一次店铺、账号、钱包与层级关系

Requirement: 提现资料资格

代理首次提现前 SHALL 提交企业微信资料资格申请;合同和法人身份证正反面必填,企业代理填写统一社会信用代码、个人代理填写法人身份证号。合同资格主体 MUST 填写统一社会信用代码或身份证号;营业执照、门头照可选,发票仅企业可选且其抬头/统一社会信用代码 MUST 与合同主体一致。审批通过且资料未过期才有效;合同和身份证通过后长期有效,直到代理替换资料、超级管理员作废或代理停用。

系统 SHALL 以不可变的资料版本保存资格事实与附件引用,历史版本、附件与审批实例 MUST NOT 被覆盖。审批 MUST 使用业务类型 withdrawal_qualification_approval,其业务标识 MUST 为资料版本主键。代理替换合同或法人身份证 MUST 新增资料版本并创建新的审批实例,同时在同一事务内使旧有效版本失效;代理 MUST 重新审批通过后才可提现。系统 MUST NOT 为资格资料提供同一版本的重新提交或重提状态机。

超级管理员作废资格 MUST 填写原因;代理停用 MUST 自动使该店铺全部有效资格失效。

资格申请、资格作废与后台查询 MUST 沿用既有认证、自身店铺或数据范围校验MUST NOT 公开。

Scenario: 资料替换后提现

  • WHEN 有效资格的代理替换合同或法人身份证资料
  • THEN 旧有效版本立即失效,新版本进入企业微信审批,代理重新审批通过后才可提现

Scenario: 历史版本不被覆盖

  • WHEN 同一店铺存在多个资料版本
  • THEN 每个版本保留各自的附件引用、审批实例与审批结果,历史版本不被改写

Scenario: 超级管理员作废资格

  • WHEN 超级管理员填写原因后作废某有效资格
  • THEN 该资格失效且原因可查询,代理提现被拒绝直至重新审批通过

Scenario: 代理停用使资格失效

  • WHEN 代理店铺被停用
  • THEN 该店铺全部有效资格失效,历史版本与审批结果保留

Scenario: 未认证或越权访问资格接口

  • WHEN 未认证请求,或请求的店铺超出当前账号数据范围
  • THEN 系统拒绝访问且不披露资格是否存在

Requirement: 提现冻结与企业微信终审

提现申请 SHALL 先校验申请店铺存在有效资料资格,再冻结可提现余额、金额、手续费、收款信息和可选发票快照,并创建企业微信审批。余额不足、资格无效或非本人代理时,系统 MUST NOT 创建提现申请、审批实例或任何冻结。发票为申请级材料,若上传 MUST 按当时有效合同主体校验并冻结至该次审批快照。本地 MUST NOT 提供人工通过或驳回终审。审批 MUST 使用业务类型 commission_withdrawal_approval

每次提交或重提 MUST 新增一条不可变的审批尝试记录冻结该次金额、手续费、实际到账、收款信息与申请级发票快照并为其创建独立的审批实例提现申请行只保存最新审批实例标识用于列表投影MUST NOT 作为历史事实来源。企业微信通过即视为已到账:系统 MUST 保持 WithdrawalStatusApproved2并在同一事务内仅一次从冻结余额扣减同时写入到账时间MUST NOT 使用已到账状态值 4。

企业微信驳回时,系统 MUST 仅一次释放本次尝试的冻结余额并记录释放时间;cancelleddeleted MUST 按驳回同等处理。驳回后代理可修改金额、收款信息和本次发票重新提交,每次新建审批尝试记录与审批实例;资料资格保持有效。企业微信对已通过申请撤销时,系统 MUST NOT 回滚已到账金额、MUST NOT 重新冻结MUST 保持通过状态并写入正交的异常标记与原因供详情展示与人工处理MUST NOT 自动重提。

重复、乱序或结果未知的回调 MUST NOT 造成重复扣减、重复释放或第二笔冻结;结果未知时申请保持在途并由既有查询恢复机制收敛。

Scenario: 资格无效或余额不足不创建提现

  • WHEN 代理店铺不存在有效资料资格、资格已失效,或可提现余额不足
  • THEN 系统拒绝创建提现申请,不创建审批实例且不冻结任何余额

Scenario: 提现审批通过

  • WHEN 企业微信对一笔待审提现返回最终通过且该结果首次被消费
  • THEN 系统仅一次从冻结余额扣减,申请保持已通过状态并记录到账时间

Scenario: 提现审批驳回

  • WHEN 企业微信最终驳回一笔提现申请
  • THEN 系统仅一次释放该次尝试的冻结余额并记录释放时间,保留审批快照,并允许在资格仍有效时修改后重提

Scenario: 驳回后重提

  • WHEN 代理修改金额、收款信息或本次发票后重新提交被驳回的提现申请
  • THEN 系统新增审批尝试记录并创建新的审批实例,历史尝试、快照与审批结果不被覆盖

Scenario: 企微取消或删除

  • WHEN 企业微信对待审提现返回取消或删除
  • THEN 系统按驳回同等处理,仅一次释放冻结余额并记录释放时间

Scenario: 通过后撤销

  • WHEN 企业微信对已通过的提现申请返回通过后撤销
  • THEN 系统不回滚已到账金额、不重新冻结、不自动重提,仅写入异常标记与原因供详情展示

Scenario: 重复回调不重复扣减

  • WHEN 同一审批终态被重复投递或乱序到达
  • THEN 系统至多扣减或释放一次,不产生第二笔冻结

Requirement: 提现本地人工终审边界

系统 SHALL 拒绝本地人工通过与驳回已关联审批实例的提现申请;该判定 MUST 以申请记录中是否已关联审批实例为准MUST NOT 依赖任何新增配置开关。本地接口拒绝时 MUST 返回稳定的状态冲突错误且 MUST NOT 改变申请状态、余额或流水。

approval_instance_id 为空的存量提现申请 MUST 保持既有本地人工处理行为不变;其他审批场景的本地历史语义 MUST NOT 被本能力改变。

Scenario: 新提现申请拒绝本地终审

  • WHEN 平台账号对已关联企业微信审批实例的提现申请调用本地通过或驳回接口
  • THEN 系统返回状态冲突、申请状态与钱包余额不变

Scenario: 存量提现保持兼容

  • WHEN 本地通过或驳回接口作用于从未关联审批实例的存量提现申请
  • THEN 系统按既有规则完成扣减或释放,行为与本次变更前一致

Requirement: 待审提现与佣金回溯的释放接缝

系统 SHALL 保证后续佣金回溯处理待审提现时,每个未结算审批尝试的冻结余额可被幂等释放:释放金额 MUST 取该尝试的金额事实,释放完成 MUST 写入该尝试的释放时间事实,且释放 MUST 先于同一店铺佣金余额扣减完成。已释放的尝试 MUST NOT 被重复释放,重复处理 MUST NOT 重复增加可提现余额。

Scenario: 回溯先释放后扣减

  • WHEN 佣金回溯处理存在待审提现的店铺
  • THEN 系统先释放每个未结算尝试的冻结余额并记录释放时间,再扣减佣金余额

Scenario: 重复释放不重复入账

  • WHEN 同一未结算尝试的释放被重复执行
  • THEN 系统至多释放一次,冻结余额与可提现余额不被重复调整

可达操作索引

本节只用于入口导航,不是行为 Requirement业务义务以上述 Requirements 为准。

代理分销注册

POST /api/c/v1/agent-distribution-registrations(代理扫码注册,公开接口)。

提现资料资格

POST /api/admin/shops/{shop_id}/withdrawal-qualifications(提交提现资料资格);GET /api/admin/shops/{shop_id}/withdrawal-qualifications(查询提现资料资格版本);POST /api/admin/withdrawal-qualifications/{id}/void(作废提现资料资格)。

佣金提现

GET /api/admin/shops/{shop_id}/withdrawal-requests(代理商提现记录);POST /api/admin/shops/{shop_id}/withdrawal-requests(发起提现申请);PUT /api/admin/shops/{shop_id}/withdrawal-requests/{id}(重提被驳回的提现申请);GET /api/admin/shops/{shop_id}/withdrawal-requests/{id}(提现申请详情);POST /api/admin/commission/withdrawal-requests/{id}/approve(本地人工通过提现,仅存量无审批实例申请);POST /api/admin/commission/withdrawal-requests/{id}/reject(本地人工驳回提现,仅存量无审批实例申请)。