主题
大批量发票交付
发票 ALL 有三种交付方式,在创建授权时通过 invoice_delivery_mode 选定。三种模式交付的都是同一份原始 invoice.v1 数据, 只是取数通道不同。
| 模式 | 适用 | 通道 |
|---|---|---|
LEGACY(默认) | 小批量 | /api/channel/data/invoice/all 分页 |
CURSOR | 边拉边处理,内存占用可控 | POST /api/v2/channel/invoices/cursor |
PACKAGE | 大批量离线导入 | 状态 → 清单 → 逐片下载 |
只影响发票 ALL
CURSOR / PACKAGE 不影响 Worker 原始文件,也不影响发票 STANDARD。 PACKAGE 模式同时可用游标接口;CURSOR 模式不能用分片接口。
V2 接口强制签名
三个 V2 POST 接口必须同时携带 X-Channel-Key、X-Channel-Timestamp、 X-Channel-Nonce、X-Channel-Signature,见鉴权与签名。 请求体禁止额外字段,且不需要 channelCode。
CURSOR · 游标续拉
POST /api/v2/channel/invoices/cursor
json
{
"channelOrderNo": "BANK-20260812-0001",
"orderNo": "1e3f2f55-8da4-4ba9-b941-84860eb1d243",
"limit": 500
}| 字段 | 说明 |
|---|---|
cursor | 上一批返回的 nextCursor;首次调用省略 |
limit | 本批条数,默认 500,服务端上限 2,000;也接受 pageSize |
响应:
json
{
"success": true,
"data": {
"schemaVersion": "invoice.v1",
"datasetId": "8c1a0b2e-8b1e-4a35-9a54-1f1b6b0b8e01",
"status": "READY",
"total": 2841,
"count": 500,
"invoiceCount": 1204,
"invoiceCountStatus": "VERIFIED",
"records": [],
"nextCursor": "eyJ2IjoxLCJzbmFwc2hvdCI6Ii4uLiJ9.abcdef",
"hasMore": true,
"expiresAt": "2026-08-19T02:00:00Z"
}
}终止条件:hasMore === false 或 nextCursor === null。 游标有效期 24 小时,过期返回 INVOICE_CURSOR_EXPIRED。
python
cursor = None
while True:
body = signed_post(
"/api/v2/channel/invoices/cursor",
{
"channelOrderNo": ORDER_ID,
"orderNo": job_id,
"cursor": cursor,
"limit": 500,
},
)
data = body["data"]
store_batch(data["records"]) # 逐批落库,以 cursor 做幂等
if not data["hasMore"] or not data["nextCursor"]:
break
cursor = data["nextCursor"]PACKAGE · 分片包
mermaid
sequenceDiagram
participant S as 客户服务端
participant A as 平台 API
participant D as 签名下载地址
S->>A: POST package/status
A-->>S: PENDING / BUILDING
S->>A: POST package/status(重试)
A-->>S: READY + exportId
S->>A: POST package/manifest(exportId)
A-->>S: parts[] + sha256 + downloadUrl
loop 每个分片
S->>D: GET downloadUrl
D-->>S: gzip 分片
S->>S: 校验 sha256,按 partNo 合并
end第 1 步 · 查询状态
POST /api/v2/channel/invoices/package/status
json
{
"channelOrderNo": "BANK-20260812-0001",
"orderNo": "1e3f2f55-8da4-4ba9-b941-84860eb1d243"
}调用本接口会触发分片包的准备流程,可安全重复调用。 status 变为 READY 后才能取清单。
json
{
"success": true,
"data": {
"mode": "PACKAGE",
"schemaVersion": "invoice.v1",
"datasetId": "8c1a0b2e-8b1e-4a35-9a54-1f1b6b0b8e01",
"status": "READY",
"recordCount": 2841,
"exportId": "3f4d1e77-2c9a-4b21-8f60-5d7c0a1b2c3d",
"partCount": 3,
"totalSize": 5242880,
"manifestSha256": "…",
"manifestEndpoint": "/api/v2/channel/invoices/package/manifest",
"expiresAt": "2026-08-19T02:00:00Z"
}
}第 2 步 · 获取清单
POST /api/v2/channel/invoices/package/manifest
json
{
"channelOrderNo": "BANK-20260812-0001",
"orderNo": "1e3f2f55-8da4-4ba9-b941-84860eb1d243",
"exportId": "3f4d1e77-2c9a-4b21-8f60-5d7c0a1b2c3d"
}json
{
"success": true,
"data": {
"format": "JSONL",
"compression": "gzip",
"totalRecords": 2841,
"chunkSize": 1000,
"partCount": 3,
"parts": [
{
"partNo": 1,
"fileName": "invoices-part-1.jsonl.gz",
"recordCount": 1000,
"firstSequence": 1,
"lastSequence": 1000,
"fileSize": 1747626,
"sha256": "…",
"downloadUrl": "https://rpa-api.xiaowu005.xyz/api/v2/channel/invoices/packages/…/parts/1/download?expires=…&signature=…"
}
]
}
}第 3 步 · 下载分片
GET /api/v2/channel/invoices/packages/{export_id}/parts/{part_no}/download
下载规则
- 直接使用响应中的
downloadUrl,不得自行拼接 URL。 - 不要为下载请求附加渠道签名请求头 —— 链接本身已带
expires与signature。 - 下载后必须用清单中的
sha256校验,再按partNo顺序合并。 - 支持断点续传(
Accept-Ranges: bytes)。
python
import gzip
import hashlib
import json
import requests
for part in manifest["parts"]:
raw = requests.get(part["downloadUrl"], timeout=300).content
if hashlib.sha256(raw).hexdigest() != part["sha256"]:
raise RuntimeError(f"分片 {part['partNo']} 校验失败")
for line in gzip.decompress(raw).decode("utf-8").splitlines():
store_record(json.loads(line))错误处理
| HTTP | code | 处理 |
|---|---|---|
| 400 | INVOICE_CURSOR_INVALID | 游标不属于本数据集,从头重拉 |
| 400 | INVOICE_EXPORT_NOT_FOUND | exportId 不属于本订单,重新查状态 |
| 404 | INVOICE_ORDER_NOT_FOUND | 核对 channelOrderNo 与 orderNo |
| 409 | INVOICE_DELIVERY_MODE_MISMATCH | 本单未申请该交付模式 |
| 409 | INVOICE_DATASET_NOT_READY | 快照未就绪,等产品成功后重试 |
| 409 | INVOICE_EXPORT_NOT_READY | 继续查询 package/status |
| 409 | INVOICE_DATASET_EXPIRED / INVOICE_CURSOR_EXPIRED / INVOICE_EXPORT_EXPIRED | 停止续拉,联系平台 |
| 409 | CHANNEL_REQUEST_REPLAYED | Nonce 重复,换新 Nonce 重发 |
已签发的链接继续有效
已签发的 CURSOR、快照、PACKAGE、分片哈希及下载地址在字段兼容升级后继续有效; 新任务和明确重建的 PACKAGE 才会包含新的兼容键。