PAYATHON 2026

如何通过 Cloud Billing Catalog API 确定最小计费周期?

支付老李

结论

仅凭 Cloud Billing Catalog API,无法确定资源的最小计量单位或最小计费周期。

baseUnit: "s" 并不表示 Cloud DNS 托管区域按秒取整计费。它只是定价表达式中的基础换算单位。Managed Zone 的用量如何采集、取整和汇总,仍需以对应产品的计费文档为准,也就是不足一小时按一小时计算。

API 字段应如何理解

这些字段分别描述报价单位、内部基础单位,以及两者的换算关系:

  • usageUnit: "mo":unitPrice 对应的报价单位是月。
  • baseUnit: "s":计价系统用秒来表达或传递基础用量。
  • baseUnitConversionFactor: 2505600:一个报价单位所包含的基础单位数量,即:
1 mo = 2505600 s

换算后:

2505600 / 3600 = 696 h
696 / 24 = 29 d

这里的 mo 是计费系统定义的标准化价格单位,不能直接理解为当前自然月的实际秒数。因此,不能由此推断某个月一定按 29 天结算,也不能推断用量精确到秒,只有不足一秒时才会进位。

首档价格为:

0.20 USD / mo

将它换算成内部基础单位价格:

0.20 / 2505600 USD / s

如果计费系统接收到的基础用量是 (B),理论上的价格换算形式为:

cost = (B / baseUnitConversionFactor) × unitPrice

问题在于,Catalog API 并未说明资源的原始活动如何转换成 (B)。如果产品规则先将托管区域的存续时间向上取整到小时,再转换成秒,那么存续时间不足一小时的区域仍会产生 3600 s 的计费用量,而不是按实际存续秒数计费。

例如,某个托管区域只存在 10 分钟,产品规则可能先进行以下处理:

10 min → ceil(10 / 60) h → 1 h → 3600 s

然后再根据 Catalog 中的月价和换算因子计算:

cost = (3600 / 2505600) × 0.20

所以,按小时取整与使用秒作为内部基础单位并不矛盾。前者规定如何量化用量,后者用于表达价格和换算单位。

aggregationInfo 也不是最小计费周期

响应中的:

{
  "aggregationLevel": "ACCOUNT",
  "aggregationInterval": "MONTHLY",
  "aggregationCount": 1
}

表示该 SKU 的阶梯用量按账号每月聚合一次,主要用于确定不同 tieredRates 门槛的应用方式。

例如:

{
  "startUsageAmount": 0,
  "unitPrice": {
    "nanos": 200000000
  }
},
{
  "startUsageAmount": 25,
  "unitPrice": {
    "nanos": 100000000
  }
}

这表示账号的月度聚合用量超过 25 个报价单位后,后续用量进入下一价格档位。MONTHLY 不表示资源至少需要购买一个月,也不表示每月只计算一次,更不表示不足一个月时不能按比例计费。

这里需要区分四个概念:

概念Managed Zone 示例Catalog API 是否完整提供
报价单位mo是
内部基础单位s是
阶梯价格聚合周期MONTHLY是
原始用量采集、取整规则不足一小时按一小时否

正确的判断步骤

判断一个 SKU 的实际计费粒度时,可以按以下顺序处理:

  1. 使用 Catalog API 获取 usageUnit、baseUnit、换算因子、价格档位和聚合方式。
  2. 查阅对应产品的定价说明,确认用量如何采集、取整和按比例折算。
  3. 检查产品是否设有最低计费时长、启动费、按请求进位或其他特殊规则。
  4. 使用 Cloud Billing 导出的实际用量验证边界场景,例如资源只存在几分钟或跨越整点。
  5. 如果产品文档未说明取整规则,就不能根据 baseUnit 推断,应向 Google Cloud 支持确认该 SKU 的计量逻辑。

解析 Catalog 响应的示例

下面的代码只能解释价格如何换算,无法判断产品是否按小时取整:

from decimal import Decimal

pricing_expression = {
    "usageUnit": "mo",
    "baseUnit": "s",
    "baseUnitConversionFactor": 2505600,
}

unit_price = Decimal("0.20")
factor = Decimal(str(pricing_expression["baseUnitConversionFactor"]))

price_per_base_unit = unit_price / factor

print(f"Price: {unit_price} USD/{pricing_expression['usageUnit']}")
print(
    f"Conversion: 1 {pricing_expression['usageUnit']} "
    f"= {factor} {pricing_expression['baseUnit']}"
)
print(
    f"Internal equivalent: {price_per_base_unit} "
    f"USD/{pricing_expression['baseUnit']}"
)

如果产品文档明确规定按小时向上取整,可以在估算程序中单独实现这项规则:

from decimal import Decimal, ROUND_CEILING

SECONDS_PER_HOUR = Decimal("3600")
BASE_UNIT_CONVERSION_FACTOR = Decimal("2505600")
MONTHLY_UNIT_PRICE = Decimal("0.20")

def estimate_managed_zone_cost(actual_seconds: int) -> Decimal:
    if actual_seconds <= 0:
        return Decimal("0")

    billed_hours = (
        Decimal(actual_seconds) / SECONDS_PER_HOUR
    ).to_integral_value(rounding=ROUND_CEILING)

    billed_base_units = billed_hours * SECONDS_PER_HOUR

    return (
        billed_base_units
        / BASE_UNIT_CONVERSION_FACTOR
        * MONTHLY_UNIT_PRICE
    )

这段代码表达的是先按照产品规则量化用量,再根据 Catalog 价格进行换算。它并不是对 Google Cloud 账单系统实现细节的复刻。

注意事项

  • 不要将 baseUnit 理解为用量采样频率、取整粒度或最低购买时长。
  • 不要将 aggregationInterval: "MONTHLY" 理解为最低计费周期。
  • 不要用自然月的秒数替换 baseUnitConversionFactor。估算时应使用对应价格版本返回的换算因子。
  • pricingInfo 可能包含不同生效时间的价格。计算时应选择与实际用量时间相匹配的记录。
  • 计算阶梯价时,需要同时考虑 aggregationLevel 和 aggregationInterval,不能对每个资源单独套用。
  • Catalog API 主要说明单位用量的价格及换算方式,产品文档则说明哪些行为会产生多少计费用量。

因此,这个响应与 Cloud DNS 按小时取整的规则并不冲突。s 是表达价格时使用的基础单位,小时才是产品文档规定的用量折算粒度。

备注:内容仅供参考。