cc REFERRAL ATTRIBUTION IMPLEMENTATION
本地来源:Knowledge/World/项目/文档/referral/cc-REFERRAL_ATTRIBUTION_IMPLEMENTATION.md
用户来源追踪与邀请奖励系统 - 完整实施文档
文档版本:v1.1 创建日期:2025-01-09 更新日期:2026-01-09 适用项目:seedvr2.net (Next.js 15 + Drizzle ORM + PostgreSQL) 支付系统:Stripe + Creem 双支付
目录
一、系统架构总览
1.1 三层追踪体系
``` ┌─────────────────────────────────────────────────────────────┐ │ 用户来源追踪体系 │ ├─────────────────────────────────────────────────────────────┤ │ │ │ ┌─────────────────┐ ┌─────────────────┐ ┌───────────┐ │ │ │ P0. UTM 归因 │ │ P1. 邀请奖励 │ │ P2. 广告 │ │ │ │ (数据分析基础) │ │ (用户增长) │ │ 回传 │ │ │ ├─────────────────┤ ├─────────────────┤ ├───────────┤ │ │ │ ?utm_source= │ │ ?invite=ABC123 │ │ gclid │ │ │ │ google │ │ │ │ fbclid │ │ │ │ ?utm_campaign= │ │ 注册:双方+基础奖励 │ │ │ │ │ │ summer_sale │ │ 首单:仅邀请人10%│ │ 付费后 │ │ │ │ │ │ 上限:1000 │ │ 回传转化 │ │ │ ├─────────────────┤ ├─────────────────┤ ├───────────┤ │ │ │ ✅ 自建 │ │ ✅ 自建 │ │ 需要调用 │ │ │ │ ❌ 不需要Consent │ │ ❌ 不需要Consent │ │ Meta/Google│ │ │ │ │ │ │ │ API │ │ │ │ 存储:Cookie │ │ 存储:数据库 │ │ ✅ 需要 │ │ │ │ │ │ │ │ Consent │ │ │ └─────────────────┘ └─────────────────┘ └───────────┘ │ │ │ │ IP地理定位方案:Cloudflare Headers(免费,0ms延迟) │ │ 支付系统:Stripe + Creem(两边都要处理邀请奖励) │ └─────────────────────────────────────────────────────────────┘ ```
1.2 数据流向
``` 用户访问(携带 ?invite=xxx 或 ?utm_source=xxx) ↓ ┌──────────────────────────────────────┐ │ Middleware │ │ 1. 提取 UTM 参数 → Cookie │ │ 2. 提取 invite 参数 → Cookie │ │ 3. 提取 Click IDs → Cookie │ │ 4. 获取 Cloudflare Geo Headers │ └──────────────────────────────────────┘ ↓ 用户注册(检查 invite Cookie) ↓ ┌──────────────────────────────────────┐ │ 注册时处理(auth.ts) │ │ 1. 验证邀请码有效性 │ │ 2. 创建 referral 记录 │ │ 3. 等待邮箱验证 │ └──────────────────────────────────────┘ ↓ 用户首单支付(Stripe/Creem Webhook) ↓ ┌──────────────────────────────────────┐ │ 支付成功时处理 │ │ 1. 检查是否为首单 │ │ 2. 检查是否有邀请关系 │ │ 3. 计算奖励积分(订单积分×10%) │ │ 4. 仅邀请人发放积分 │ │ 5. 更新 referral 记录 │ │ 6. [P2] 触发广告回传 │ └──────────────────────────────────────┘ ```
1.3 项目技术栈(seedvr2.net 实际配置)
| 组件 | 技术 | 说明 |
|---|---|---|
| 框架 | Next.js 15 | App Router |
| 数据库 | PostgreSQL + Drizzle ORM | Neon 托管,使用 `drizzle` schema |
| 认证 | Better Auth | 支持 Google/GitHub 登录 |
| 支付 | Stripe + Creem | 双支付系统 |
| 积分系统 | 已有完整实现 | `src/credits/credits.ts` |
| 积分类型 | enum CREDIT_TRANSACTION_TYPE | `src/credits/types.ts` |
| 部署 | Vercel | 通过 Cloudflare CDN |
| ID 类型 | uuid | 所有表的 ID 都是 uuid 类型 |
二、优先级P0:UTM归因采集
2.1 功能说明
| 项目 | 值 |
|---|---|
| 目的 | 知道用户从哪个渠道来(分析用) |
| 数据存储 | Cookie + payment.metadata |
| 是否需要数据库表 | ❌ 不需要(存 Cookie 够用) |
| 是否需要Consent | ❌ 不需要(纯内部分析) |
2.2 采集的参数
```typescript // UTM 参数 const UTM_PARAMS = [ 'utm_source', // 来源(google, facebook, twitter...) 'utm_medium', // 媒介(cpc, organic, email, social...) 'utm_campaign', // 活动名称(summer_sale, black_friday...) 'utm_term', // 关键词(仅付费搜索用) 'utm_content', // 内容变体(banner_v1, text_link...) ] as const;
// 广告平台 Click IDs const CLICK_IDS = [ 'gclid', // Google Ads(90天有效) 'gbraid', // Google Ads iOS(30天有效) 'wbraid', // Google Ads Web-to-App(30天有效) 'fbclid', // Meta/Facebook(7天有效) 'msclkid', // Microsoft Ads(90天有效) 'ttclid', // TikTok Ads(28天有效) ] as const; ```
2.3 Cookie 设计
| Cookie 名 | 用途 | 有效期 | HttpOnly | 说明 |
|---|---|---|---|---|
| `attr_first` | 首次触点 | 1年 | ✅ | 永不覆盖 |
| `attr_last` | 末次触点 | 30分钟 | ✅ | 每次有新数据时更新 |
2.4 Cookie 数据结构
```typescript interface AttributionData { utm: { utm_source?: string; utm_medium?: string; utm_campaign?: string; utm_term?: string; utm_content?: string; }; clickIds: { gclid?: string; fbclid?: string; msclkid?: string; // ... }; referrer: string | null; // 来源页面 referrerType: 'direct' | 'organic' | 'social' | 'referral' | 'paid'; landingPage: string; // 落地页路径 timestamp: number; // 时间戳 geo?: { country?: string; // 从 Cloudflare Headers 获取 city?: string; }; } ```
2.5 IP 地理定位方案
采用方案:Cloudflare Headers(免费,0ms延迟)
```typescript // 在 Middleware 中读取 const ip = request.headers.get('cf-connecting-ip') || request.headers.get('x-forwarded-for')?.split(',')[0] || request.headers.get('x-real-ip');
const geo = { country: request.headers.get('cf-ipcountry') // 如: CN, US || request.headers.get('x-vercel-ip-country'), city: request.headers.get('cf-ipcity') // 如: Shanghai || request.headers.get('x-vercel-ip-city'), }; ```
| 方案 | 成本 | 延迟 | 推荐度 |
|---|---|---|---|
| Cloudflare Headers | 免费 | 0ms | ⭐⭐⭐⭐⭐ |
| Vercel Geo Headers | Pro计划免费 | 0ms | ⭐⭐⭐⭐ |
| MaxMind API | 按量付费 | +50ms | ⭐⭐ |
三、优先级P1:邀请奖励系统
3.1 功能概述
| 项目 | 值 |
|---|---|
| 目的 | 用户增长 + 用户福利 |
| 奖励形式 | 积分(站内货币,不涉及提现) |
| 层级 | 单层(A 邀请 B,B 再邀请 C 时 A 不获益) |
| 是否需要第三方 | ❌ 完全自建 |
| 是否需要Consent | ❌ 不需要 |
| 裂变支持 | ✅ 被邀请人可以成为邀请人 |
3.2 奖励规则(最终确认版)
注册奖励
| 角色 | 正常注册 | 被邀请注册 | 说明 |
|---|---|---|---|
| 邀请人(A) | - | +基础奖励 | 额外奖励(= INITIAL_CREDITS_AMOUNT) |
| 被邀请人(B) | 基础奖励 | 基础奖励 + 基础奖励 = 2×基础奖励 | 基础 + 额外 |
基础奖励基数读取 `INITIAL_CREDITS_AMOUNT`(只读配置)。
发放条件:被邀请人完成邮箱验证后发放
首单奖励
| 角色 | 奖励 | 计算公式 | 示例 |
|---|---|---|---|
| 邀请人(A) | 订单积分 × 10% | `Math.floor(orderCredits * 0.1)` | 订单300积分 → +30 |
| 被邀请人(B) | ❌ 无首单奖励 | - | - |
重要规则: - ✅ 仅首单有奖励,续费不算 - ✅ 订阅和积分包购买都算首单 - ✅ 10% 向下取整(如 55 积分订单 → 5 积分奖励) - ✅ 单次上限 1,000 积分 - ✅ 奖励积分 30 天过期(和正常积分一样) - ✅ 首单奖励仅给邀请人,被邀请人无此奖励
3.3 邀请码设计
| 配置项 | 值 | 说明 |
|---|---|---|
| 格式 | `u_` + 8位哈希 | 如 `u_a1b2c3d4` |
| 字符集 | `a-z0-9` | 小写字母 + 数字 |
| 生成时机 | 用户注册时自动生成 | 每个用户都有 |
| 唯一性 | 数据库 unique 约束 | 冲突时重新生成 |
邀请码生成函数
```typescript // src/lib/referral.ts
function generateInviteCode(userId: string): string { // 方案:u_ + 用户ID的短哈希 const hash = crypto .createHash('md5') .update(userId + Date.now().toString()) .digest('hex') .slice(0, 8); return `u_\${hash}`; } ```
3.4 邀请链接设计
| 配置项 | 值 |
|---|---|
| URL 格式 | `https://domain.com?invite=u_a1b2c3d4` |
| 参数名 | `invite` |
| Cookie 名 | `invite_code` |
| Cookie 有效期 | 30 天 |
| 落地页 | 无专门页面,直接跳首页 |
3.5 邀请管理页面
页面路径:`/[locale]/(protected)/settings/referral`
⚠️ 注意:seedvr2.net 使用 `(protected)` 路由组,不是 `(app)`
访问权限: - 已登录用户:显示完整邀请信息和统计 - 未登录用户:显示邀请规则说明(引导注册)
五、数据库设计
5.1 新增 referral 表
```typescript // src/db/schema.ts 新增 // ⚠️ 重要:seedvr2.net 使用 drizzleSchema 命名空间 + uuid 类型(与现有 user 表一致)
// 在 schema.ts 文件顶部已有: // const drizzleSchema = pgSchema('drizzle');
export const referral = drizzleSchema.table('referral', { id: uuid('id').defaultRandom().primaryKey(),
// 邀请关系(uuid 类型与 user.id 一致) inviterId: uuid('inviter_id').notNull().references(() => user.id, { onDelete: 'cascade' }), inviteeId: uuid('invitee_id').notNull().references(() => user.id, { onDelete: 'cascade' }), inviteCode: text('invite_code').notNull(),
// 奖励统计 inviterReward: integer('inviter_reward').default(0).notNull(), inviteeReward: integer('invitee_reward').default(0).notNull(),
// 状态追踪 status: text('status').default('pending').notNull(), // pending | verified | rewarded emailVerified: boolean('email_verified').default(false).notNull(), firstOrderPaid: boolean('first_order_paid').default(false).notNull(), firstOrderId: uuid('first_order_id'),
// 防刷信息 inviteeIp: text('invitee_ip'), inviteeDevice: text('invitee_device'),
// 时间 createdAt: timestamp('created_at').defaultNow().notNull(), verifiedAt: timestamp('verified_at'), firstOrderAt: timestamp('first_order_at'), }, (table) => ({ referralInviterIdx: index('referral_inviter_idx').on(table.inviterId), referralInviteeIdx: index('referral_invitee_idx').on(table.inviteeId), referralCodeIdx: index('referral_code_idx').on(table.inviteCode), referralStatusIdx: index('referral_status_idx').on(table.status), })); ```
5.2 修改 user 表
```typescript // src/db/schema.ts 修改 user 表,添加字段 // ⚠️ 注意:seedvr2.net user 表使用 uuid 类型
export const user = drizzleSchema.table("user", { // ... 现有字段 ...
// 新增:邀请系统 inviteCode: text('invite_code').unique(), invitedBy: uuid('invited_by'), invitedAt: timestamp('invited_at'), }, (table) => ({ // ... 现有索引 ... userInviteCodeIdx: index('user_invite_code_idx').on(table.inviteCode), })); ```
5.3 新增积分类型
```typescript // src/credits/types.ts 修改 CREDIT_TRANSACTION_TYPE // ⚠️ 注意:seedvr2.net 使用 enum 形式,不是 const
export enum CREDIT_TRANSACTION_TYPE { // ... 现有类型 ...
// 新增:邀请奖励 REFERRAL_REGISTER_INVITER = 'REFERRAL_REGISTER_INVITER', REFERRAL_REGISTER_INVITEE = 'REFERRAL_REGISTER_INVITEE', REFERRAL_PURCHASE_INVITER = 'REFERRAL_PURCHASE_INVITER', } ```
六、环境变量配置
6.1 邀请奖励系统(P1)
```bash
功能开关
REFERRAL_ENABLED=true
奖励配置
REFERRAL_PURCHASE_RATE=0.10 # 首单奖励比例(10%,仅邀请人获得) REFERRAL_MAX_REWARD=1000 # 单次奖励上限 REFERRAL_REWARD_EXPIRE_DAYS=30 # 奖励积分过期天数
邀请码配置
REFERRAL_CODE_PREFIX=u_ REFERRAL_CODE_LENGTH=8
Cookie 配置
REFERRAL_COOKIE_NAME=invite_code REFERRAL_COOKIE_MAX_AGE=2592000 # 30天 ```
七、文件改动清单
7.1 新建文件
| 文件路径 | 用途 | 优先级 |
|---|---|---|
| `src/lib/referral.ts` | 邀请系统核心逻辑 | P1 |
| `src/lib/attribution.ts` | 归因数据读取工具 | P0 |
| `src/app/[locale]/(protected)/settings/referral/page.tsx` | 邀请管理页面 | P1 |
| `src/app/[locale]/(protected)/settings/referral/layout.tsx` | 页面布局 | P1 |
| `src/app/(default)/(protected)/settings/referral/page.tsx` | 默认语言版本 | P1 |
| `src/app/api/referral/validate/route.ts` | 验证邀请码 API | P1 |
7.2 修改文件
| 文件路径 | 改动内容 | 优先级 |
|---|---|---|
| `src/middleware.ts` | 添加 UTM/invite 参数捕获 | P0 |
| `src/db/schema.ts` | 添加 referral 表 + user 表新字段 | P1 |
| `src/lib/auth.ts` | 注册时处理邀请关系 | P1 |
| `src/credits/types.ts` | 添加邀请奖励积分类型(enum) | P1 |
| `src/payment/provider/stripe.ts` | 首单支付时发放邀请奖励 | P1 |
| `src/payment/provider/creem-utils.ts` | 首单支付时发放邀请奖励 | P1 |
| `src/routes.ts` | 添加 SettingsReferral 路由 | P1 |
| `messages/en.json` | 邀请页面英文文案 | P1 |
| `messages/zh.json` | 邀请页面中文文案 | P1 |
修订记录
| 版本 | 日期 | 修改内容 |
|---|---|---|
| v1.0 | 2025-01-09 | 初始版本 |
| v1.1 | 2026-01-09 | 适配 seedvr2.net:使用 drizzleSchema + uuid、enum 积分类型、(protected) 路由组 |
本文档为站内渲染。原始文件本地路径:saas/source/knowledge-world/Knowledge-World-项目-文档-referral-cc-REFERRAL_ATTRIBUTION_IMPLE-672046.md(仅本地保留,不入库不部署)