20 KiB
分销与绑定流程图
用流程图把「绑定」和「推荐人/邀请码」在系统中的用法讲清楚。
建议配合《邀请码分销规则说明》一起看。
一、概念速查
| 名词 | 是什么 | 存哪儿 | 谁用 |
|---|---|---|---|
| 邀请码 | 一串码,如 SOULABC123 |
每个用户一条:users.referral_code |
链接里 ref=邀请码,用来认出是谁推荐的 |
| 推荐人 | 拿佣金的那个人(用户) | 用用户ID存:referrer_id |
绑定表、订单表、分佣都只认这个 ID |
| 被推荐人 | 通过链接进来的访客/买家 | 用用户ID存:referee_id |
绑定表里「谁被谁推荐」 |
关系:邀请码 → 查 users 表 → 得到推荐人用户ID(referrer_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_id 和 orders.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 不参与分佣计算,只用于统计和展示。
什么情况下能拿到佣金(推广者视角)
满足下面全部条件时,你(推广者)才能拿到这笔订单的佣金:
-
对方是通过你的链接进来的
对方点击的链接里带有你的邀请码(如?ref=你的邀请码),进入小程序后系统会记下推荐码,并在登录时用于绑定。 -
对方已经绑定到你
对方完成登录后,系统成功调用了绑定接口(新绑定或续期),且当前存在一条「被推荐人 = 对方、推荐人 = 你」的绑定记录,且该绑定 status = active、expiry_date > 当前时间(在 30 天有效期内或已续期)。 -
对方在绑定有效期内下单并支付成功
对方在上述有效期内发起了购买(章节或全书),并完成微信支付;支付成功后,微信会回调我们的接口。 -
支付回调时仍能查到你的有效绑定
支付成功回调执行时,系统按「买家 = 对方」查referral_bindings,能查到一条有效绑定且推荐人是你,才会把约 90% 的佣金计入你的待结算收益(pending_earnings),并把该绑定标记为已转化(converted)。
简单记:你的链接 → 对方进来并登录绑定到你 → 有效期内对方付款 → 你拿佣金。
章节分享这块的分销收益方式
章节页分享(读某一章时分享给好友/朋友圈)与首页、推广中心的分享用同一套绑定与分佣规则,只是落地页是「某一章」的阅读页。收益方式如下:
-
入口与绑定
你从阅读页分享出去的链接带ref=你的邀请码(例如/pages/read/read?id=1.2&ref=你的邀请码)。对方点进后进入该章节阅读页,系统记下推荐码;对方登录后即完成绑定(新绑定或续期)。绑定规则(30 天、不重复绑、超时可重绑)与其它分享入口一致。 -
收益比例
订单实付金额的约 90% 给推广者(与全书、其它章节一致,由referral_config.distributorShare配置)。对方买的是这一章、别的章还是全书,都按该笔订单金额 × 90% 计算佣金。 -
计佣次数(每个被推荐人只计一次)
系统在支付成功回调里会查「该买家」的有效绑定,有则给推荐人加佣金,并把这条绑定标记为已转化(converted)。
因此:同一个被推荐人在绑定有效期内,只有其「第一笔」支付会给你分佣;该用户之后再买其它章节或全书,不再重复给你分佣(绑定已用掉)。 -
小结
章节分享的收益:你分享章节链接(带 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_id、referral_code只做展示和对账,不参与分佣计算。
八、逻辑漏洞与注意点
以下为与流程图、实现对照后容易出现的漏洞和设计注意点,便于排查与加固。
8.1 严重:支付回调中买家身份不能信任客户端
问题:支付回调(/api/miniprogram/pay/notify)里若优先使用请求体/attach 里的 userId 作为买家,则该 userId 来自创建订单时客户端传入的 body.userId。若被篡改(如传成他人 userId),会导致:
- 订单归属、解锁权限记到错误用户;
- 分佣按「错误买家」查绑定表,可能把佣金算到错误推荐人或不分佣。
正确做法:买家身份必须以微信回调中的 openId 为准(微信侧不可伪造),用 openId 查 users 得到 buyerUserId;attach 中的 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 时双写,避免单改其一。 |
若要把某一段改成「按步骤」的纯文字版或拆成多张图,可以说明要哪一段(绑定 / 下单 / 分佣 / 概念)。