Files
NianAIGC/lib/server/billing-catalog.ts

290 lines
12 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import {
createBillingPriceRule,
listBillingPriceRules,
updateBillingPriceRule,
type BillingPriceRuleInput
} from "@/lib/server/billing-store";
import type { BillingPriceRule } from "@/lib/types";
export const BILLING_USD_CNY_RATE = 7.2;
export const DEFAULT_BILLING_MARKUP_MULTIPLIER = 1.2;
export const BILLING_CATALOG_OBSERVED_AT = "2026-08-11";
const BAILIAN_IMAGE_URL = "https://help.aliyun.com/zh/model-studio/wan2-7-image-pro";
const BAILIAN_VIDEO_URL = "https://help.aliyun.com/zh/model-studio/wan2-7-i2v";
const EVOLINK_IMAGE_URL = "https://evolink.ai/zh/gpt-image-2";
const VOLC_ARK_PRICING_URL = "https://www.volcengine.com/docs/82379/1544106?lang=zh";
const JIMENG_PRICING_URL = "https://www.volcengine.com/activity/jimeng";
const EVOLINK_GPT_IMAGE_DIMENSIONS = [
{
key: "quality",
label: "生成质量",
baselineValue: "medium",
defaultValue: "medium",
tiers: [
{ value: "low", label: "快速", standardFactor: 0.11, markupMultiplier: DEFAULT_BILLING_MARKUP_MULTIPLIER, enabled: true, note: "官方口径约为 medium 的 0.11 倍成本。" },
{ value: "medium", label: "标准", standardFactor: 1, markupMultiplier: DEFAULT_BILLING_MARKUP_MULTIPLIER, enabled: true },
{ value: "high", label: "精细", standardFactor: 4, markupMultiplier: DEFAULT_BILLING_MARKUP_MULTIPLIER, enabled: true, note: "官方口径约为 medium 的 4 倍成本。" }
]
},
{
key: "resolution",
label: "分辨率",
baselineValue: "1K",
defaultValue: "1K",
tiers: [
{ value: "1K", label: "1K", standardFactor: 1, markupMultiplier: DEFAULT_BILLING_MARKUP_MULTIPLIER, enabled: true },
{ value: "2K", label: "2K", standardFactor: 4, markupMultiplier: DEFAULT_BILLING_MARKUP_MULTIPLIER, enabled: true, note: "按像素预算相对 1K 的估算倍率。" },
{ value: "4K", label: "4K", standardFactor: 8, markupMultiplier: DEFAULT_BILLING_MARKUP_MULTIPLIER, enabled: true, note: "按平台支持的最大像素预算估算。" }
]
},
{
key: "aspectRatio",
label: "画面比例",
baselineValue: "1:1",
defaultValue: "1:1",
tiers: [
{ value: "1:1", label: "1:1", standardFactor: 1, markupMultiplier: DEFAULT_BILLING_MARKUP_MULTIPLIER, enabled: true },
{ value: "4:3", label: "4:3", standardFactor: 1, markupMultiplier: DEFAULT_BILLING_MARKUP_MULTIPLIER, enabled: true },
{ value: "16:9", label: "16:9", standardFactor: 1, markupMultiplier: DEFAULT_BILLING_MARKUP_MULTIPLIER, enabled: true },
{ value: "9:16", label: "9:16", standardFactor: 1, markupMultiplier: DEFAULT_BILLING_MARKUP_MULTIPLIER, enabled: true },
{ value: "other", label: "其他比例", match: {}, standardFactor: 1, markupMultiplier: DEFAULT_BILLING_MARKUP_MULTIPLIER, enabled: true, note: "同一分辨率档位下按相同比例预算估算。" }
]
},
{
key: "referenceImageCount",
label: "参考图数量",
baselineValue: 0,
defaultValue: 0,
tiers: [
{ value: 0, label: "无参考图", standardFactor: 1, markupMultiplier: DEFAULT_BILLING_MARKUP_MULTIPLIER, enabled: true },
{ value: "14", label: "14 张", match: { min: 1, max: 4 }, standardFactor: 1, markupMultiplier: DEFAULT_BILLING_MARKUP_MULTIPLIER, enabled: true, note: "EvoLink 上游按 image input token 计费,当前平台按基础生成成本估算。" },
{ value: "58", label: "58 张", match: { min: 5, max: 8 }, standardFactor: 1, markupMultiplier: DEFAULT_BILLING_MARKUP_MULTIPLIER, enabled: true, note: "EvoLink 上游按 image input token 计费,当前平台按基础生成成本估算。" },
{ value: "916", label: "916 张", match: { min: 9, max: 16 }, standardFactor: 1, markupMultiplier: DEFAULT_BILLING_MARKUP_MULTIPLIER, enabled: true, note: "EvoLink 上游按 image input token 计费,当前平台按基础生成成本估算。" }
]
}
] satisfies NonNullable<BillingPriceRuleInput["parameterDimensions"]>;
export const DEFAULT_BILLING_PRICE_RULES: BillingPriceRuleInput[] = [
{
id: "base-volcengine-jimeng-seedream46",
provider: "volcengine-visual",
capability: "image.generate",
reqKey: "jimeng_seedream46_cvtob",
unit: "image",
standardUnitPriceFen: 20,
markupMultiplier: DEFAULT_BILLING_MARKUP_MULTIPLIER,
enabled: true,
note: "即梦4.6参考基准:公开活动页按 ¥200/1000 张折算;官方实时价格以供应商控制台为准。",
source: {
url: JIMENG_PRICING_URL,
currency: "CNY",
unitPrice: 0.2,
basis: "公开资源包 ¥200/1000 张折算;非 API 实时刊例价",
observedAt: BILLING_CATALOG_OBSERVED_AT
}
},
{
id: "base-evolink-gpt-image-2",
provider: "evolink",
capability: "image.generate",
reqKey: "gpt-image-2",
unit: "image",
standardUnitPriceFen: 34,
markupMultiplier: DEFAULT_BILLING_MARKUP_MULTIPLIER,
enabled: true,
note: "EvoLink 按 token 计费;当前以 medium / 1K / 1:1 / 无参考图作为平台标准基准,参数档位另列。",
parameterDimensions: EVOLINK_GPT_IMAGE_DIMENSIONS,
source: {
url: EVOLINK_IMAGE_URL,
currency: "USD",
unitPrice: 0.047,
fxRate: BILLING_USD_CNY_RATE,
basis: "页面估算medium、1K、1:1、无参考图约 $0.047/张;折算 ¥0.3384,账本按 ¥0.34",
observedAt: BILLING_CATALOG_OBSERVED_AT
}
},
{
id: "base-bailian-wan27-image-pro",
provider: "bailian",
capability: "image.generate",
reqKey: "wan2.7-image-pro",
unit: "image",
standardUnitPriceFen: 50,
markupMultiplier: DEFAULT_BILLING_MARKUP_MULTIPLIER,
enabled: true,
note: "北京地域官方基础价¥0.50/张。",
source: {
url: BAILIAN_IMAGE_URL,
currency: "CNY",
unitPrice: 0.5,
basis: "北京地域,按成功生成图片计费",
observedAt: BILLING_CATALOG_OBSERVED_AT
}
},
{
id: "base-bailian-wan27-i2v-720p",
provider: "bailian",
capability: "video.generate",
reqKey: "wan2.7-i2v-2026-04-25",
variantKey: "resolution=720p",
unit: "video_second",
standardUnitPriceFen: 60,
markupMultiplier: DEFAULT_BILLING_MARKUP_MULTIPLIER,
enabled: true,
note: "北京地域官方基础价720P ¥0.60/秒。",
source: {
url: BAILIAN_VIDEO_URL,
currency: "CNY",
unitPrice: 0.6,
basis: "北京地域720P按成功视频秒数计费",
observedAt: BILLING_CATALOG_OBSERVED_AT
}
},
{
id: "base-bailian-wan27-i2v-1080p",
provider: "bailian",
capability: "video.generate",
reqKey: "wan2.7-i2v-2026-04-25",
variantKey: "resolution=1080p",
unit: "video_second",
standardUnitPriceFen: 100,
markupMultiplier: DEFAULT_BILLING_MARKUP_MULTIPLIER,
enabled: true,
note: "北京地域官方基础价1080P ¥1.00/秒。",
source: {
url: BAILIAN_VIDEO_URL,
currency: "CNY",
unitPrice: 1,
basis: "北京地域1080P按成功视频秒数计费",
observedAt: BILLING_CATALOG_OBSERVED_AT
}
},
{
id: "base-seedance-2-0-480p",
provider: "seedance",
capability: "video.generate",
reqKey: "doubao-seedance-2-0-260128",
variantKey: "resolution=480p",
unit: "video_second",
standardUnitPriceFen: 46,
markupMultiplier: DEFAULT_BILLING_MARKUP_MULTIPLIER,
enabled: true,
note: "官方 5 秒、16:9、无输入视频样例折算¥2.31/5秒≈¥0.46/秒;实际按方舟 token 用量结算。",
source: {
url: VOLC_ARK_PRICING_URL,
currency: "CNY",
unitPrice: 0.46,
basis: "doubao-seedance-2.0480P无输入视频、16:9、5 秒示例折算;实际 token 计费可能随输入变化",
observedAt: BILLING_CATALOG_OBSERVED_AT
}
},
{
id: "base-seedance-2-0-720p",
provider: "seedance",
capability: "video.generate",
reqKey: "doubao-seedance-2-0-260128",
variantKey: "resolution=720p",
unit: "video_second",
standardUnitPriceFen: 99,
markupMultiplier: DEFAULT_BILLING_MARKUP_MULTIPLIER,
enabled: true,
note: "官方 5 秒、16:9、无输入视频样例折算¥4.97/5秒≈¥0.99/秒;实际按方舟 token 用量结算。",
source: {
url: VOLC_ARK_PRICING_URL,
currency: "CNY",
unitPrice: 0.99,
basis: "doubao-seedance-2.0720P无输入视频、16:9、5 秒示例折算;实际 token 计费可能随输入变化",
observedAt: BILLING_CATALOG_OBSERVED_AT
}
},
{
id: "base-seedance-2-0-1080p",
provider: "seedance",
capability: "video.generate",
reqKey: "doubao-seedance-2-0-260128",
variantKey: "resolution=1080p",
unit: "video_second",
standardUnitPriceFen: 248,
markupMultiplier: DEFAULT_BILLING_MARKUP_MULTIPLIER,
enabled: true,
note: "官方 5 秒、16:9、无输入视频样例折算¥12.39/5秒≈¥2.48/秒;实际按方舟 token 用量结算。",
source: {
url: VOLC_ARK_PRICING_URL,
currency: "CNY",
unitPrice: 2.48,
basis: "doubao-seedance-2.01080P无输入视频、16:9、5 秒示例折算;实际 token 计费可能随输入变化",
observedAt: BILLING_CATALOG_OBSERVED_AT
}
},
{
id: "base-seedance-2-0-4k",
provider: "seedance",
capability: "video.generate",
reqKey: "doubao-seedance-2-0-260128",
variantKey: "resolution=4k",
unit: "video_second",
standardUnitPriceFen: 505,
markupMultiplier: DEFAULT_BILLING_MARKUP_MULTIPLIER,
enabled: true,
note: "官方 5 秒、16:9、无输入视频样例折算¥25.27/5秒≈¥5.05/秒;实际按方舟 token 用量结算。",
source: {
url: VOLC_ARK_PRICING_URL,
currency: "CNY",
unitPrice: 5.05,
basis: "doubao-seedance-2.04K无输入视频、16:9、5 秒示例折算;实际 token 计费可能随输入变化",
observedAt: BILLING_CATALOG_OBSERVED_AT
}
}
];
export async function ensureDefaultBillingPriceRules(): Promise<BillingPriceRule[]> {
const existing = await listBillingPriceRules({ includeDisabled: true });
const created: BillingPriceRule[] = [];
for (const candidate of DEFAULT_BILLING_PRICE_RULES) {
const candidateKey = priceRuleMatchKey(candidate);
const existingRule = existing.find((rule) => priceRuleMatchKey(rule) === candidateKey);
if (existingRule) {
const updated = await updateBillingPriceRule(existingRule.id, {
reqKey: candidate.reqKey,
variantKey: candidate.variantKey,
unit: candidate.unit,
standardUnitPriceFen: candidate.standardUnitPriceFen,
enabled: candidate.enabled,
conditions: candidate.conditions,
quantitySource: candidate.quantitySource,
priority: candidate.priority,
note: candidate.note,
source: candidate.source,
parameterDimensions: candidate.parameterDimensions
});
if (updated) {
const index = existing.findIndex((rule) => rule.id === updated.id);
if (index >= 0) existing[index] = updated;
}
continue;
}
if (existing.some((rule) => rule.provider === candidate.provider
&& rule.capability === candidate.capability
&& !rule.reqKey
&& !rule.variantKey)) continue;
try {
const rule = await createBillingPriceRule(candidate);
created.push(rule);
} catch (error) {
if (!isConflict(error)) throw error;
}
}
return [...existing, ...created];
}
function priceRuleMatchKey(rule: Pick<BillingPriceRule, "provider" | "capability" | "reqKey" | "variantKey" | "conditions">): string {
const conditions = Object.fromEntries(Object.entries(rule.conditions || {}).sort(([left], [right]) => left.localeCompare(right)));
return [rule.provider, rule.capability, rule.reqKey || "", rule.variantKey || "", JSON.stringify(conditions)].join("\u0000");
}
function isConflict(error: unknown): boolean {
return typeof error === "object" && error !== null && "status" in error && (error as { status?: unknown }).status === 409;
}