Referral Reward System 设计指南:SaaS 技术实现完整教程

全面指南,涵盖推荐奖励系统的设计与实现、奖励结构、邀请码生成、信用引擎架构、防欺诈策略和数据库 Schema。

Maya Patel
Maya Patel
June 13, 202613 min read
其他语言:Deutsch · English

TL;DR: 设计良好的推荐项目是 SaaS 产品中投资回报率最高的获客渠道,被推荐客户的留存率通常高出 25-30%,终身价值(LTV)高出 16%。本指南涵盖完整实现方案——从奖励结构心理学、邀请码生成,到信用引擎架构、防欺诈策略和数据分析。包含可直接用于生产环境的数据库 Schema 和基于 TanStack Start + Cloudflare D1 的服务端代码。


推荐项目为何有效(数据支撑)

推荐营销不仅仅是一种增长手段——它本质上是一种信任机制。人们对同龄人推荐的信任度远高于其他任何营销形式:

渠道信任率转化率平均 LTV数据来源
朋友推荐92%10-30%+16% 更高Nielsen
在线评价70%3-10%基准值BrightLocal
付费广告25%0.5-2%-20% 更低Meta Ads

对于 SaaS 来说,被推荐客户的流失率低 25-30%,终身价值比自然获客客户高出 16%(Deloitte)。原因如下:

  1. 预筛选的潜在客户:推荐人只会邀请真正能从中受益的人
  2. 社交式上手:新用户有一位内置的"伙伴"帮助他们起步
  3. 网络效应:团队或组织中加入的人越多,黏性就越强

奖励结构:什么有效,什么无效

双侧奖励模型

最有效的 SaaS 推荐项目会同时奖励推荐人和被推荐用户:

模型推荐人获得被推荐人获得代表公司
双侧折扣1 个月免费首 3 个月 8 折Dropbox、Airbnb
双侧积分$50 账户积分$25 账户积分Uber、Robinhood
单侧奖励1 个月免费无(效果较差)
公益捐赠捐 $5 给慈善捐 $5 给慈善TOMS、Warby Parker(B2C)

确定奖励金额

黄金法则:奖励金额应相当于 1 个月订阅费用的 25-50%。

以 $29/月的 SaaS 为例:
  - 推荐人奖励:$7.25 - $14.50 积分(1-2 周免费)
  - 被推荐人奖励:$7.25 - $14.50 积分

以 $99/月的 SaaS 为例:
  - 推荐人奖励:$25 - $50 积分
  - 被推荐人奖励:$25 - $50 积分

积分 vs. 折扣 vs. 现金

奖励类型优点缺点最适合场景
服务积分成本低(零边际成本),鼓励持续使用用户可能不需要更多积分按用量计费的 SaaS
订阅折扣直接降低流失壁垒与账单周期对接复杂月订阅 SaaS
现金/PayPal激励效果最强成本高,体感像交易企业级 SaaS
礼品卡管理简单感知价值低于现金B2C/B2B 混合
功能解锁零成本,感知价值高仅适用于免费增值模式免费增值产品

推荐系统的数据库 Schema

结构良好的数据库是任何推荐系统的基础。以下 Schema 支持可配置奖励的邀请码、兑换追踪和完整的积分账本:

sql
-- 表 1:邀请码(由推荐人生成)
CREATE TABLE invite_codes (
  id TEXT PRIMARY KEY,
  code TEXT NOT NULL UNIQUE, -- 例如 'FRIEND-ABC123'
  creator_id TEXT NOT NULL, -- 创建此码的用户
  max_uses INTEGER DEFAULT 10,
  use_count INTEGER NOT NULL DEFAULT 0,
  reward_type TEXT NOT NULL DEFAULT 'credit' CHECK (
    reward_type IN ('credit', 'discount_percent', 'discount_fixed', 'month_free')
  ),
  reward_value INTEGER NOT NULL DEFAULT 500, -- 以分为单位或百分比点数
  is_active INTEGER NOT NULL DEFAULT 1,
  expires_at INTEGER, -- 可选的过期时间
  created_at INTEGER NOT NULL DEFAULT (unixepoch()),
  FOREIGN KEY (creator_id) REFERENCES users(id)
);

CREATE INDEX idx_invite_codes_code ON invite_codes(code);
CREATE INDEX idx_invite_codes_creator ON invite_codes(creator_id);

-- 表 2:兑换记录
CREATE TABLE invite_redemptions (
  id TEXT PRIMARY KEY,
  invite_code_id TEXT NOT NULL,
  referrer_id TEXT NOT NULL, -- 分享码的人
  referred_user_id TEXT NOT NULL, -- 使用该码的新用户
  reward_referrer INTEGER NOT NULL DEFAULT 0, -- 授予推荐人的积分(分)
  reward_referred INTEGER NOT NULL DEFAULT 0, -- 授予被推荐人的积分(分)
  status TEXT NOT NULL DEFAULT 'pending' CHECK (
    status IN ('pending', 'completed', 'expired', 'fraudulent')
  ),
  created_at INTEGER NOT NULL DEFAULT (unixepoch()),
  completed_at INTEGER, -- 条件达成时的时间(例如被推荐人付款)
  FOREIGN KEY (invite_code_id) REFERENCES invite_codes(id),
  FOREIGN KEY (referrer_id) REFERENCES users(id),
  FOREIGN KEY (referred_user_id) REFERENCES users(id)
);

CREATE INDEX idx_invite_redemptions_referrer ON invite_redemptions(referrer_id);
CREATE INDEX idx_invite_redemptions_referred ON invite_redemptions(referred_user_id);

-- 表 3:积分账本(基于积分的奖励)
CREATE TABLE credit_ledger (
  id TEXT PRIMARY KEY,
  user_id TEXT NOT NULL,
  amount_cents INTEGER NOT NULL, -- 正数为积分,负数为消费
  balance_after_cents INTEGER NOT NULL,
  reason TEXT NOT NULL CHECK (
    reason IN (
      'referral_reward', 'referral_signup_bonus',
      'credit_purchase', 'subscription_payment',
      'admin_adjustment', 'expired'
    )
  ),
  reference_id TEXT, -- 关联 invite_redemptions 或 invoice
  created_at INTEGER NOT NULL DEFAULT (unixepoch()),
  FOREIGN KEY (user_id) REFERENCES users(id)
);

CREATE INDEX idx_credit_ledger_user ON credit_ledger(user_id);

-- 视图:每位用户的推荐统计
CREATE VIEW referral_stats AS
SELECT
  ic.creator_id as user_id,
  COUNT(DISTINCT ir.id) as total_referrals,
  COUNT(DISTINCT CASE WHEN ir.status = 'completed' THEN ir.id END) as completed_referrals,
  COALESCE(SUM(CASE WHEN ir.status = 'completed' THEN ir.reward_referrer END), 0) as total_rewards_earned,
  ROUND(AVG(CASE WHEN ir.status = 'completed' THEN ir.reward_referrer END), 0) as avg_reward_per_referral
FROM invite_codes ic
LEFT JOIN invite_redemptions ir ON ir.invite_code_id = ic.id
GROUP BY ic.creator_id;

邀请码生成

邀请码需要唯一、方便人类阅读,同时防止被猜测。以下工具函数使用不含歧义字符的字符集来生成码,并通过服务端函数创建:

typescript
// src/lib/invite/codes.ts
import { createServerFn } from "@tanstack/react-start"

// 安全、方便人类阅读的邀请码生成
export function generateInviteCode(length: number = 8): string {
  // 使用避免歧义字符的字符集
  const chars = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789" // 排除 I、O、0、1
  const prefix = "FRIEND-"
  const random = Array.from({ length }, () =>
    chars[Math.floor(Math.random() * chars.length)]
  ).join("")
  return `${prefix}${random}`
}

export const createInviteCode = createServerFn({ method: "POST" }).handler(
  async (_, { request }) => {
    const userId = await getUserId(request)
    const code = generateInviteCode()

    // 确保唯一性(碰撞概率可忽略但仍检查)
    const existing = await env.DB.prepare(
      "SELECT id FROM invite_codes WHERE code = ?"
    ).bind(code).first()

    if (existing) {
      return createInviteCode(_, { request }) // 重试
    }

    // 默认奖励:双方各获得 500 分($5.00)账户积分
    await env.DB.prepare(
      `INSERT INTO invite_codes (id, code, creator_id, reward_value)
       VALUES (?, ?, ?, 500)`
    ).bind(crypto.randomUUID(), code, userId).run()

    return { code, shareUrl: `https://tanstackship.com/invite/${code}` }
  }
)

推荐流程实现

步骤 1:分享推荐链接

typescript
// src/components/invite/ShareInvite.tsx
import { useMutation } from "@tanstack/react-query"
import { createInviteCode } from "../../lib/invite/codes"

export function ShareInvite() {
  const mutation = useMutation({
    mutationFn: () => createInviteCode(),
  })

  const shareUrl = mutation.data?.shareUrl ?? ""

  return (
    <div className="p-6 border rounded-lg">
      <h2 className="text-xl font-bold mb-4">邀请朋友,赚取积分</h2>
      <p className="text-gray-600 mb-4">
        分享你的邀请链接,朋友注册即可获得 $5
      </p>

      <button
        onClick={() => mutation.mutate()}
        className="bg-blue-600 text-white px-6 py-2 rounded-lg"
        disabled={mutation.isPending}
      >
        {mutation.isPending ? "生成中..." : "获取你的推荐链接"}
      </button>

      {shareUrl && (
        <div className="mt-4">
          <label className="block text-sm font-medium mb-1">
            你的推荐链接
          </label>
          <div className="flex gap-2">
            <input
              type="text"
              value={shareUrl}
              readOnly
              className="flex-1 px-3 py-2 border rounded"
            />
            <button
              onClick={() => navigator.clipboard.writeText(shareUrl)}
              className="px-4 py-2 bg-gray-100 rounded hover:bg-gray-200"
            >
              复制
            </button>
          </div>
        </div>
      )}
    </div>
  )
}

步骤 2:注册时兑换邀请码

typescript
// src/lib/invite/redeem.ts
import { createServerFn } from "@tanstack/react-start"

export const redeemInviteCode = createServerFn({ method: "POST" }).handler(
  async ({ code, newUserId }: { code: string; newUserId: string }) => {
    // 验证邀请码
    const invite = await env.DB.prepare(
      `SELECT ic.*, u.email as creator_email
       FROM invite_codes ic
       JOIN users u ON u.id = ic.creator_id
       WHERE ic.code = ? AND ic.is_active = 1
       AND (ic.expires_at IS NULL OR ic.expires_at > unixepoch())
       AND ic.use_count < ic.max_uses`
    ).bind(code).first()

    if (!invite) {
      return { success: false, error: "邀请码无效或已过期" }
    }

    // 防止自我推荐
    if (invite.creator_id === newUserId) {
      return { success: false, error: "不能使用自己的邀请码" }
    }

    const now = Math.floor(Date.now() / 1000)
    const redemptionId = crypto.randomUUID()

    // 创建兑换记录(条件达成前状态为 pending)
    await env.DB.prepare(
      `INSERT INTO invite_redemptions
        (id, invite_code_id, referrer_id, referred_user_id,
         reward_referrer, reward_referred, status, created_at)
       VALUES (?, ?, ?, ?, ?, ?, 'pending', ?)`
    ).bind(
      redemptionId,
      invite.id,
      invite.creator_id,
      newUserId,
      invite.reward_value, // 推荐人获得奖励
      invite.reward_value, // 被推荐人获得奖励
      now
    ).run()

    // 增加邀请码使用次数
    await env.DB.prepare(
      `UPDATE invite_codes SET use_count = use_count + 1 WHERE id = ?`
    ).bind(invite.id).run()

    // 立即授予被推荐人积分(注册奖励)
    await awardCredit(newUserId, invite.reward_value, "referral_signup_bonus", redemptionId)

    return { success: true, bonusAmount: invite.reward_value }
  }
)

步骤 3:条件达成时发放推荐人奖励

typescript
// src/lib/invite/rewards.ts
// 当被推荐用户完成首次付款时触发
export const processReferralReward = createServerFn({ method: "POST" }).handler(
  async ({ referredUserId }: { referredUserId: string }) => {
    const pendingRedemption = await env.DB.prepare(
      `SELECT ir.*, ic.reward_type
       FROM invite_redemptions ir
       JOIN invite_codes ic ON ic.id = ir.invite_code_id
       WHERE ir.referred_user_id = ? AND ir.status = 'pending'`
    ).bind(referredUserId).first()

    if (!pendingRedemption) return { processed: false }

    // 授予推荐人积分
    await awardCredit(
      pendingRedemption.referrer_id,
      pendingRedemption.reward_referrer,
      "referral_reward",
      pendingRedemption.id
    )

    // 标记为已完成
    await env.DB.prepare(
      `UPDATE invite_redemptions SET status = 'completed', completed_at = unixepoch()
       WHERE id = ?`
    ).bind(pendingRedemption.id).run()

    return { processed: true, amount: pendingRedemption.reward_referrer }
  }
)

信用引擎架构

一个稳健的积分系统,处理推荐奖励、订阅支付和手动调整:

typescript
// src/lib/credit/engine.ts

export async function awardCredit(
  userId: string,
  amountCents: number,
  reason: CreditLedger["reason"],
  referenceId: string
) {
  const currentBalance = await getCreditBalance(userId)
  const newBalance = currentBalance + amountCents

  await env.DB.prepare(
    `INSERT INTO credit_ledger (id, user_id, amount_cents, balance_after_cents, reason, reference_id)
     VALUES (?, ?, ?, ?, ?, ?)`
  ).bind(
    crypto.randomUUID(),
    userId,
    amountCents,
    newBalance,
    reason,
    referenceId
  ).run()

  return newBalance
}

export async function spendCredit(
  userId: string,
  amountCents: number,
  referenceId: string
): Promise<boolean> {
  const balance = await getCreditBalance(userId)
  if (balance < amountCents) return false // 积分不足

  const newBalance = balance - amountCents

  await env.DB.prepare(
    `INSERT INTO credit_ledger (id, user_id, amount_cents, balance_after_cents, reason, reference_id)
     VALUES (?, ?, ?, ?, 'subscription_payment', ?)`
  ).bind(
    crypto.randomUUID(),
    userId,
    -amountCents,
    newBalance,
    referenceId
  ).run()

  return true
}

export async function getCreditBalance(userId: string): Promise<number> {
  const result = await env.DB.prepare(
    `SELECT balance_after_cents
     FROM credit_ledger
     WHERE user_id = ?
     ORDER BY created_at DESC
     LIMIT 1`
  ).bind(userId).first()

  return (result?.balance_after_cents as number) ?? 0
}

防欺诈策略

推荐欺诈是一个真实存在的问题。以下是多层防御体系:

typescript
// src/lib/invite/fraud-detection.ts
import { createServerFn } from "@tanstack/react-start"

export const validateReferral = createServerFn({ method: "POST" }).handler(
  async ({ referrerId, refereeId, ipAddress }: {
    referrerId: string
    refereeId: string
    ipAddress: string
  }) => {
    const checks = await Promise.all([
      // 检查 1:同一 IP 检测
      env.DB.prepare(
        `SELECT COUNT(*) as count FROM users
         WHERE ip_address = ? AND id != ?`
      ).bind(ipAddress, refereeId).first(),

      // 检查 2:推荐速度(过去 24 小时内多少个)
      env.DB.prepare(
        `SELECT COUNT(*) as count FROM invite_redemptions
         WHERE referrer_id = ? AND created_at > unixepoch() - 86400`
      ).bind(referrerId).first(),

      // 检查 3:相同设备指纹(如果有的话)
      // 实现取决于你的指纹方案
    ])

    const [ipCheck, velocityCheck] = (checks as [{ count: number }, { count: number }])

    if (ipCheck.count > 3) {
      // 标记审查 — 同一 IP 下不同用户过多
      await flagForReview(referrerId, "referral_fraud_same_ip")
      return { valid: false, reason: "suspicious_activity" }
    }

    if (velocityCheck.count > 10) {
      // 24 小时内超过 10 次推荐对大多数 SaaS 来说很可疑
      await flagForReview(referrerId, "referral_fraud_high_velocity")
      return { valid: false, reason: "rate_limited" }
    }

    return { valid: true }
  }
)

推荐项目数据分析

追踪推荐漏斗指标有助于持续优化项目。以下 SQL 查询提供推荐健康的完整视图:

sql
-- 监控推荐项目健康的仪表板查询

-- 1. 推荐漏斗
SELECT
  COUNT(DISTINCT ic.creator_id) as users_with_codes,
  COUNT(DISTINCT ir.referred_user_id) as users_who_clicked,
  COUNT(DISTINCT CASE WHEN ir.status = 'completed' THEN ir.referred_user_id END) as converted_referrals,
  COUNT(DISTINCT CASE WHEN s.status = 'active' THEN ir.referred_user_id END) as retained_referrals
FROM invite_codes ic
LEFT JOIN invite_redemptions ir ON ir.invite_code_id = ic.id
LEFT JOIN subscriptions s ON s.user_id = ir.referred_user_id AND s.status = 'active'

-- 2. 推荐排行榜
SELECT
  u.email,
  COUNT(ir.id) as referrals_sent,
  COUNT(CASE WHEN ir.status = 'completed' THEN 1 END) as referrals_completed,
  SUM(CASE WHEN ir.status = 'completed' THEN ir.reward_referrer END) / 100.0 as rewards_earned
FROM users u
JOIN invite_codes ic ON ic.creator_id = u.id
LEFT JOIN invite_redemptions ir ON ir.invite_code_id = ic.id
GROUP BY u.id
ORDER BY referrals_completed DESC
LIMIT 25

-- 3. 推荐 vs. 自然获客留存对比
SELECT
  CASE WHEN ir.id IS NOT NULL THEN 'referred' ELSE 'organic' END as acquisition_channel,
  COUNT(DISTINCT u.id) as total_users,
  COUNT(DISTINCT CASE WHEN s.status = 'active' THEN u.id END) as active_users,
  ROUND(AVG(s.mrr), 2) as avg_mrr,
  ROUND(AVG(s.created_at - u.created_at) / 86400, 0) as avg_days_to_churn_or_now
FROM users u
LEFT JOIN subscriptions s ON s.user_id = u.id
LEFT JOIN invite_redemptions ir ON ir.referred_user_id = u.id
GROUP BY acquisition_channel

推荐项目优化清单

  • 奖励结构为双侧(推荐人和被推荐人均受益)
  • 奖励金额为 1 个月订阅费用的 25-50%
  • 邀请码易于分享(URL、复制按钮、邮件、社交平台)
  • 已建立欺诈检查(同 IP、速度限制、自我推荐)
  • 积分系统正确处理并发消费
  • 推荐人奖励在条件达成后才释放(非立即发放)
  • 推荐数据分析仪表板展示漏斗和 ROI
  • 朋友注册后向推荐人发送邮件通知
  • 奖励到账后向推荐人发送邮件通知
  • 已建立 A/B 测试框架用于奖励金额实验
  • 服务条款涵盖推荐欺诈和奖励撤销
  • 自我推荐检测防止用户刷单

总结

推荐项目不是"设置好就不用管"的增长渠道——它需要精心设计、实施和持续优化。核心原则如下:

  1. 双向奖励——推荐人和被推荐人都应感到自己是赢家
  2. 延迟发放推荐人奖励——直到被推荐人采取了有价值的行动(付款、激活)才发放,以防止欺诈
  3. 优先建设积分基础设施——能处理今日推荐奖励的积分引擎,明天也能处理客服积分、测试用户奖励和促销活动
  4. 持续监控欺诈——缺乏适当管控时,增长渠道会迅速变成成本中心
  5. 衡量推荐 LTV vs. 自然 LTV——如果被推荐客户没有更高的价值,需要调整奖励结构或目标人群

做得好,推荐就会成为你最高质量、最低成本的获客渠道——并把你的客户变成最有效的销售团队。

将推荐项目纳入更广泛增长策略,请参阅我们的 SaaS 增长框架:从零到百、UTM 归因完整指南 用于追踪推荐来源、SaaS 定价心理学 用于奖励优化,以及 SaaS 邮件营销自动化 用于推荐通知流程。