name: credits-handler description: 管理积分系统(分配、购买、使用)。在添加积分类型、配置定价或构建积分UI时使用。
积分处理器
此技能指导您完成整个积分系统,从后端配置到前端UI实现。
1. 配置
所有积分配置都位于 src/lib/credits/config.ts 中。
添加新的积分类型
-
定义类型:将新类型添加到
creditTypeSchema枚举中。export const creditTypeSchema = z.enum([ "image_generation", "video_generation", "your_new_credit_type" // 添加此项 ]); -
配置定价和元数据:向
creditsConfig添加一个条目。your_new_credit_type: { name: "新积分名称", currency: "USD", minimumAmount: 10, // 选项A:固定阶梯定价 slabs: [ { from: 1, to: 100, pricePerUnit: 0.10 }, { from: 101, to: 1000, pricePerUnit: 0.08 }, ], // 选项B:动态计算器(例如,基于用户套餐) priceCalculator: (amount, userPlan) => { // 此处逻辑 return amount * 0.1; } } -
套餐分配(可选):在
onPlanChangeCredits中定义订阅套餐时给予多少积分。
2. UI实现:购买积分
要让用户购买积分,请使用 useBuyCredits 钩子。此钩子处理价格计算(考虑套餐折扣)和生成结账URL。
关键钩子:useBuyCredits
位置:src/lib/credits/useBuyCredits.ts
用法
import useBuyCredits from "@/lib/credits/useBuyCredits";
import { PlanProvider } from "@/lib/plans/getSubscribeUrl";
const {
price, // 计算出的总价(数字 | 未定义)
isLoading, // 价格计算进行中
error, // 错误状态
getBuyCreditsUrl // 生成支付URL的函数
} = useBuyCredits(creditType, amount);
示例:构建定价卡片
以下是创建积分购买UI的模式,类似于 src/components/website/website-credits-section.tsx。
"use client";
import { useState } from "react";
import useBuyCredits from "@/lib/credits/useBuyCredits";
import { PlanProvider } from "@/lib/plans/getSubscribeUrl";
import { Button } from "@/components/ui/button";
import { Loader2 } from "lucide-react";
// 1. 定义您的套餐包
const PACKAGE = {
credits: 100,
name: "入门包",
};
export function BuyCreditsCard({ creditType }: { creditType: "image_generation" }) {
const [provider] = useState(PlanProvider.STRIPE); // 或 LEMONSQUEEZY
// 2. 调用钩子
const { price, isLoading, error, getBuyCreditsUrl } = useBuyCredits(
creditType,
PACKAGE.credits
);
// 3. 处理购买
const handleBuy = () => {
const url = getBuyCreditsUrl(provider);
window.location.href = url;
};
// 4. 渲染UI
return (
<div className="border p-4 rounded-lg">
<h3>{PACKAGE.name}</h3>
<div className="text-2xl font-bold">
{isLoading ? (
<Loader2 className="animate-spin" />
) : (
`$${price?.toFixed(2) || "0.00"}`
)}
</div>
<Button
onClick={handleBuy}
disabled={isLoading || !price}
className="w-full mt-4"
>
购买 {PACKAGE.credits} 积分
</Button>
{error && <p className="text-red-500 text-sm">{error.message}</p>}
</div>
);
}
3. UI实现:显示积分
要显示用户的当前余额,请使用 useCredits 钩子。
关键钩子:useCredits
位置:src/lib/users/useCredits.ts
返回数据结构
const {
credits, // Record<string, number> | undefined
// 例如 { "image_generation": 100, "video_generation": 50 }
isLoading, // 布尔值
error, // 任何类型
mutate // SWR mutate 函数,用于刷新数据
} = useCredits();
用法示例
import useCredits from "@/lib/users/useCredits";
export function CreditBalance() {
const { credits, isLoading } = useCredits();
if (isLoading) return <div>加载中...</div>;
return (
<div>
图片生成积分: {credits?.image_generation || 0}
</div>
);
}
4. 核心操作(后端)
这些函数在API路由和Webhook中使用。
分配积分(例如,在套餐变更时)
使用 allocatePlanCredits 在用户订阅/升级时给予积分。
- 文件:
src/lib/credits/allocatePlanCredits.ts - 输入:
userId,planId,paymentId(用于幂等性)。 - 行为:检查
onPlanChangeCredits配置,并在适用时添加积分。
添加/扣除积分
要手动操作余额,请使用 src/lib/credits/recalculate.ts 中的辅助函数(例如,addCredits, deductCredits)。
注意:在添加积分时,请始终确保您有一个唯一的 paymentId 或交易参考号,以防止重复。
5. 检查余额(后端/通用)
在执行操作之前,请使用 canDeductCredits。
import { canDeductCredits } from "@/lib/credits/credits";
// 检查用户是否有足够的积分
const hasBalance = canDeductCredits(
"image_generation",
1,
user // 必须包含 { credits: { ... } }
);
if (!hasBalance) {
throw new Error("积分不足");
}
6. 参考
有关数据库模式和架构的深入探讨,请参阅 reference.md。