480 lines
19 KiB
TypeScript
480 lines
19 KiB
TypeScript
import {
|
||
createBillingPriceRule,
|
||
listBillingPriceRules,
|
||
updateBillingPriceRule,
|
||
type BillingPriceRuleInput
|
||
} from "@/lib/server/billing-store";
|
||
import type { BillingParameterDimension, 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-21";
|
||
|
||
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 SEEDREAM_PRICING_URL = "https://docs.volcengine.com/docs/82379/1544106?lang=zh";
|
||
const MINIMAX_PRICING_URL = "https://platform.minimaxi.com/docs/guides/pricing-paygo#视频";
|
||
|
||
const SEEDREAM_50_PRO_DIMENSIONS = [
|
||
{
|
||
key: "size",
|
||
label: "分辨率",
|
||
baselineValue: "1.5K",
|
||
defaultValue: "1.5K",
|
||
tiers: [
|
||
{ value: "1K", label: "1K", standardFactor: 1, markupMultiplier: DEFAULT_BILLING_MARKUP_MULTIPLIER, enabled: true },
|
||
{ value: "1.5K", label: "1.5K", standardFactor: 1, markupMultiplier: DEFAULT_BILLING_MARKUP_MULTIPLIER, enabled: true },
|
||
{ value: "2K", label: "2K", standardFactor: 2, markupMultiplier: DEFAULT_BILLING_MARKUP_MULTIPLIER, enabled: true }
|
||
]
|
||
}
|
||
] satisfies NonNullable<BillingPriceRuleInput["parameterDimensions"]>;
|
||
|
||
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: "3:4", label: "3:4", 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: "70:99", label: "A4 竖版", standardFactor: 1, markupMultiplier: DEFAULT_BILLING_MARKUP_MULTIPLIER, enabled: true },
|
||
{ value: "99:70", label: "A4 横版", 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: "1–4", label: "1–4 张", match: { min: 1, max: 4 }, standardFactor: 1, markupMultiplier: DEFAULT_BILLING_MARKUP_MULTIPLIER, enabled: true, note: "EvoLink 上游按 image input token 计费,当前平台按基础生成成本估算。" },
|
||
{ value: "5–8", label: "5–8 张", match: { min: 5, max: 8 }, standardFactor: 1, markupMultiplier: DEFAULT_BILLING_MARKUP_MULTIPLIER, enabled: true, note: "EvoLink 上游按 image input token 计费,当前平台按基础生成成本估算。" },
|
||
{ value: "9–16", label: "9–16 张", match: { min: 9, max: 16 }, standardFactor: 1, markupMultiplier: DEFAULT_BILLING_MARKUP_MULTIPLIER, enabled: true, note: "EvoLink 上游按 image input token 计费,当前平台按基础生成成本估算。" }
|
||
]
|
||
}
|
||
] satisfies NonNullable<BillingPriceRuleInput["parameterDimensions"]>;
|
||
|
||
function evolink25PriceRule(model: string, sourceUrl: string): BillingPriceRuleInput {
|
||
const parameterDimensions = structuredClone(EVOLINK_GPT_IMAGE_DIMENSIONS);
|
||
parameterDimensions[0].tiers = [
|
||
{ value: "low", label: "快速", standardFactor: 4 / 9, markupMultiplier: DEFAULT_BILLING_MARKUP_MULTIPLIER, enabled: true },
|
||
{ value: "medium", label: "标准", standardFactor: 1, markupMultiplier: DEFAULT_BILLING_MARKUP_MULTIPLIER, enabled: true },
|
||
{ value: "high", label: "精细", standardFactor: 35 / 9, markupMultiplier: DEFAULT_BILLING_MARKUP_MULTIPLIER, enabled: true }
|
||
];
|
||
parameterDimensions[1].tiers = parameterDimensions[1].tiers.slice(0, 1);
|
||
return {
|
||
id: `base-evolink-${model}`,
|
||
provider: "evolink",
|
||
capability: "image.generate",
|
||
reqKey: model,
|
||
unit: "image",
|
||
standardUnitPriceFen: 9,
|
||
markupMultiplier: DEFAULT_BILLING_MARKUP_MULTIPLIER,
|
||
enabled: true,
|
||
note: "1K 基础输出估算:low/medium/high 成本为 ¥0.04/¥0.09/¥0.35;输入费用未单独加收,非上游实际 token 账单。",
|
||
parameterDimensions,
|
||
source: {
|
||
url: sourceUrl,
|
||
currency: "USD",
|
||
unitPrice: 0.012,
|
||
fxRate: BILLING_USD_CNY_RATE,
|
||
basis: "1024×1024、无参考图输出费用估算:low 约 $0.0053,medium 约 $0.012,high 约 $0.0474;人民币标准成本向上取整到分。参考图和提示词会影响上游实扣。",
|
||
observedAt: "2026-09-23"
|
||
}
|
||
};
|
||
}
|
||
|
||
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
|
||
}
|
||
},
|
||
evolink25PriceRule("gpt-image-2.5-flare", "https://evolink.ai/gpt-image-2-5"),
|
||
evolink25PriceRule("gpt-image-2.5-sunburst", "https://evolink.ai/zh/gpt-image-2-5-sunburst"),
|
||
{
|
||
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-seedream-5-0-pro",
|
||
provider: "seedream",
|
||
capability: "image.generate",
|
||
reqKey: "doubao-seedream-5-0-pro-260628",
|
||
unit: "image",
|
||
standardUnitPriceFen: 30,
|
||
markupMultiplier: DEFAULT_BILLING_MARKUP_MULTIPLIER,
|
||
enabled: true,
|
||
note: "基础生图:1K/1.5K ¥0.30,2K ¥0.60;首张输入图免费,额外输入图 ¥0.02/张。",
|
||
parameterDimensions: SEEDREAM_50_PRO_DIMENSIONS,
|
||
source: {
|
||
url: SEEDREAM_PRICING_URL,
|
||
currency: "CNY",
|
||
unitPrice: 0.3,
|
||
basis: "Seedream 5.0 Pro 单图基础生图;2K 按 ¥0.60/张,额外参考图按 ¥0.02/张",
|
||
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.0,480P,无输入视频、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.0,720P,无输入视频、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.0,1080P,无输入视频、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.0,4K,无输入视频、16:9、5 秒示例折算;实际 token 计费可能随输入变化",
|
||
observedAt: BILLING_CATALOG_OBSERVED_AT
|
||
}
|
||
},
|
||
{
|
||
id: "base-seedance-2-5-480p",
|
||
provider: "seedance",
|
||
capability: "video.generate",
|
||
reqKey: "doubao-seedance-2-5-260628",
|
||
variantKey: "resolution=480p",
|
||
unit: "video_second",
|
||
standardUnitPriceFen: 67,
|
||
markupMultiplier: DEFAULT_BILLING_MARKUP_MULTIPLIER,
|
||
enabled: true,
|
||
note: "官方 5 秒、16:9、无输入视频样例 ¥3.36,实际按方舟 token 用量结算。",
|
||
source: {
|
||
url: `${VOLC_ARK_PRICING_URL}#sd25_price`,
|
||
currency: "CNY",
|
||
unitPrice: 0.67,
|
||
basis: "doubao-seedance-2.5,480P,无输入视频、16:9、5 秒示例折算",
|
||
observedAt: BILLING_CATALOG_OBSERVED_AT
|
||
}
|
||
},
|
||
{
|
||
id: "base-seedance-2-5-720p",
|
||
provider: "seedance",
|
||
capability: "video.generate",
|
||
reqKey: "doubao-seedance-2-5-260628",
|
||
variantKey: "resolution=720p",
|
||
unit: "video_second",
|
||
standardUnitPriceFen: 151,
|
||
markupMultiplier: DEFAULT_BILLING_MARKUP_MULTIPLIER,
|
||
enabled: true,
|
||
note: "官方 5 秒、16:9、无输入视频样例 ¥7.56,实际按方舟 token 用量结算。",
|
||
source: {
|
||
url: `${VOLC_ARK_PRICING_URL}#sd25_price`,
|
||
currency: "CNY",
|
||
unitPrice: 1.51,
|
||
basis: "doubao-seedance-2.5,720P,无输入视频、16:9、5 秒示例折算",
|
||
observedAt: BILLING_CATALOG_OBSERVED_AT
|
||
}
|
||
},
|
||
{
|
||
id: "base-seedance-2-5-1080p",
|
||
provider: "seedance",
|
||
capability: "video.generate",
|
||
reqKey: "doubao-seedance-2-5-260628",
|
||
variantKey: "resolution=1080p",
|
||
unit: "video_second",
|
||
standardUnitPriceFen: 374,
|
||
markupMultiplier: DEFAULT_BILLING_MARKUP_MULTIPLIER,
|
||
enabled: true,
|
||
note: "官方 5 秒、16:9、无输入视频样例 ¥18.71,实际按方舟 token 用量结算。",
|
||
source: {
|
||
url: `${VOLC_ARK_PRICING_URL}#sd25_price`,
|
||
currency: "CNY",
|
||
unitPrice: 3.74,
|
||
basis: "doubao-seedance-2.5,1080P,无输入视频、16:9、5 秒示例折算",
|
||
observedAt: BILLING_CATALOG_OBSERVED_AT
|
||
}
|
||
},
|
||
{
|
||
id: "base-minimax-h3-768p",
|
||
provider: "minimax",
|
||
capability: "video.generate",
|
||
reqKey: "MiniMax-H3",
|
||
variantKey: "resolution=768p",
|
||
unit: "video_second",
|
||
standardUnitPriceFen: 50,
|
||
markupMultiplier: DEFAULT_BILLING_MARKUP_MULTIPLIER,
|
||
enabled: true,
|
||
note: "MiniMax H3 768P 输出 ¥0.50/秒;首五张输入图免费。",
|
||
source: {
|
||
url: MINIMAX_PRICING_URL,
|
||
currency: "CNY",
|
||
unitPrice: 0.5,
|
||
basis: "MiniMax H3 768P 输出视频,按成功视频秒数计费",
|
||
observedAt: "2026-09-04"
|
||
}
|
||
},
|
||
{
|
||
id: "base-minimax-h3-2k",
|
||
provider: "minimax",
|
||
capability: "video.generate",
|
||
reqKey: "MiniMax-H3",
|
||
variantKey: "resolution=2k",
|
||
unit: "video_second",
|
||
standardUnitPriceFen: 80,
|
||
markupMultiplier: DEFAULT_BILLING_MARKUP_MULTIPLIER,
|
||
enabled: true,
|
||
note: "MiniMax H3 2K 输出 ¥0.80/秒;首五张输入图免费。",
|
||
source: {
|
||
url: MINIMAX_PRICING_URL,
|
||
currency: "CNY",
|
||
unitPrice: 0.8,
|
||
basis: "MiniMax H3 2K 输出视频,按成功视频秒数计费",
|
||
observedAt: "2026-09-04"
|
||
}
|
||
}
|
||
];
|
||
|
||
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: mergeParameterDimensions(candidate.parameterDimensions, existingRule.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 mergeParameterDimensions(
|
||
defaults: BillingParameterDimension[] | undefined,
|
||
saved: BillingParameterDimension[] | undefined
|
||
): BillingParameterDimension[] | undefined {
|
||
return defaults?.map((dimension) => {
|
||
const savedDimension = saved?.find((item) => item.key === dimension.key);
|
||
return {
|
||
...dimension,
|
||
tiers: dimension.tiers.map((tier) => {
|
||
const savedTier = savedDimension?.tiers.find((item) => String(item.value) === String(tier.value));
|
||
const savedMarkup = savedTier?.markupMultiplier;
|
||
return {
|
||
...tier,
|
||
markupMultiplier: typeof savedMarkup === "number"
|
||
&& Number.isFinite(savedMarkup)
|
||
&& savedMarkup >= 1
|
||
&& savedMarkup <= 1000
|
||
? savedMarkup
|
||
: tier.markupMultiplier
|
||
};
|
||
})
|
||
};
|
||
});
|
||
}
|
||
|
||
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;
|
||
}
|