# 对账 用任务账单接口核对每个任务的预留、扣款、退款和应收。 每个任务都有一份账单:一个汇总加一串不可变的账单事件。汇总回答「这个任务一共收了多少」,事件回答「钱是怎么动的」。 ## 读取账单 ```bash curl --request GET "${CHENVOYA_BASE_URL}/v1/video/tasks/${TASK_ID}/billing" \ --header "Authorization: Bearer ${CHENVOYA_API_KEY}" ``` ```python import urllib.request import urllib.parse import json import os import sys import re import uuid key = os.environ["CHENVOYA_API_KEY"].strip() if not key or re.search(r"[\x00-\x1f\x7f-\x9f]", key): sys.exit("Set CHENVOYA_API_KEY to a non-empty value without control characters") base = os.environ["CHENVOYA_BASE_URL"].rstrip("/") headers = {"Authorization": "Bearer " + key} TASK_ID = os.environ["TASK_ID"] path = "/v1/video/tasks/{task_id}/billing".replace("{task_id}", urllib.parse.quote(TASK_ID, safe="")) request = urllib.request.Request(base + path, data=None, headers=headers, method="GET") try: with urllib.request.urlopen(request) as response: print(response.read().decode("utf-8")) except urllib.request.HTTPError as error: print(error.read().decode("utf-8")) sys.exit(1) ``` ```javascript // 需要 Node.js 20 及以上版本,保存为 .mjs 文件后运行。 const key = process.env.CHENVOYA_API_KEY?.trim(); if (!key || /[\x00-\x1f\x7f-\x9f]/.test(key)) { console.error("Set CHENVOYA_API_KEY to a non-empty value without control characters"); process.exit(1); } const base = process.env.CHENVOYA_BASE_URL; if (!base) throw new Error("Set CHENVOYA_BASE_URL"); const headers = {Authorization: `Bearer ${key}`}; const TASK_ID = process.env.TASK_ID; if (!TASK_ID) throw new Error("Set TASK_ID"); const path = "/v1/video/tasks/{task_id}/billing".replace("{task_id}", encodeURIComponent(TASK_ID)); const response = await fetch(base.replace(/\/$/, "") + path, {method: "GET", headers, body: undefined}); console.log(await response.text()); if (!response.ok) process.exitCode = 1; ``` ```go package main import ( "fmt" "io" "net/http" "net/url" "os" "regexp" "strings" ) func main() { key := strings.TrimSpace(os.Getenv("CHENVOYA_API_KEY")) if key == "" || regexp.MustCompile(`[\x00-\x1f\x7f-\x9f]`).MatchString(key) { fail("Set CHENVOYA_API_KEY to a non-empty value without control characters") } base := strings.TrimRight(os.Getenv("CHENVOYA_BASE_URL"), "/") if base == "" { fail("Set CHENVOYA_BASE_URL") } taskID := os.Getenv("TASK_ID") if taskID == "" { fail("Set TASK_ID") } path := strings.Replace("/v1/video/tasks/{task_id}/billing", "{task_id}", url.PathEscape(taskID), 1) request, err := http.NewRequest("GET", base+path, nil) if err != nil { fail(err.Error()) } request.Header.Set("Authorization", "Bearer "+key) response, err := http.DefaultClient.Do(request) if err != nil { fail(err.Error()) } text, err := io.ReadAll(response.Body) response.Body.Close() if err != nil { fail(err.Error()) } fmt.Println(string(text)) if response.StatusCode < 200 || response.StatusCode > 299 { os.Exit(1) } } func fail(message string) { fmt.Fprintln(os.Stderr, message) os.Exit(1) } ``` 汇总属于整个任务,不会因为事件分页变成当前页的小计。事件按 `cursor` 和 `page_size` 翻页,最后一页的 `next_cursor` 为 `null`。 ## 汇总字段 | 字段 | 含义 | | --- | --- | | `reserved_amount` | 任务预留的金额。 | | `charged_amount` | 任务的计费金额。 | | `funding_debit_amount` | 实际从余额扣减的金额。 | | `funding_refund_amount` | 实际退回余额的金额。 | | `outstanding_receivable` | 尚未收取的应收金额。 | | `final_actual_cost` | 最终实际费用;结算确定前为 `null`。 | ## 账单事件 每次资金变动都记一条事件,例如创建预留的 `hold_created`、收取费用的 `charge_captured`、释放预留的 `hold_released`。预留的创建、增加、减少和释放本身不表示扣款、退款或应收变动,金额以各金额字段为准。全部事件类型见「[状态与原因](./api-docs-zh-api-statuses.md#billing-event)」。 ## 需要核对时 少数任务的用量需要核对,这时 `settlement_status` 为 `reconciliation_required`,`final_actual_cost` 为 `null`,`reconciliation` 给出核对原因 `reason_code`、缺少的计量字段 `missing_meters`,以及是否需要你操作的 `customer_action_required`。核对完成后结算状态变为 `settled` 或 `settled_with_receivable`。 ## 关联标识 `request_id` 和 `billing_correlation_id` 在任务、账单和回调里保持一致,可以用来把你的订单和我们的账单对上。 ## 下一步 - [计费方式](./api-docs-zh-billing.md):创建时按上限预留,完成后按实际用量结算,失败和取消的任务不收费。 - [查询单任务账单](./api-docs-zh-api-video-tasks-billing.md):读取本团队任务的稳定账单汇总及一页有序、不可变的账单事件。 - [状态与原因](./api-docs-zh-api-statuses.md):任务、交付、结算、素材和账单的全部取值。