384 lines
20 KiB
Markdown
384 lines
20 KiB
Markdown
# 分销与绑定流程图
|
||
|
||
> 用流程图把「绑定」和「推荐人/邀请码」在系统中的用法讲清楚。
|
||
> 建议配合《邀请码分销规则说明》一起看。
|
||
|
||
---
|
||
|
||
## 一、概念速查
|
||
|
||
| 名词 | 是什么 | 存哪儿 | 谁用 |
|
||
|------|--------|--------|------|
|
||
| **邀请码** | 一串码,如 `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`,不等到下单。后端根据上述规则决定是**新绑定 / 续期 / 抢夺 / 拒绝**。
|
||
|
||
```mermaid
|
||
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`。逻辑是:**先认绑定,再认邀请码**。
|
||
|
||
```mermaid
|
||
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。
|
||
|
||
```mermaid
|
||
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 = active**、**expiry_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 邀请码(怎么用、不混用)
|
||
|
||
```mermaid
|
||
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**,不会把邀请码字符串当推荐人存。
|
||
|
||
---
|
||
|
||
## 七、表与字段关系简图
|
||
|
||
```mermaid
|
||
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 时双写,避免单改其一。 |
|
||
|
||
---
|
||
|
||
若要把某一段改成「按步骤」的纯文字版或拆成多张图,可以说明要哪一段(绑定 / 下单 / 分佣 / 概念)。
|