ai-chatbot/lib/points/feature-charge.ts

105 lines
2.7 KiB
TypeScript

import {
consumeUserPoints,
getPointsSystemConfig,
getUserById,
} from "@/lib/db/queries";
import type { PointsSystemConfig } from "@/lib/points/config";
type FeaturePointsChargeInput = {
userId: string;
unitCost: number;
reason: string;
modelId?: string | null;
messageId?: string | null;
note: string;
pointsConfig?: PointsSystemConfig;
forceEnabled?: boolean;
};
type FeaturePointsChargeOk = {
ok: true;
chargeable: boolean;
unitCost: number;
consume: (overrides?: {
modelId?: string | null;
messageId?: string | null;
note?: string;
reason?: string;
}) => Promise<{
ok: boolean;
balance: number | null;
skipped: boolean;
} | null>;
};
type FeaturePointsChargeBlocked = {
ok: false;
status: number;
payload: {
success: false;
error: string;
code: string;
message: string;
balance: number;
expectedCost: number;
unitCost: number;
};
};
function normalizeRuntimeCost(value: unknown) {
const parsedValue = Number(value);
return Number.isFinite(parsedValue) && parsedValue > 0
? Math.max(0, Math.round(parsedValue))
: 0;
}
export async function prepareFeaturePointsCharge(
input: FeaturePointsChargeInput
): Promise<FeaturePointsChargeOk | FeaturePointsChargeBlocked> {
const unitCost = normalizeRuntimeCost(input.unitCost);
const pointsConfig = input.pointsConfig || (await getPointsSystemConfig());
const chargeable = (pointsConfig.enabled === true || input.forceEnabled === true) && unitCost > 0;
if (
chargeable &&
(input.forceEnabled === true || pointsConfig.blockOnInsufficient !== false)
) {
const userData = await getUserById({ id: input.userId });
if (!userData?.isHighVip) {
const currentBalance = Math.max(0, Number(userData?.pointsBalance || 0));
if (currentBalance < unitCost) {
return {
ok: false,
status: 402,
payload: {
success: false,
error: "积分不足",
code: "insufficient_points",
message: `积分不足,当前余额 ${currentBalance},本次预计消耗 ${unitCost}。`,
balance: currentBalance,
expectedCost: unitCost,
unitCost,
},
};
}
}
}
return {
ok: true,
chargeable,
unitCost,
consume: async (overrides = {}) => {
if (!chargeable) return null;
return consumeUserPoints({
userId: input.userId,
amount: unitCost,
reason: overrides.reason || input.reason,
modelId: overrides.modelId ?? input.modelId ?? null,
messageId: overrides.messageId ?? input.messageId ?? null,
note: overrides.note || input.note,
});
},
};
}