跳转到主要内容

FSD M7 · 分销提现

← 总则 | 附录 → | 上游:PRD §4 M7 实现落点:backend/internal/domain/distribution/(model 181/service 233/repo 214)+ cmd/server/distribution_handlers.go(184 行 11 handler)+ cmd/server/routes.go:137-151(挂载)。事实基准=Go 代码;冻结版 qmtv/server/index.js:598-638(用户面 7)+:708-737(状态机 4)两段不连续(中间隔 admin 段 :640-:703)仅差异对拍参照。 域内要点:零域铁律——withdrawals/exchanges 全域不触 users.credits/credit_records/通知/SSE(「返佣写入方在 M8 approve 链」model.go:4-8,唯一写入方 M8 F8.03);available 分母无 status 过滤(cancelled/rejected 永不释放额度——:715 note「收益已解锁」冻结谎言照抄);settings 回退双 shape 不可合并(overview 三键 vs 提现面一键);affiliate_threshold 980 硬编码与 M8 不同源勿统一;W_FLOW 严格邻接状态机(终态无键恒 400);admin PUT 不限属主 vs cancel/confirm 属主强制。bind 形态:POST/PUT 三处裸传 return err(echo 默认 400 无统一信封——与 m12「请求体格式错误」两域不同制)。PRD §4 M7 已登记差异 ①~⑩(prd-qmtv-v1.md:180-184)引用不重立。

M7 分销提现

11 端点全量(用户面 9 条 amw + admin 2 条 amw+adminMw 双挂——物理在 /admin/ 前缀但归本域,M12 裁决⑩)。

F7.01 分销总览(overview)

对应 PRD:§4 M7 · GET /api/distribution/overview | 状态:实现 | 实现落点:distribution_handlers.go:28-37、service.go:34-70、repo.go:57-98 | UI:DistributionView.vue(Promise.all 五请求聚合单 query ['dist-all'] 保「任一失败单 toast」语义)| 场景挂钩:无专项 e2e(E2E-WD-01 待实现);DistributionChain 第七链集成锚

1 功能定义

下级数/累计佣金/在途提现/分销设置/推广码/开通标志/阈值——四查询七键总览。

2 触发条件与前置状态

登录态(amw)+DB 就绪;无行级前置。

3 输入与校验

无 body/query。

4 处理流程

①CountDownlines(referral_relations JOIN users 语义保留勿简化单表 COUNT——孤儿 invitee 面 COUNT 与冻结 all().length 逐位一致,repo.go:57-58)②SumCommissions(COALESCE 0)③PendingWithdrawSum(冻结双 IN 冗余 → Go 等价单 IN 三态 ('pending','reviewing','topay')——paid 被 AND 掉,repo.go:72-78 结果逐位一致)④GetDistSettings(ErrNoRows → SettingsFallback{0.2,0.05,100} 冻结 :602 字面量)⑤GetUserDist(users 两列窄投影 referral_code,affiliate_enabled)。

5 输出与结果状态

OverviewDoc 七键序 downline_count,total_commission,pending_withdraw,settings,referral_code,affiliate_enabled,affiliate_threshold(model.go:160-168)。settings 双 shape:有行=SettingsRow 四键 id,commission_rate,level2_rate,min_withdraw;无行=SettingsFallback 三键 {commission_rate:0.2,level2_rate:0.05,min_withdraw:100} 无 id 键——两 shape 不可合并。referral_code/affiliate_enabled 在 UserDist 为 json:"-" 经 OverviewDoc 再出。

6 状态流转

只读零写。

7 边界与异常

settings 表错误非 ErrNoRows → 500;其余仅 DB 错误透传。零业务 4xx 面。

8 权限与数据规则

数据严格限本人(inviter_id=?/user_id=?);affiliate_threshold 恒 980 硬编码(model.go:158-159/service.go:70)——与 M8 recharge 读 affiliate_threshold_cny 刻意不同源勿统一(PRD M7 ⑤=M8 ⑤ 双登记)。

F7.02 下线与佣金流水(downlines/records)

对应 PRD:§4 M7 · GET /api/distribution/downlines、GET /api/distribution/records | 状态:实现 | 实现落点:distribution_handlers.go:39-60、service.go:73-82、repo.go:103-117 | UI:DistributionView.vue(聚合 query 内)| 场景挂钩:DistributionChain(seed 对拍 40 佣金断言)

1 功能定义

下线列表(含次级下线数 sub_count)+佣金流水(含来源人昵称 from_name)。

2 触发条件与前置状态

登录态;commission_records 空表时 records 为 [](零域 quirk——唯一写入方 M8 approve 链,repo.go:6 注)。

3 输入与校验

无 body/query。

4 处理流程

downlines 单 SELECT(id,nickname,email,created_at+sub_count 相关子查询,无 ORDER BY 无 LIMIT=冻结 :606 同形);records 单 SELECT(c.* 显式 8 列+u.nickname AS from_name 尾插,ORDER BY created_at DESC **LIMIT 100**)。

5 输出与结果状态

downlines 行键 id,nickname,email,created_at,sub_count;records 行键 id,user_id,from_user_id,order_amount,commission,level,status,created_at,from_name(order_amount/commission float64——0004 DECIMAL 后 int64 对小数行恒 scan error→500,二十二期 A1 收口)。LIMIT 分化:域内仅 records 有 LIMIT 100。

6 状态流转

无写。

7 边界与异常

零行业务异常;空列表 JSON [](gorp nil→MakeSlice 与冻结 .all() 逐位一致)。

8 权限与数据规则

本人维度;from_name JOIN 可能因 from_user 无行而少行(INNER JOIN 同构冻结 :609)。

F7.03 提现列表与申请(withdrawals GET/POST)

对应 PRD:§4 M7 · GET、POST /api/distribution/withdrawals | 状态:实现 | 实现落点:distribution_handlers.go:62-93、service.go:85-123+:155-167、repo.go:121-125+:144-166 | UI:DistributionView.vue(wd 提现表单)| 场景挂钩:E2E-WD-01(待实现——主干「余额划转」措辞与差异①相悖,引用时勘正为「佣金台账划转不动 users.credits」);DistributionChain(⑨⑩ 双断言)

1 功能定义

本人提现工单列表+按可提现额度(Σ佣金−Σ提现)申请提现(佣金台账面非余额)。

2 触发条件与前置状态

POST 前置=金额 ≥ min_withdraw 且 ≤ available。

3 输入与校验

POST body credits float64,bind 裸传 return err(handlers:85-87——echo 默认 400 无统一信封);校验序:①credits==0 || credits<min_withdraw → 400 动态文案(!credits 语义=0)②credits>available → 400「可提现积分不足」。

4 处理流程

GET 单 SELECT 7 列 ORDER BY created_at DESC 无 LIMIT。POST:①读 settings 取 min_withdraw——回退仅 {min_withdraw:100} 一键(与 overview 回退三键不同 shape 不共用,PRD ④)②算 available=Σcommission−Σwithdrawals(分母 SUM 无 status 过滤——cancelled/rejected 永不释放额度,:715 note「收益已解锁」冻结谎言照抄,PRD ②)③w_ id → INSERT 三列 (id,user_id,credits),status/created_at 走 DDL DEFAULT(‘pending’)。

5 输出与结果状态

WithdrawalRow 行键序 id,user_id,credits,status,created_at,note,reviewed_at(note/reviewed_at 可空→null 透传);POST {"id":"w_..."}。

6 状态流转

INSERT 即建 pending 态工单(后续流转 F7.05/F7.06)。

7 边界与异常
条件系统行为提示/码
金额 0 或 < min_withdraw400最低提现 %d 积分(动态 min_withdraw 插值=冻结 :619 模板串)
超出可提现额度400「可提现积分不足」
8 权限与数据规则

credits 绑定 float64=冻结 +req.body.credits 强转(PRD ⑥——冻结接受 JSON 数字串 Go bind 拒之);零域:不触 users.credits/credit_records/通知/SSE。

F7.04 兑换(exchanges GET/POST)

对应 PRD:§4 M7 · GET、POST /api/distribution/exchanges | 状态:实现 | 实现落点:distribution_handlers.go:95-124、service.go:128-148、repo.go:128-132+:152-172 | UI:DistributionView.vue(兑换表单)| 场景挂钩:DistributionChain(死态断言)

1 功能定义

佣金积分兑换列表+申请兑换(佣金台账面,min=10 常量不读 settings)。

2 触发条件与前置状态

POST 前置=金额 ≥10 且 ≤(Σ佣金−Σ兑换)。

3 输入与校验

POST body credits float64,bind 裸传 return err;校验序:①credits==0 || credits<10 → 400 ②credits>available → 400。

4 处理流程

GET 单 SELECT 5 列无 LIMIT。POST:Σcommissions → ΣexchangesAll(同构无 status 过滤)→ ex_ id → INSERT 三列,status 走 DDL DEFAULT ‘pending’——全冻结无任何 UPDATE 死态照抄(PRD ⑨)。

5 输出与结果状态

ExchangeRow 行键 id,user_id,credits,status,created_at;POST {"id":"ex_..."}。

6 状态流转

exchanges.status 恒 ‘pending’(死态——集成断言钉死)。

7 边界与异常
条件系统行为提示/码
金额 0 或 <10400「最低兑换 10 积分」
超出可兑换额度400「可兑换积分不足」
8 权限与数据规则

amw 本人维度;兑换不扣 withdrawals 面——available 独立再减 Σexchanges(service.go:130-131 注=冻结 :630-631)。

F7.05 撤回与确认到账(cancel/confirm)

对应 PRD:§4 M7 · POST /api/distribution/withdrawals/:id/cancel、POST …/:id/confirm | 状态:实现 | 实现落点:distribution_handlers.go:126-145、service.go:181-201、repo.go:177-207 | UI:DistributionView.vue(工单操作)| 场景挂钩:DistributionChain

1 功能定义

属主自撤(仅 pending/reviewing)+打款后属主确认(仅 paid)——状态机用户面两臂。

2 触发条件与前置状态

cancel 前置 status ∈ {pending,reviewing};confirm 前置 status=‘paid’;均先过属主 404 关(WHERE id=? AND user_id=?)。

3 输入与校验

无 body;路径 :id。

4 处理流程

cancel:属主查行 → 状态谓词 → UPDATE ... SET status='cancelled', note='用户撤回(收益已解锁)'——不写 reviewed_at、不回补余额(released 纯展示,repo.go:196-197 注)→ 响应。confirm:属主查行 → status!=="paid" 谓词 → UPDATE ... SET status='done', note='用户确认到账'(同样不写 reviewed_at)→ ok(true)。

5 输出与结果状态

cancel {"cancelled":true,"released":<float>};confirm data:true。

6 状态流转

pending/reviewing →(cancel)→ cancelled;paid →(confirm)→ done。cancelled/done 均终态(W_FLOW 无键恒 400)。

7 边界与异常
条件系统行为提示/码
工单不存在/非本人404「工单不存在」(cancel/confirm/admin PUT 三处共享同文案同哨兵)
非 pending/reviewing 撤回400「当前状态不可撤回(已进入打款流程)」(全角括号照抄)
非 paid 确认400「仅在「已打款」状态下可确认到账」(直角引号照抄)
8 权限与数据规则

amw+属主强制(与 admin PUT 不限属主形成对照);cancel 不写 reviewed_at;released 纯展示不回补额度(「收益已解锁」谎言见 F7.03)。

F7.06 管理端审核状态机(admin withdrawals PUT/GET)

对应 PRD:§4 M7 · PUT、GET /api/admin/withdrawals(/:id) | 状态:实现 | 实现落点:distribution_handlers.go:147-171、service.go:204-233、model.go:44-71、repo.go:135-140+:186-194+:209-214 | UI:AdminView.vue(提现 tab 消费本域端点——wdSet/wdReject 合并 mutation)| 场景挂钩:无专项 e2e(E2E-WD-01 待实现);DistributionChain(逐态迁移+终态 400+bogus 态三断言)

1 功能定义

admin 严格邻接流转提现工单+全量工单列表(归 M7 域系 M12 裁决⑩——物理在 /admin/ 前缀但域逻辑/册归属均本域)。

2 触发条件与前置状态

amw+adminMw 双挂(全组唯二,其余 9 条仅 amw);PUT 前置=工单存在(不限属主 WHERE id=?)且当前态在 W_FLOW 有键且 next ∈ 邻接表。

3 输入与校验

PUT body {"status":string,"note":string|null}(Note *string;note 空串→nil COALESCE 保留旧值=冻结 :732 req.body.note || null);bind 裸传 return err。GET 无参数(无 status 过滤——与 m08 admin list 双分支不同制)。

4 处理流程

PUT:①WithdrawalByID 不限属主 ②W_FLOW[w.Status] miss→nil 等价空表(=冻结 ||[])③线性扫邻接不合 → StatusError{400, 不允许从「X」流转到「Y」}(zhOr miss 原样回显英文态=冻结 W_ZH[s]||s)④UPDATE ... SET status=?, note=COALESCE(?,note), reviewed_at=?——reviewed_at 每次合法流转覆写,值=newUTCLocalTime() UTC 墙钟(与 auth LocalTime 本地时刻意不同源,PRD ⑦)⑤响应。GET:w.* 显式 7 列+email 尾插,ORDER BY created_at DESC 无 LIMIT。

5 输出与结果状态

PUT {"status":next,"zh":"<W_ZH[next]>"};GET 行键序 id,user_id,credits,status,created_at,note,reviewed_at,email。

6 状态流转

W_FLOW 邻接表全表(model.go:47-52=冻结 index.js:708 字面量直译勿优化):

当前态可流转 next说明
pendingreviewing, rejected待审核臂
reviewingtopay, rejected审核中臂
topaypaid, rejected待打款臂
paiddone打款完成臂
done / rejected / cancelled(无键→nil→恒 400)三终态禁出

W_ZH:pending=待审核/reviewing=审核中/topay=待打款/paid=已打款/done=已完成/rejected=已驳回/cancelled=已撤回。完整迁移链 pending→reviewing→topay→paid→done,任意臂可旁路 rejected,用户臂 cancelled。

7 边界与异常
条件系统行为提示/码
工单不存在404「工单不存在」(三处共享哨兵)
非邻接流转/终态流转/bogus 态400不允许从「%s」流转到「%s」(严格邻接禁跳转——W_ZH miss 原样回显英文态,PRD ⑧)
非 admin403“forbidden”(adminMw)
8 权限与数据规则

quirk:PUT 不限属主 vs cancel/confirm 属主(F7.05 对照);note COALESCE 缺省保留;reviewed_at UTC 每次合法流转覆写(cancel/confirm 不写——不对称)。


册尾端点覆盖对账(11/11)

对账 backend/cmd/server/routes.go:141-151 亲数 11 条 ↔ 本册 F7.01~F7.06。口径脚注:冻结源物理排布两段不连续(用户面 7 条 index.js:598-638 + 状态机 4 条 :708-737,中间隔 admin 段);Go 物理分组单块连续 11 条;M12 组注释「41 物理 − withdrawals 2 条归 M7」(routes.go:268-270)——#10/#11 物理在 /admin/ 前缀但归本域(M12 裁决⑩)。

#端点(routes.go:line)节
1GET /distribution/overview(:141)F7.01
2GET /distribution/downlines(:142)F7.02
3GET /distribution/records(:143)F7.02
4GET /distribution/withdrawals(:144)F7.03
5POST /distribution/withdrawals(:145)F7.03
6GET /distribution/exchanges(:146)F7.04
7POST /distribution/exchanges(:147)F7.04
8POST /distribution/withdrawals/:id/cancel(:148)F7.05
9POST /distribution/withdrawals/:id/confirm(:149)F7.05
10PUT /admin/withdrawals/:id(:150,amw+adminMw)F7.06
11GET /admin/withdrawals(:151,amw+adminMw)F7.06

差异纪律:PRD §4 M7 ①~⑩ 全引用不重立(含 DECIMAL 收口⑥/UTC reviewed_at ⑦/W_FLOW ⑧/死态⑨/M12 归属⑩);本册零新立 DIF(19 端点 wire 面键集/键序/文案/状态码/校验序与冻结逐位一致——撰写期双侧对拍判定)。注记:Go 注释冻结行号微偏三处(model.go:27-28 引 :633/:636 实 :632/:633、repo.go:168-171 引 :637 实 :635、service.go:130 引 :628-638 实 628-636)——行为零差勘误候选非 DIF。