交易记录与花费
每一笔进出组织的余额都会写进一本只能追加的账本。计费页面用两种方式把它呈现出来:一张图表看花钱的形状,一张表格看背后确切的记录。
交易列表
表格按从新到旧排列,一次显示 50 条记录;加载更多会取下一页。
| 列 | 里面装的是什么 |
|---|---|
| 日期 | 这条记录写入的时间,精确到秒 |
| 类型 | 这是哪一类记录 |
| 详情 | 对扣费记录来说,是它所属的实例和它覆盖的时长;其他记录见下文 |
| 金额 | 余额的带符号变化,充值为正 |
| 余额 | 这条记录之后紧接着的余额 |
你会遇到的类型:
| 类型 | 控制台标签 | 由什么产生 |
|---|---|---|
topup | 额度充值 | 一笔已确认的 Stripe 付款 |
charge_gpu | GPU 使用 | 运行时长,每个实例每个整点小时一行 |
charge_storage | 存储 | 停止期间的磁盘时长,每个实例每个整点小时一行 |
adjustment | 调整 | 任何一次既不是充值、也不是计量用量的带符号余额变动 —— 多数是 Stripe 的追回,自动入账。见下文 |
adjustment 不只是手动修正。Stripe 的事件会自己产生它,而这一行的 meta.reason 会说明是哪一种。这时类型列显示的是原因,而不是调整:
meta.reason | 控制台标签 | 方向 | 发生了什么 |
|---|---|---|---|
refund | 退款 | 扣款 | 一笔付款被退款了。只扣这一次事件所涉及的金额,所以第二次部分退款不会把第一次的再扣一遍 |
dispute_opened | 争议已发起 | 扣款 | 有人发起了拒付,钱已经没了 |
dispute_funds_withdrawn | 争议——资金已扣除 | 扣款 | 一笔先以问询形式到达的争议最终把资金划走了,所以它刚发起时并没有扣款 |
dispute_won | 争议胜诉 | 入账 | 你赢了争议,它的扣款被冲回 |
dispute_reinstated | 争议——部分资金已退回 | 入账 | 争议输了,但卡组织退回了一部分钱。退回的钱只入账一次,而且只以当初扣掉的为限:如果更早的一笔退款已经让这笔付款的一部分没有被扣,这一部分不会再入账一次 |
dispute_capped | 争议——已按付款金额调整 | 入账 | 同一笔付款的退款在争议扣款之后才到达,于是那次扣款的一部分被退回:一笔付款永远不会被追回两次 |
staff_adjustment | 客服调整 | 两者皆可 | Superheat 客服手动调整了余额。详情单元格显示他们写给你的说明 |
一笔争议追回的金额,永远不会超过这笔付款还剩下可追回的部分:付款金额(当发卡行把几笔付款合并成一笔争议时,则是争议金额)减去已经记在它上面的退款。所以一笔 $10 的充值,先退款 $4、再被争议并且输了,总共花掉的是 $10 —— 不管退款和争议以什么顺序到达,都不会是 $14。当发卡行用第二笔金额更正过的争议取代第一笔时,每笔争议都单独跟踪。如果卡组织以另一种币种、或者没有说明金额地退回了一笔输掉的争议的一部分,系统不会自动入账任何东西;Superheat 工作人员会收到提醒去审核。
正式争议在进行期间还会冻结这个组织:POST /v1/instances 和启动一个已停止的实例都会返回 402 PAYMENTS_FROZEN,这样一笔拒付就没法用来买 GPU 时间。既然已停止的实例在此期间无法启动,它们的 GPU 也会被释放,冻结期间按下的停止会立刻释放 GPU,两者都以机器支持为前提——参见支付争议未结期间。争议结束时无论输赢都会解冻 —— 输了就保留那次扣款,之后由余额来决定你能做什么 —— 但前提是这个组织没有其他正式争议还开着。Superheat 工作人员审核争议之后也可以解除冻结。而一次问询,也就是发卡行只是在提问、还没有任何资金变动,既不会冻结账户,也不产生任何账本记录:为了一个往往不了了之的提问就去扣款,等于从一个什么错都没犯的人手里拿走额度。如果问询升级成正式争议,冻结和扣款到那时才会跟上。每笔争议最多扣款一次,赢下的争议会原样退回它扣掉的金额。
对扣费记录来说,详情单元格会链到那个实例的计费标签页,并给出这一行覆盖的时长,例如 47 分 0 秒。仍在进行中的那个小时的记录会标上进行中。充值没有对应的实例,改为显示付款方式(通过Stripe)。退款或争议记录显示已退款或支付争议,客服调整显示客服写的说明,其他调整显示一个短横线。
每个实例每小时一行
费用仍然大约每分钟结算一次,和计费方式里描述的完全一样,只是不再各占一行。每个实例在当前整点小时(UTC)里有一行未关闭的扣费记录,每分钟的结算都加到这一行上:它的金额和覆盖的时长会一直增长,它之后的余额会一直下降,直到这一行关闭。在以下情况下,这一行关闭,下一次结算开一行新的:
- 这个整点小时结束;
- 实例改变状态 —— 停止、启动或销毁都会为到那一刻为止的时长单独写一行已关闭的记录;
- 费率改变,或者实例迁到了另一台机器上;
- 在它之后写入了一笔充值或调整。付款之后的余额永远不会被改写,所以一行记录不会越过付款继续增长。
所以一个跑满一整天的实例大约产生 24 行,而不是 1,440 行;在一个小时中间停止,会让那个小时的记录提前结束。要看单个实例的合计,就打开那个实例读已结算支出;要看逐日的合计,就用图表。不满一美分的金额会显示到小数点后四位,所以一笔小额的存储费用读起来是 $0.0007 之类的数字,而不是 $0.00。
消费图表
消费 —— 最近 30 天每天画一个点,30 天的合计就在标题旁边。把鼠标悬在某个点上,会给出那一天的数字。
图表统计的是什么:
- 只有 GPU 费用和存储费用。充值和调整不计入——这是花费,不是现金流。
- 天是按你浏览器所在时区的日历日分桶的,所以深夜跑的任务会落在你跑它的那一天。控制台会把这个时区发给 API,由 API 按它给费用分桶。一行扣费记录计入它那个小时开始的那一天:在偏移量不是整小时的时区(例如印度),午夜前后最多一小时的用量可能落到相邻的那一天。
- 没有费用的日子会画成零,而不是跳过,正是这一点让图表在几阵工作之间出现平直的一段段。
端点
两个端点读的都是当前组织的账本。发送一个 bearer 令牌——可以是已登录的会话,也可以是 shk_ 组织 API 密钥。用会话时,由 X-Org-Id 选择组织,不带这个请求头就用个人组织;API 密钥被钉在它自己的组织上,为其他任何组织发送 X-Org-Id 都会被以 ORG_MISMATCH 拒绝。这两个端点都不要求管理员,所以成员或 API 密钥都能读。参见 身份验证。
GET /v1/billing/transactions
curl "$SUPERHEAT_API/v1/billing/transactions?limit=100" \
-H "Authorization: Bearer $SUPERHEAT_TOKEN" \
-H "X-Org-Id: $ORG_ID"
| 参数 | 取值范围 | 默认值 | 作用 |
|---|---|---|---|
limit | 1 到 200 | 50 | 每页的记录数 |
cursor | 一个记录 id | — | 返回比这个 id 更早的记录 |
instance_id | 一个实例 id | — | 把这一页限定在单个实例上 |
记录以从新到旧的顺序装在 items 里返回,旁边还有一个 next_cursor。把那个值作为 cursor 传回去就能取下一页;当它返回 null 时,你就到账本的尽头了。
每条记录都带着自己的 id(它同时充当分页游标)、type、所属的 instance_id(充值或调整则为 null)、created_at 时间戳、带符号的金额,以及由此得出的余额。调整带一个来自上表的 meta.reason,reason 在记录的顶层重复它(没有 reason 的记录为 null)。
计量类的费用带一个 meta 对象,包括 period_start、period_end、seconds(这一行计费的时长)、rate_micro_per_hour 和 settlements(它累加了多少次每分钟的结算;按小时成行之前写入的记录没有这个计数,每行就是一次结算)。它的 created_at 是第一次结算发生的时间,这一行只接受同一个整点小时内的结算。accruing 为 true 的,是实例仍在往上累加的那一行:在它关闭之前,它的金额、meta.seconds、meta.period_end 和 meta.settlements 都会增长,它之后的余额以及后面各行扣费记录之后的余额也会下降同样的金额。其他记录一旦写入就不再改变。如果你把账本复制到别处,请对曾经 accruing 的记录持续重读,直到它变回 false。
要拉取单个实例的费用:
curl "$SUPERHEAT_API/v1/billing/transactions?instance_id=$INSTANCE_ID&limit=200" \
-H "Authorization: Bearer $SUPERHEAT_TOKEN" \
-H "X-Org-Id: $ORG_ID"
GET /v1/billing/spend-daily
curl "$SUPERHEAT_API/v1/billing/spend-daily?days=90" \
-H "Authorization: Bearer $SUPERHEAT_TOKEN" \
-H "X-Org-Id: $ORG_ID"
| 参数 | 取值范围 | 默认值 | 作用 |
|---|---|---|---|
days | 1 到 90 | 30 | 往前聚合多久 |
tz | IANA 时区名,例如 Europe/Berlin | UTC | 按谁的日历日给费用分组。API 不认识的名称会返回 422 INVALID_TIME_ZONE |
这就是图表的数据源:按 tz 中的日历日分组的 GPU 和存储费用,从旧到新,每条记录带一个 date 和那一天的合计。只有有费用的日子才会出现,所以清闲的一周产生的是没有记录,而不是一串零——如果你要画一条稠密的序列,空档得自己填。