← 文档索引

邀请奖励 + 奖励任务

设计文档 · 2026-07-12 · Spec ②(推广激励)· 状态 待评审

目标:用「送 Pro 会员天数」驱动增长。两条并行的得分途径,共用同一条发天数链路:
邀请好友 —— 两段式,双方都得(注册各 +3 天,被邀请人首充双方再各 +7 天)。
奖励任务 —— 首个任务「加入 Telegram 频道」+3 天,经 Bot getChatMember 真校验。框架可扩展(后续「关注推特 / App 评分」复用)。
发天数原语:复用现有 subscriptions 延时逻辑(applySubscription(..., source) = max(到期,now)+days),与付费 / 兑换码同一条,不造新轮子。

1 · 邀请奖励模型(两段式,双方都得)

阶段触发邀请人被邀请人封顶
注册被邀请人用邀请码注册成功+3 天 Pro+3 天 Pro邀请人 N 次/月(默认 10)
首充被邀请人首次成功付费(任何档,含 ¥6 优惠)+7 天 Pro+7 天 Pro不封顶

每转化一个付费用户:邀请人累计 +10 天、被邀请人累计 +10 天。注册段是唯一「免费面」故设月度封顶;首充段真金白银自带成本,不封顶。

邀请码 / 链接 / 绑定

2 · 防刷

3 · 数据模型

users 加一列(SQLite / MySQL 两套迁移):

类型说明
invite_codevarchar(16) UNIQUE本用户的永久邀请码;注册/首次访问邀请页时惰性生成。
first_paid_atdatetime NULL首次成功付费时刻;判「首充」+ 幂等首充奖励。

referrals —— 邀请关系(关系型,一对一绑定):

类型说明
idPK
inviter_idbigint邀请人 user id(索引)。
invitee_idbigint UNIQUE被邀请人 user id;唯一 = 一人只被绑一次。
device_uuidvarchar被邀请人注册设备 dp_uuid(设备去重取证)。
statusvarcharboundreg_rewardedpaid_rewarded,或 rejected(防刷命中)。
reg_rewarded_atdatetime NULL注册段奖励发放时刻(幂等)。
paid_rewarded_atdatetime NULL首充段奖励发放时刻(幂等)。
created_atdatetime

reward_claims —— 通用一次性任务领取(TG 及后续任务复用):

类型说明
idPK
user_idbigint领取人。
task_keyvarchar任务标识,如 telegram_join
external_refvarchar外部去重键;TG 用 telegram_id
granted_daysint本次发放天数。
granted_atdatetime
约束:UNIQUE(user_id, task_key)(每账户每任务一次)+ UNIQUE(task_key, external_ref)(同一 telegram_id 只领一次)。

奖励发放本身落现有 subscriptions(source='invite' / 'task')+ sub_events 审计,不新增奖励台账表。

4 · 后端 API + 钩子

端点 / 钩子动作
Register(..., inviteCode)注册流程加 inviteCode 参:建号成功后解析码 → 邀请人 → 建 referrals(bound) → 过防刷闸(自邀请 / 设备去重 / 月度封顶)→ 发注册段双方 +3(reg_rewarded)。
pay webhook.settle()首充钩子:在 GrantPaidSubscriptionTx 之后,若 users.first_paid_at 本次由空转非空(=首充)且该用户是被邀请人(referrals 命中)→ 发首充段双方 +7(paid_rewarded,幂等)。
GET /v1/invite我的邀请码 / 链接 + 战绩:已邀请数、已转化(首充)数、累计获赠天数、明细列表;附奖励任务清单(TG 完成态)。
GET /v1/tasks/telegram/start签发绑定 token(10 分钟有效、绑当前账户),返回 bot 深链 t.me/<reward_bot>?start=<token>
POST /tg/webhookTelegram Bot 更新回调(secret 校验,与 pay webhook 同款外部回调):收 /start <token> → 校验成员 → 发 TG 任务 +3。详见 §5。

5 · TG 任务验证流程(Bot getChatMember)

App(客户端) pangolin-server Telegram(Bot/API) ① GET /v1/tasks/telegram/start ② 返回 t.me/<bot>?start=<token> ③ 拉起 bot,用户按 Start(/start token) ④ POST /tg/webhook(update:token + telegram_id) ⑤ getChatMember(频道, telegram_id) ⑥ status = member / administrator ⑦ 发 +3 天(reward_claims) ⑧ sendMessage「✅ 已到账 +3 天」 ⑨ App 回前台 GET /v1/invite → 任务已完成 非成员 → bot 回「请先加入频道再验证」,不发
  1. token 短时有效(10 分钟)、一次性,绑当前账户 —— 防止链接被转发后他人领取。
  2. webhook 安全:设置 Telegram secret_token,服务端校验 X-Telegram-Bot-Api-Secret-Token 头;路径也可带 secret 段。控制面经 Cloudflare Tunnel 已是公网 HTTPS,Telegram 可达,无需开入站端口
  3. 成员判定:getChatMember 返回 member / administrator / creator 视为已加入;left / kicked / 查询失败视为未加入。
  4. 不追溯退群:一次性发放,发后不因退群回收(回收体验差、收益低)。

6 · 新基建(上线前置,需在 Telegram 侧配一次)

7 · 客户端 UI

邀请页(占位屏已在)升级为「邀请 + 任务」两区,数据来自 GET /v1/invite:

邀请区(上)

  • 真实邀请码 + 邀请链接,复制 / 系统分享。
  • 规则说明:注册双方各 +3、首充双方各 +7。
  • 战绩台账:已邀请 / 已转化 / 累计获赠天数 + 明细列表。

任务区(下)「更多得会员」

  • 首张卡:加入 Telegram 频道 +3 天。
  • 未完成 → 「加入频道」+「验证领取」两步按钮。
  • 已完成 → 「已领 +3 天」置灰。
  • 未配 bot → 整卡不显示。

注册页:加「邀请码(选填)」输入框;deep link …/i/<code> 打开 App 自动预填并锁定。l10n 六语。

8 · 边界(YAGNI,本期不做)

9 · 测试要点

10 · 涉及文件(实现锚点)

# 后端
server/internal/auth/service.go        # Register 加 inviteCode + 建 referrals + 注册段发奖
server/internal/reward/                 # 新包:邀请/任务发奖 + 防刷闸 + getChatMember
server/internal/pay/webhook.go          # settle() 首充钩子接首充段发奖
server/internal/httpapi/                 # GET /v1/invite、/v1/tasks/telegram/start、POST /tg/webhook
server/migrations/{mysql,sqlite}/        # users +2 列、referrals、reward_claims
# 客户端
client/lib/screens/invite_page.dart      # 占位 → 真实(邀请区 + 任务区)
client/lib/widgets/auth_screen.dart      # 注册页加邀请码输入 + deep link 预填
client/lib/services/ + state/            # invite api + provider(auth_api.Register 加 inviteCode)
client/lib/l10n/                          # 六语文案

下一步:定稿后进 writing-plans 出逐任务实现计划(docs/superpowers/plans/2026-07-12-invite-task-rewards.md),Subagent 驱动执行。TG 新基建(bot / 频道管理员 / webhook)在实现前由你在 Telegram 侧配好。