ProductReadyProductReady

用量与 Bonus 系统

Space 用量追踪、计划限制、Bonus 额度和兑换码

用量与 Bonus 系统

ProductReady 包含完整的用量追踪和 Bonus 系统:

  • 追踪资源使用 - 按 Space 追踪 AI Credits、Posts、Storage
  • 执行计划限制 - Free、Pro、Enterprise 不同配额
  • 发放 Bonus 额度 - 通过兑换码或促销活动
  • 展示用量 - Dashboard 进度条和警告提示

此系统与支付计费系统分离。它追踪用户消耗了什么,而 billing 处理如何付款


架构概览

┌─────────────────────────────────────────────────────────────┐
│                     计划配置                                 │
│  (src/config/plan-limits.ts + pricing-plans.ts)             │
└─────────────────────────────────────────────────────────────┘


┌─────────────────────────────────────────────────────────────┐
│                    数据库 Schema                             │
│  ┌──────────────┐  ┌─────────────────┐  ┌────────────────┐  │
│  │ spaceUsage   │  │ spaceBonusUsage │  │ bonusTemplates │  │
│  │ (用量追踪)   │  │ (Bonus 额度)    │  │ (管理员配置)   │  │
│  └──────────────┘  └─────────────────┘  └────────────────┘  │
└─────────────────────────────────────────────────────────────┘


┌─────────────────────────────────────────────────────────────┐
│                    服务层                                    │
│  ┌──────────────────┐  ┌─────────────────────────────────┐  │
│  │ usage-service.ts │  │ bonus-service.ts                │  │
│  │ - getUsageSummary│  │ - grantBonusFromTemplate        │  │
│  │ - consumeUsage   │  │ - grantManualBonus              │  │
│  └──────────────────┘  └─────────────────────────────────┘  │
└─────────────────────────────────────────────────────────────┘

计划配置

计划限制

src/config/plan-limits.ts 定义各计划的资源限制:

export type UsageType = "ai_credits" | "posts" | "storage";
export type PeriodType = "monthly" | "total";

export interface PlanLimits {
  aiCredits: UsageLimit;
  posts: UsageLimit;
  storage: UsageLimit;
  openApi: FeatureFlag;
}

const MB = 1024 * 1024;
const GB = MB * 1024;
export const UNLIMITED = -1;

export const PLAN_LIMITS: Record<PlanKey, PlanLimits> = {
  Free: {
    aiCredits: { limit: 50, period: "monthly", label: "AI Credits" },
    posts: { limit: 100, period: "total", label: "Posts" },
    storage: { limit: 100 * MB, period: "total", label: "Storage" },
    openApi: { enabled: false, label: "OpenAPI Access" },
  },
  Pro: {
    aiCredits: { limit: 500, period: "monthly", label: "AI Credits" },
    posts: { limit: 1000, period: "total", label: "Posts" },
    storage: { limit: 10 * GB, period: "total", label: "Storage" },
    openApi: { enabled: true, label: "OpenAPI Access" },
  },
  Enterprise: {
    aiCredits: { limit: UNLIMITED, period: "monthly", label: "AI Credits" },
    posts: { limit: UNLIMITED, period: "total", label: "Posts" },
    storage: { limit: UNLIMITED, period: "total", label: "Storage" },
    openApi: { enabled: true, label: "OpenAPI Access" },
  },
};

数据库 Schema

Space 用量表

export const spaceUsage = pgTable("space_usage", {
  spaceId: text("space_id").primaryKey(),
  aiCreditsUsed: integer("ai_credits_used").default(0).notNull(),
  postsUsed: integer("posts_used").default(0).notNull(),
  storageUsed: bigint("storage_used", { mode: "number" }).default(0).notNull(),
  currentPeriod: text("current_period").notNull(), // "YYYY-MM"
});

Bonus 用量表

export const spaceBonusUsage = pgTable("space_bonus_usage", {
  id: text("id").primaryKey(),
  spaceId: text("space_id").notNull(),
  type: text("type").notNull(),        // ai_credits, posts, storage
  amount: integer("amount").notNull(),  // 总 Bonus 数量
  used: integer("used").default(0),     // 已消耗数量
  source: text("source").notNull(),     // redemption, promotion, manual
  sourceName: text("source_name").notNull(),
  expiresAt: timestamp("expires_at"),   // null = 永不过期
});

Bonus 模板表

export const spaceBonusTemplates = pgTable("space_bonus_templates", {
  id: text("id").primaryKey(),
  name: text("name").notNull(),
  type: text("type").notNull(),           // ai_credits, posts, storage
  amount: integer("amount").notNull(),
  durationDays: integer("duration_days"), // null = 永不过期
  applicablePlans: text("applicable_plans").array(),
  isActive: boolean("is_active").default(true).notNull(),
});

用量服务

获取用量摘要

import { getUsageSummary } from "~/lib/billing";

const summary = await getUsageSummary(spaceId);
// 返回:
// {
//   plan: "Pro",
//   items: [
//     { type: "ai_credits", used: 150, limit: 1100, percentage: 13.6, isWarning: false },
//   ],
// }

消耗用量

import { consumeUsage } from "~/lib/billing";

const result = await consumeUsage(spaceId, "ai_credits", 10);
// 返回: { success: true, remaining: 90 }
// 或: { success: false, error: "quota_exhausted" }

消耗优先级

  1. Bonus 额度优先(最早过期的先用)
  2. 计划配额(Bonus 用完后)
  3. 拒绝(所有配额用完)

兑换码系统

兑换码支持多态奖励

类型描述
plan升级 Space 到指定计划
bonus从模板发放 Bonus 额度

创建兑换码(管理员)

await trpc.redemptionCodes.create.mutate({
  code: "PROMO2024",
  rewardType: "plan",
  planType: "Pro",
  durationDays: 30,
  maxRedemptions: 100,
});
await trpc.redemptionCodes.create.mutate({
  code: "BONUS500",
  rewardType: "bonus",
  bonusTemplateId: "tmpl_500_credits",
  durationDays: 60,
  maxRedemptions: 50,
});

兑换码(用户)

const result = await trpc.redemptionCodes.redeem.mutate({
  code: "PROMO2024",
  spaceId: "space_123",
});

给最终用户的兑换指南

作为开发者,你可以使用以下模板来指导用户完成兑换流程。只需复制并自定义下方的消息即可。

当向用户分发兑换码时,你可以使用这些可复制粘贴的模板:

模板 1:计划升级码

您好 [用户名],

感谢您对升级工作空间的兴趣!这是您的专属兑换码:

🎁 兑换码:[您的兑换码]

兑换步骤,升级到 [Pro/Enterprise] 计划:

1. 访问:https://yourapp.com/redeem
2. 登录您的账号(新用户需要先注册)
3. 输入兑换码:[您的兑换码]
4. 选择要升级的工作空间
5. 点击"兑换码"按钮

您的工作空间将立即升级!此兑换码有效期[30天 / 至 年/月/日 / 永久]。

如有任何问题,欢迎联系我们的客服团队。

祝好,
[您的团队名称]

模板 2:Bonus 额度码

您好 [用户名],

好消息!我们为您准备了 Bonus 额度,让您体验我们的高级功能。

🎁 Bonus 码:[您的兑换码]

领取步骤:

1. 访问 https://yourapp.com/redeem
2. 登录您的账号
3. 输入兑换码:[您的兑换码]
4. 选择您的工作空间
5. 点击"兑换码"

您将立即获得 [X] 个 AI Credits / Posts / 存储空间,可使用 [期限]。

尽情探索我们的平台吧!

[您的团队名称]

模板 3:简洁邮件格式

主题:您的兑换码已就绪!🎁

您好 [用户名],

您的兑换码:[您的兑换码]

兑换地址:https://yourapp.com/redeem

此兑换码将[升级您的计划至 Pro / 授予您 X 个 Bonus 额度]。

有疑问?请回复此邮件。

谢谢,
[您的团队]

兑换页面功能

用户在 /redeem 页面将看到友好的界面,包括:

  • 自动识别兑换码:如果您分享链接 https://yourapp.com/redeem?code=PROMO2024,兑换码会自动填充
  • 登录保护:用户必须登录后才能兑换
  • 工作空间选择:用户可以选择将兑换码应用到哪个工作空间
  • 即时确认:显示成功消息和升级/Bonus 详情
  • 当前计划可见性:用户在兑换前可以看到当前计划

分发最佳实践

  1. 使用直链:分享 https://yourapp.com/redeem?code=您的兑换码 来预填兑换码
  2. 设定预期:告诉用户他们将获得什么(计划升级、额度数量、有效期)
  3. 提供支持:附上客服联系方式以解决兑换问题
  4. 追踪兑换:在管理面板 /systemadmin/redemption-codes 监控兑换率
  5. 设置过期时间:使用限时兑换码营造紧迫感

UI 组件

用量面板

import { UsagePanel } from "~/components/dashboard/usage-panel";

<UsagePanel spaceId={spaceId} />

Bonus 表格

import { BonusTable } from "~/components/dashboard/bonus-table";

<BonusTable spaceId={spaceId} />

管理员页面

Bonus 模板管理

路径: /systemadmin/bonus-templates

功能:

  • 创建/编辑/删除 Bonus 模板
  • 设置数量、有效期、适用计划
  • 切换启用状态

兑换码管理

路径: /systemadmin/redemption-codes

功能:

  • 创建单个或批量兑换码
  • 选择奖励类型(计划或 Bonus)
  • 查看兑换历史
  • 导出 CSV

最佳实践

1. 操作前检查用量

async function generateContent(spaceId: string) {
  const result = await consumeUsage(spaceId, "ai_credits", 1);
  if (!result.success) {
    throw new Error("AI 额度已用完,请升级计划。");
  }
  // 继续生成...
}

2. 80% 时显示警告

{item.isWarning && (
  <Alert variant="warning">
    您已使用 {item.percentage}% 的 {item.label}。
    考虑升级您的计划。
  </Alert>
)}

3. 通过 Bonus 激励用户

// 用户完成引导后发放 Bonus
await grantPromotionalBonus(
  spaceId,
  "ai_credits",
  50,
  "完成引导奖励",
  30 // 30 天有效期
);

On this page