105 lines
2.7 KiB
TypeScript
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,
|
|
});
|
|
},
|
|
};
|
|
}
|