Files
soul-yongping/开发文档/8、部署/分销与绑定流程图.md
2026-02-09 15:09:29 +08:00

20 KiB
Raw Blame History

分销与绑定流程图

用流程图把「绑定」和「推荐人/邀请码」在系统中的用法讲清楚。
建议配合《邀请码分销规则说明》一起看。


一、概念速查

名词 是什么 存哪儿 谁用
邀请码 一串码,如 SOULABC123 每个用户一条:users.referral_code 链接里 ref=邀请码,用来认出是谁推荐的
推荐人 拿佣金的那个人(用户) 用户ID存:referrer_id 绑定表、订单表、分佣都只认这个 ID
被推荐人 通过链接进来的访客/买家 用户ID存:referee_id 绑定表里「谁被谁推荐」

关系:邀请码 → 查 users 表 → 得到推荐人用户IDreferrer_id。系统里所有「归属、分佣」只认 referrer_id不直接认邀请码字符串。


二、整体流程总览(一图看懂)

┌─────────────────────────────────────────────────────────────────────────────────┐
│                          分销全流程:从分享到分佣                                  │
└─────────────────────────────────────────────────────────────────────────────────┘

  推广者 A推荐人                    访客/买家 B被推荐人                    系统

        │                                      │                                  │
        │  1. 分享带 ref 的链接                 │                                  │
        │     ?ref=A的邀请码                    │                                  │
        ├─────────────────────────────────────>│  2. 点击链接进入小程序/阅读页       │
        │                                      ├─────────────────────────────────>│
        │                                      │     app.js: 存 referral_code      │
        │                                      │     可选: 记录访问 referral_visit  │
        │                                      │                                  │
        │                                      │  3. 登录(微信/手机号/getOpenId 拿到 user │
        │                                      ├─────────────────────────────────>│
        │                                      │     登录成功即调 /api/referral/bind  │
        │                                      │     入参: userId, referralCode    │
        │                                      │                                  │
        │                                      │                    4. 绑定逻辑    │
        │                                      │                    referral_code  │
        │                                      │                    → 查 users 得  │
        │                                      │                    referrer_id=A │
        │                                      │                    写 referral_   │
        │                                      │                    bindings      │
        │                                      │                    (referee=B,   │
        │                                      │                     referrer=A)  │
        │                                      │<─────────────────────────────────┤
        │                                      │  绑定成功new/renew/takeover   │
        │                                      │                                  │
        │                                      │  5. 下单(章节/找伙伴)            │
        │                                      │     POST /api/miniprogram/pay     │
        │                                      │     body: referralCode(可选)      │
        │                                      ├─────────────────────────────────>│
        │                                      │                    6. 定推荐人    │
        │                                      │                    先查 bindings │
        │                                      │                    (referee=B)→A  │
        │                                      │                    无则用 referral│
        │                                      │                    Code 解析→A   │
        │                                      │                    写 orders.     │
        │                                      │                    referrer_id=A,│
        │                                      │                    referral_code │
        │                                      │<─────────────────────────────────┤
        │                                      │  返回支付参数                     │
        │                                      │                                  │
        │                                      │  7. 调起微信支付                   │
        │                                      ├───────────────────────────────> 微信
        │                                      │  8. 用户付款成功                   │
        │                                      │<───────────────────────────────  微信
        │                                      │                                  │
        │                                      │                    9. 支付回调    │
        │                                      │                    POST .../notify│
        │                                      │                    查 bindings    │
        │                                      │                    (referee=B)→A  │
        │                                      │                    佣金=金额×90%  │
        │                                      │                    A.pending_     │
        │                                      │                    earnings += 佣金│
        │                                      │                    binding→      │
        │                                      │                    converted     │
        │                                      │                                  │
        │  10. 推广者 A 看到待结算收益 +90%     │                                  │
        │<─────────────────────────────────────│                                  │

三、绑定流程(邀请码 → 推荐关系)

绑定解决的是:「谁B是通过谁A的链接来的」,并写入 referral_bindings

绑定规则(后端统一保证):

  • 不重复绑定:被推荐人 B 已有当前推荐人 A 的有效绑定时,再次用 A 的邀请码调用 bind → 不新建记录,只做续期(把过期时间再延长 30 天)。
  • 有时效:每条绑定的有效期为 30 天expiry_date);分佣、下单定推荐人时只认「未过期」的绑定。
  • 超时可重新绑定:超过 30 天未续期的绑定视为过期;此时 B 再通过其他人 C 的链接进来并登录 → 允许绑定到 C旧绑定标记过期新绑定 C即「抢夺」若仍通过 A 的链接 → 续期 A 的绑定。

绑定从登录就开始:只要前端拿到 userId登录成功就立刻用当前的 pendingReferralCode/api/referral/bind,不等到下单。后端根据上述规则决定是新绑定 / 续期 / 抢夺 / 拒绝

flowchart TB
    subgraph 入口
        A1["推广者 A 分享链接<br/>带 ref=A的邀请码"]
        A2["访客 B 点击链接进入"]
    end

    subgraph 前端
        B1["app.js: 检测到 ref"]
        B2["写入 storage: referral_code + pendingReferralCode"]
        B3["若已登录 → 立即调 bind"]
        B4["若未登录 → 等任意登录成功后再调 bind"]
        B5["登录含: login / loginWithPhone / getOpenId 拿到 user 时"]
    end

    subgraph 后端绑定API["POST /api/referral/bind"]
        C1["入参: userId(B), referralCode"]
        C2["用 referralCode 查 users 表 → 推荐人 A"]
        C3["不能自己推荐自己"]
        C4["查 B 是否已有有效绑定(active)"]
        C5{"已有绑定?"}
        C6["同一推荐人 A → 只续期,不重复绑定<br/>expiry = 当前+30天"]
        C7["不同人且已过期(>30天) → 可重新绑定<br/>旧绑定过期,新绑定 A"]
        C8["不同人且未过期 → 拒绝"]
        C9["无绑定 / 续期 / 抢夺后 → 写 binding<br/>referrer_id=A, referee_id=B, expiry=+30天"]
    end

    A1 --> A2 --> B1 --> B2
    B2 --> B3
    B2 --> B4
    B4 --> B5
    B3 --> C1
    B5 --> C1
    C1 --> C2 --> C3 --> C4 --> C5
    C5 -->|无| C9
    C5 -->|有,同一人| C6 --> C9
    C5 -->|有,另一人已过期| C7 --> C9
    C5 -->|有,另一人未过期| C8

要点:

  • 绑定表是「谁推荐了谁」的唯一权威;分佣只看这张表。
  • 已有绑定不重复:同一推荐人再次绑只续期;30 天内不能换绑其他推荐人,超过 30 天可重新绑定(被新推荐人「抢夺」或原推荐人续期)。
  • 邀请码只在「解析出推荐人是谁」时用,解析完得到的是 referrer_id用户ID

四、下单时「推荐人」怎么定(写订单)

创建订单时要把「这笔单算谁的推广」记在 orders.referrer_idorders.referral_code。逻辑是:先认绑定,再认邀请码

flowchart LR
    subgraph 请求
        R1["POST /api/miniprogram/pay"]
        R2["body: userId(B), referralCode(可选)"]
    end

    subgraph 定推荐人
        S1["查 referral_bindings"]
        S2["WHERE referee_id = B<br/>AND status='active'<br/>AND expiry_date > NOW()"]
        S3{"查到有效绑定?"}
        S4["referrer_id = 绑定里的 referrer_id"]
        S5["referrer_id = 用 referralCode<br/>查 users 得到的 id"]
        S6["都无 → referrer_id = null"]
    end

    subgraph 写订单
        T1["INSERT orders"]
        T2["referrer_id = 上面得到的"]
        T3["referral_code = 请求里的 referralCode<br/>或推荐人当前 users.referral_code"]
    end

    R1 --> R2 --> S1 --> S2 --> S3
    S3 -->|是| S4
    S3 -->|否,但有 referralCode| S5
    S3 -->|否且无| S6
    S4 --> T1
    S5 --> T1
    S6 --> T1
    T1 --> T2 --> T3

结论:

  • 有绑定 → 订单的推荐人 = 绑定里的推荐人(与下单时传不传 referralCode 无关)。
  • 无绑定但传了 referralCode → 用邀请码解析出推荐人,写入订单。
  • 订单上的 referrer_id 用于后台展示、对账;分佣不看订单,只看绑定表。

五、分佣流程(支付成功后)

分佣只看绑定表,不看订单上的 referrer_id。

flowchart TB
    subgraph 触发
        P1["微信支付成功"]
        P2["POST /api/miniprogram/pay/notify"]
        P3["body: 订单号、金额、买家等"]
    end

    subgraph 回调逻辑
        Q1["更新订单 status=paid"]
        Q2["解锁用户权限(章节/全书)"]
        Q3["查 referral_bindings"]
        Q4["WHERE referee_id = 买家"]
        Q5["AND status='active'"]
        Q6["AND expiry_date > NOW()"]
        Q7{"查到有效绑定?"}
        Q8["取 referrer_id = 推广者 A"]
        Q9["佣金 = 订单金额 × 90%"]
        Q10["A.pending_earnings += 佣金"]
        Q11["该绑定 status → converted"]
        Q12["记录 commission_amount, order_id"]
        Q13["不分佣"]
    end

    P1 --> P2 --> P3 --> Q1 --> Q2 --> Q3 --> Q4 --> Q5 --> Q6 --> Q7
    Q7 -->|是| Q8 --> Q9 --> Q10 --> Q11 --> Q12
    Q7 -->|否| Q13

要点:

  • 分佣只认 referral_bindings 里「买家 → 有效绑定 → 推荐人」。
  • 订单里的 referrer_id / referral_code 不参与分佣计算,只用于统计和展示。

什么情况下能拿到佣金(推广者视角)

满足下面全部条件时,你(推广者)才能拿到这笔订单的佣金:

  1. 对方是通过你的链接进来的
    对方点击的链接里带有你的邀请码(如 ?ref=你的邀请码),进入小程序后系统会记下推荐码,并在登录时用于绑定。

  2. 对方已经绑定到你
    对方完成登录后,系统成功调用了绑定接口(新绑定或续期),且当前存在一条「被推荐人 = 对方、推荐人 = 你」的绑定记录,且该绑定 status = activeexpiry_date > 当前时间(在 30 天有效期内或已续期)。

  3. 对方在绑定有效期内下单并支付成功
    对方在上述有效期内发起了购买(章节或全书),并完成微信支付;支付成功后,微信会回调我们的接口。

  4. 支付回调时仍能查到你的有效绑定
    支付成功回调执行时,系统按「买家 = 对方」查 referral_bindings,能查到一条有效绑定且推荐人是你,才会把约 90% 的佣金计入你的待结算收益pending_earnings并把该绑定标记为已转化converted

简单记:你的链接 → 对方进来并登录绑定到你 → 有效期内对方付款 → 你拿佣金。


章节分享这块的分销收益方式

章节页分享(读某一章时分享给好友/朋友圈)与首页、推广中心的分享用同一套绑定与分佣规则,只是落地页是「某一章」的阅读页。收益方式如下:

  1. 入口与绑定
    你从阅读页分享出去的链接带 ref=你的邀请码(例如 /pages/read/read?id=1.2&ref=你的邀请码)。对方点进后进入该章节阅读页,系统记下推荐码;对方登录后即完成绑定新绑定或续期。绑定规则30 天、不重复绑、超时可重绑)与其它分享入口一致。

  2. 收益比例
    订单实付金额的约 90% 给推广者(与全书、其它章节一致,由 referral_config.distributorShare 配置)。对方买的是这一章、别的章还是全书,都按该笔订单金额 × 90% 计算佣金。

  3. 计佣次数(每个被推荐人只计一次)
    系统在支付成功回调里会查「该买家」的有效绑定,有则给推荐人加佣金,并把这条绑定标记为已转化converted
    因此:同一个被推荐人在绑定有效期内,只有其「第一笔」支付会给你分佣;该用户之后再买其它章节或全书,不再重复给你分佣(绑定已用掉)。

  4. 小结
    章节分享的收益:你分享章节链接(带 ref→ 对方进来并登录绑定到你 → 对方在有效期内第一次支付(可以是这一章、别的章或全书)→ 你获得该笔订单金额的约 90%;该用户后续订单不再给你分佣。


六、推荐人 vs 邀请码(怎么用、不混用)

flowchart LR
    subgraph 入口
        L1["链接 ref=SOULABC123"]
    end

    subgraph 解析
        L2["邀请码 = SOULABC123"]
        L3["users WHERE referral_code = ?"]
        L4["推荐人 = 该用户的 id"]
    end

    subgraph 存储
        M1["referral_bindings.referrer_id"]
        M2["orders.referrer_id"]
        M3["分佣发给谁"]
    end

    L1 --> L2 --> L3 --> L4
    L4 --> M1
    L4 --> M2
    L4 --> M3

    style L2 fill:#f9f,stroke:#333
    style L4 fill:#9f9,stroke:#333
  • 邀请码:只在「从链接/请求里认出是谁」这一步用,用完就解析成 referrer_id
  • 推荐人:所有「归属、分佣、统计」都只用 referrer_id,不会把邀请码字符串当推荐人存。

七、表与字段关系简图

erDiagram
    users ||--o{ referral_bindings : "referrer_id"
    users ||--o{ referral_bindings : "referee_id"
    users {
        string id PK
        string referral_code "自己的邀请码"
    }
    referral_bindings {
        string referrer_id "推荐人(谁拿佣金)"
        string referee_id "被推荐人(买家)"
        string status "active|converted|expired"
        timestamp expiry_date
    }
    orders {
        string user_id "买家"
        string referrer_id "推荐人ID(展示/对账)"
        string referral_code "下单时邀请码(展示)"
    }
    referral_bindings ||--o{ orders : "分佣时关联"
  • 绑定referrer_id = 推荐人,referee_id = 被推荐人;分佣只看这张表。
  • 订单referrer_idreferral_code 只做展示和对账,不参与分佣计算。

八、逻辑漏洞与注意点

以下为与流程图、实现对照后容易出现的漏洞和设计注意点,便于排查与加固。

8.1 严重:支付回调中买家身份不能信任客户端

问题:支付回调(/api/miniprogram/pay/notify)里若优先使用请求体/attach 里的 userId 作为买家,则该 userId 来自创建订单时客户端传入body.userId。若被篡改(如传成他人 userId会导致

  • 订单归属、解锁权限记到错误用户;
  • 分佣按「错误买家」查绑定表,可能把佣金算到错误推荐人或不分佣。

正确做法买家身份必须以微信回调中的 openId 为准(微信侧不可伪造),用 openIdusers 得到 buyerUserIdattach 中的 userId 仅作辅助或校验,不一致时以 openId 解析结果为准。

实现建议:在 notify 中先 buyerUserId = 由 openId 查 users 得到;若查不到再回退到 attach.userId并打日志告警。


8.2 设计缺口:先下单、后绑定会导致无分佣

问题:流程图要求「先绑定、再下单」分佣才生效。若用户通过 A 的链接进入但未调用 /api/referral/bind(未登录就下单、或 bind 失败/漏调),下单时传了 referralCode,订单上会有 referrer_id=A,但分佣只看绑定表,此时无绑定 → 不会给 A 分佣。

结论:这是当前设计下的预期行为,不是 bug但需要在产品/运营上保证「进入后尽快登录并完成绑定」,或在文档中明确写清:只有存在有效绑定时支付成功才会分佣


8.3 重复回调与重复分佣

现状:微信可能对同一笔支付多次回调。当前实现:

  • 订单状态已为 paid 时跳过订单更新;
  • 分佣时只取 status='active' 的绑定,且分佣后将该绑定置为 converted,同一买家不会再有第二条 active 绑定参与分佣。

因此不会重复加佣。无需改流程图,实现已防护。


8.4 绑定表与 users.referred_by 双写

现状:绑定 API 在「新绑定」或「抢夺」时会写 users.referred_by,与 referral_bindings 双写;「续期」只更新绑定表,不改 referred_by
分佣、下单定推荐人只读绑定表GET 查询「我的推荐人」等可能读 users.referred_by。只要绑定接口保证 new/takeover 时双写一致,则无逻辑漏洞。若以后有接口只改 referred_by 而不改绑定表,就会不一致,需避免。


8.5 小结

类型 说明
必须修 支付回调中买家身份以 openId 解析为准,不信任 attach.userId。
文档/产品 明确「先绑定再下单才能分佣」;未绑定仅下单只记订单归属、不分佣。
已防护 重复回调不会导致重复分佣。
需长期一致 绑定表与 users.referred_by 在 new/takeover 时双写,避免单改其一。

若要把某一段改成「按步骤」的纯文字版或拆成多张图,可以说明要哪一段(绑定 / 下单 / 分佣 / 概念)。