export const POINTS_SYSTEM_CONFIG_KEY = "points_system_config"; export const POINTS_PACKAGES_KEY = "points_packages"; export type PointsSystemConfig = { enabled: boolean; blockOnInsufficient: boolean; creditCostPer3DModel: number; creditCostPer3DWorld: number; }; export type PointsPackage = { id: string; name: string; points: number; price: number; currency: string; isEnabled: boolean; }; export const defaultPointsSystemConfig: PointsSystemConfig = { enabled: false, blockOnInsufficient: true, creditCostPer3DModel: 0, creditCostPer3DWorld: 0, }; export const defaultPointsPackages: PointsPackage[] = [ { id: "starter-1000", name: "入门包", points: 1000, price: 9.9, currency: "CNY", isEnabled: true, }, { id: "pro-5000", name: "专业包", points: 5000, price: 39.9, currency: "CNY", isEnabled: true, }, ]; export function normalizePointsSystemConfig(value: unknown): PointsSystemConfig { if (!value || typeof value !== "object") { return defaultPointsSystemConfig; } const raw = value as Partial & { creditCostPer3D?: number; }; const normalizeCost = (input: unknown) => { const parsedValue = Number(input); return Number.isFinite(parsedValue) && parsedValue > 0 ? Math.min(100000, Math.round(parsedValue)) : 0; }; const legacy3DCost = normalizeCost(raw.creditCostPer3D); return { enabled: raw.enabled === true, blockOnInsufficient: raw.blockOnInsufficient !== false, creditCostPer3DModel: normalizeCost( raw.creditCostPer3DModel ?? legacy3DCost ), creditCostPer3DWorld: normalizeCost( raw.creditCostPer3DWorld ?? legacy3DCost ), }; } export function normalizePointsPackages(value: unknown): PointsPackage[] { if (!Array.isArray(value)) { return defaultPointsPackages; } const normalized = value .map((item, index) => { if (!item || typeof item !== "object") return null; const raw = item as Partial; const id = String(raw.id || `package-${index + 1}`).trim(); const name = String(raw.name || "").trim(); const points = Number(raw.points); const price = Number(raw.price); const currency = String(raw.currency || "CNY").trim().toUpperCase(); if (!id || !name || !Number.isFinite(points) || points <= 0) return null; if (!Number.isFinite(price) || price < 0) return null; return { id: id.slice(0, 64), name: name.slice(0, 64), points: Math.round(points), price: Number(price.toFixed(2)), currency: currency.slice(0, 8) || "CNY", isEnabled: raw.isEnabled !== false, } satisfies PointsPackage; }) .filter((item): item is PointsPackage => Boolean(item)) .slice(0, 30); return normalized.length > 0 ? normalized : defaultPointsPackages; }