设计文档 · 2026-07-12 · Spec ②(推广激励)· 状态 待评审
getChatMember 真校验。框架可扩展(后续「关注推特 / App 评分」复用)。subscriptions 延时逻辑(applySubscription(..., source) = max(到期,now)+days),与付费 / 兑换码同一条,不造新轮子。
| 阶段 | 触发 | 邀请人 | 被邀请人 | 封顶 |
|---|---|---|---|---|
| 注册 | 被邀请人用邀请码注册成功 | +3 天 Pro | +3 天 Pro | 邀请人 N 次/月(默认 10) |
| 首充 | 被邀请人首次成功付费(任何档,含 ¥6 优惠) | +7 天 Pro | +7 天 Pro | 不封顶 |
每转化一个付费用户:邀请人累计 +10 天、被邀请人累计 +10 天。注册段是唯一「免费面」故设月度封顶;首充段真金白银自带成本,不封顶。
users.uuid 派生 8 位 base32(去除易混字符 0/O/1/I),存 users.invite_code。https://pangolin.yanmeiai.com/i/<code> → 落地页引导下载 App;App 内 deep link 自动预填邀请码。inviter_id ≠ invitee_id)。referrals.invitee_id 唯一);老账户不可事后补绑。DeviceMeta.dp_uuid 若已注册过别的账户,则关系照记但注册奖励不发(status=rejected)。INVITE_REG_MONTHLY_CAP(默认 10)次注册奖励;超出仍记关系、不发注册奖。首充奖励不受此限。users 加一列(SQLite / MySQL 两套迁移):
| 列 | 类型 | 说明 |
|---|---|---|
invite_code | varchar(16) UNIQUE | 本用户的永久邀请码;注册/首次访问邀请页时惰性生成。 |
first_paid_at | datetime NULL | 首次成功付费时刻;判「首充」+ 幂等首充奖励。 |
referrals —— 邀请关系(关系型,一对一绑定):
| 列 | 类型 | 说明 |
|---|---|---|
id | PK | |
inviter_id | bigint | 邀请人 user id(索引)。 |
invitee_id | bigint UNIQUE | 被邀请人 user id;唯一 = 一人只被绑一次。 |
device_uuid | varchar | 被邀请人注册设备 dp_uuid(设备去重取证)。 |
status | varchar | bound→reg_rewarded→paid_rewarded,或 rejected(防刷命中)。 |
reg_rewarded_at | datetime NULL | 注册段奖励发放时刻(幂等)。 |
paid_rewarded_at | datetime NULL | 首充段奖励发放时刻(幂等)。 |
created_at | datetime |
reward_claims —— 通用一次性任务领取(TG 及后续任务复用):
| 列 | 类型 | 说明 |
|---|---|---|
id | PK | |
user_id | bigint | 领取人。 |
task_key | varchar | 任务标识,如 telegram_join。 |
external_ref | varchar | 外部去重键;TG 用 telegram_id。 |
granted_days | int | 本次发放天数。 |
granted_at | datetime | |
约束:UNIQUE(user_id, task_key)(每账户每任务一次)+ UNIQUE(task_key, external_ref)(同一 telegram_id 只领一次)。 | ||
奖励发放本身落现有 subscriptions(source='invite' / 'task')+ sub_events 审计,不新增奖励台账表。
| 端点 / 钩子 | 动作 |
|---|---|
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/webhook | Telegram Bot 更新回调(secret 校验,与 pay webhook 同款外部回调):收 /start <token> → 校验成员 → 发 TG 任务 +3。详见 §5。 |
secret_token,服务端校验 X-Telegram-Bot-Api-Secret-Token 头;路径也可带 secret 段。控制面经 Cloudflare Tunnel 已是公网 HTTPS,Telegram 可达,无需开入站端口。getChatMember 返回 member / administrator / creator 视为已加入;left / kicked / 查询失败视为未加入。TG_REWARD_BOT_TOKEN(Bitwarden,不入 git)。getChatMember 查不到成员。TG_REWARD_CHANNEL(如 @pangolin_app)、TG_WEBHOOK_SECRET;向 Telegram setWebhook 指向 https://api.yanmeiai.com/tg/webhook。TG_REWARD_BOT_TOKEN 时,TG 任务卡在 App 里整卡隐藏(邀请功能不受影响,优雅降级)。邀请页(占位屏已在)升级为「邀请 + 任务」两区,数据来自 GET /v1/invite:
注册页:加「邀请码(选填)」输入框;deep link …/i/<code> 打开 App 自动预填并锁定。l10n 六语。
first_paid_at 已非空);未绑定用户首充不触发。max(到期,now)+days 语义,已是 Pro 的用户正确顺延(不缩短)。?)。# 后端 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 侧配好。