openapi: 3.1.0

info:
  title: 税务发票数据开放平台 · 客户 API
  version: 1.5.3
  x-platform-version: 1.5.3
  x-customer-contract: R4.4
  x-release-date: '2026-08-24'
  summary: 面向渠道客户的税务与发票数据采集、交付接口
  description: |
    本规范定义**渠道客户**（下称"客户"）可直接调用的 17 个接口，覆盖从创建授权链接、
    等待采集结果、到获取税务/发票数据与报告的完整链路。平台管理端、Worker 节点、
    授权页面自身使用的内部接口不在本规范范围内。

    当前规范已按平台 **1.5.3** 核对，客户交付基线继续使用 **R4.4**。1.5.3 未改变
    这 17 条接口路径，但补充了部分采集质量警告、TAX ALL 映射快照固化，以及
    HTML 详版/简版报告的新统计口径。

    ## 接口分层

    | 层 | 路径前缀 | 鉴权 | 用途 |
    | --- | --- | --- | --- |
    | 授权与订单 | `/api/v1/channel` | API Key | 创建授权链接、查询订单与产品状态、下载原始制品 |
    | 数据获取 | `/api/channel/data` | API Key（可强制签名） | 税务/发票结构化数据、PDF 报告状态与下载 |
    | 大批量发票交付 | `/api/v2/channel/invoices` | API Key + HMAC 签名 | 游标续拉、分片包状态/清单/下载 |
    | 公开报告页 | `/api/v1/public/html-reports` | Bearer Capability Token | HTML 在线报告 |

    ## 调用约定

    - 所有 POST 请求体为 JSON，须设置 `Content-Type: application/json; charset=utf-8`。
    - 所有时间字段为 ISO 8601（UTC）；签名时间戳为 Unix 秒。
    - 金额、数量等业务字段一律以字符串或数值原样透传，客户端解析器必须容忍新增字段
      （`additionalProperties: true`）。
    - 数据获取接口沿用**业务码信封**（`code` / `msg` / `data` / `success`），
      HTTP 状态码保持 `200`；鉴权与参数级错误则使用标准 HTTP 错误码与
      `{ code, message, details }` 结构。

    ## 两套返回结构

    | 结构 | 使用接口 | 判定成功 |
    | --- | --- | --- |
    | 业务码信封 | `/api/channel/data/*` | `success === true`（`code` 为 `0` 或 `200`） |
    | 标准 REST | `/api/v1/channel/*`、`/api/v2/channel/*` | HTTP 2xx |

    > 当订单启用 `output_encryption=SM4` 时，上述**成功**的 JSON 报文整体被替换为
    > SM4-GCM 密文信封（见 `Sm4Envelope`）；错误响应始终保持明文。

  contact:
    name: 平台技术支持
    url: https://rpa-api.xiaowu005.xyz/health
  license:
    name: 商业授权 · 仅限已签约渠道使用
    identifier: LicenseRef-Proprietary

servers:
  - url: https://rpa-api.xiaowu005.xyz
    description: 生产环境
  - url: https://rpa-test-api.xiaowu005.xyz
    description: 测试环境（开发联调、签名验证、异常场景）

tags:
  - name: 授权与订单
    description: |
      创建授权链接是一切业务的起点。客户服务端在请求体中声明采集范围、输出类型、
      交付方式与回调配置，平台返回本次授权对应的 `authorization_url`，
      由客户前端直接跳转。之后通过订单/产品查询接口跟踪采集状态。
  - name: 税务数据
    description: |
      税务共两种输出：`ALL` 为税局原始/映射数据（固定 37 个顶层节点），
      `STANDARD` 为平台加工后的标准化结构（固定 24 个模块）。
      只能获取创建授权时已选择的输出类型。同一任务的 TAX ALL 快照会固化
      已发布映射修订与目录哈希，重复查询不会因后台后续发布而改变。
  - name: 发票数据
    description: |
      发票同样区分 `ALL`（税局源列透传，分页返回）与 `STANDARD`
      （平台加工，固定 13 个模块）。发票数据量大时应改用 V2 的
      游标或分片包交付。
  - name: 报告
    description: |
      PDF 报告与 HTML 在线报告是两个独立开关：`pdf_report_types` 控制可下载 PDF，
      `generate_html_reports` 控制在线 HTML 版。两者都要求同时采集税务与发票。
  - name: 大批量发票交付
    description: |
      `CURSOR` 适合边拉边处理，`PACKAGE` 适合大批量离线下载。
      这三个 POST 接口强制要求 HMAC 签名头。

security:
  - ChannelKey: []

paths:
  ##########################################################################
  # 1. 授权与订单
  ##########################################################################
  /api/v1/channel/authorization-links:
    post:
      tags: [授权与订单]
      operationId: createAuthorizationLink
      summary: 创建授权链接
      description: |
        创建一次企业授权申请，返回授权页面地址。

        **幂等**：同一渠道内 `external_order_id` 唯一。完全相同的活动请求可安全重试；
        若企业、税号、回调地址、采集范围、输出类型、加密开关、保留期或发票期间等
        任一要素发生变化，返回 `409 EXTERNAL_ORDER_CONFLICT`。
        链接未提交且已过期后，可用同一 `external_order_id` 创建新的授权代次。

        **约束**
        - `collect_tax` 与 `collect_invoice` 至少一项为 `true`。
        - 申请 PDF 报告或 HTML 报告时，必须同时采集税务和发票。
        - 客户前端必须直接打开返回的 `authorization_url`，不得自行拼接授权页地址。
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/AuthorizationLinkRequest'
            examples:
              最小请求:
                summary: 只提交必填字段，其余走默认值
                value:
                  external_order_id: BANK-20260812-0001
                  enterprise_name: 示例企业有限公司
                  taxpayer_id: 91420100XXXXXXXXXX
              完整请求:
                summary: 税务 + 发票、STANDARD 输出、PDF 与 HTML 报告、SM4 加密
                value:
                  external_order_id: BANK-20260812-0001
                  enterprise_name: 示例企业有限公司
                  taxpayer_id: 91420100XXXXXXXXXX
                  success_url: https://bank.example.com/application/continue
                  exit_url: https://bank.example.com/application/authorization
                  callback_url: https://bank.example.com/rpa/callback
                  callback_mode: AGGREGATE
                  collect_tax: true
                  collect_invoice: true
                  tax_data_types: [ALL, STANDARD]
                  invoice_data_types: [ALL, STANDARD]
                  invoice_period: LAST_36_MONTHS
                  invoice_delivery_mode: CURSOR
                  pdf_report_types: [TAX_INVOICE_DETAIL, TAX_INVOICE_SUMMARY]
                  generate_html_reports: true
                  output_encryption: SM4
                  data_retention_days: 90
                  expires_in_seconds: 1800
                  extras_data:
                    applicationId: LOAN-88213
              仅发票原始数据:
                summary: 只采发票、只要 ALL、使用分片包交付
                value:
                  external_order_id: BANK-20260812-0002
                  enterprise_name: 示例企业有限公司
                  taxpayer_id: 91420100XXXXXXXXXX
                  collect_tax: false
                  collect_invoice: true
                  invoice_data_types: [ALL]
                  invoice_delivery_mode: PACKAGE
                  callback_url: https://bank.example.com/rpa/callback
      responses:
        '200':
          description: 授权链接创建成功
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthorizationLinkResponse'
              example:
                authorization_code: 550e8400-e29b-41d4-a716-446655440000
                authorization_url: https://rpa-auth.xiaowu005.xyz/user/authorization/taxXyd?code=550e8400-e29b-41d4-a716-446655440000
                external_order_id: BANK-20260812-0001
                invoice_period: LAST_36_MONTHS
                invoice_start_date: '2023-08-12'
                invoice_end_date: '2026-08-12'
                expires_at: '2026-08-12T10:30:00Z'
                dataDeliveryMode: STRUCTURED
                generateHtmlReports: true
                outputEncryption: SM4
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          description: 渠道未被授权生成所请求的定制报告
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError'
              example:
                code: REPORT_NOT_ENTITLED
                message: Channel is not entitled to the requested custom report
                details: {}
        '409':
          description: |
            幂等冲突或前置条件不满足。常见 `code`：
            `EXTERNAL_ORDER_CONFLICT`（同一订单号绑定了不同的授权数据）、
            `TAX_URL_MISSING`（该纳税人所在地区尚未配置税局入口）。
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError'
              example:
                code: EXTERNAL_ORDER_CONFLICT
                message: External order id is already bound to different authorization data
                details: {}
        '422':
          $ref: '#/components/responses/ValidationError'

  /api/v1/channel/orders/{external_order_id}:
    get:
      tags: [授权与订单]
      operationId: getChannelOrder
      summary: 查询订单
      description: |
        返回该业务订单**最新授权代次**的整体状态。

        `state` 为 `SUCCESS` 或 `ERROR` 时表示已结束；`CONNECT` / `PROCESS` /
        `INTERACTION` 表示仍在进行中。轮询间隔不得低于 15 秒，建议 15–30 秒。

        平台 1.5.3 中，非阻断的税务子项目异常可以在保留已有数据后让订单保持
        `SUCCESS`。此时 `qualityStatus=WARNING`、
        `collectionResult=COMPLETED_WITH_WARNINGS`，详细范围见 `dataQuality` 与
        `qualityIssues`；客户端不能只用 `state` 推断“所有来源均完整”。

        状态查询是回调通知的**补充**手段，不应作为主链路。
      parameters:
        - $ref: '#/components/parameters/ExternalOrderId'
      responses:
        '200':
          description: 订单状态
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: '#/components/schemas/OrderResult'
                  - $ref: '#/components/schemas/Sm4Envelope'
              example:
                external_order_id: BANK-20260812-0001
                authorization_code: 550e8400-e29b-41d4-a716-446655440000
                job_id: 1e3f2f55-8da4-4ba9-b941-84860eb1d243
                attempt_id: a2ad4ec4-b878-42c5-b623-4d962f350e72
                state: SUCCESS
                dataDeliveryMode: STRUCTURED
                invoice_period: LAST_36_MONTHS
                invoice_start_date: '2023-08-12'
                invoice_end_date: '2026-08-12'
                qualityStatus: PASSED
                qualityIssues: []
                artifacts: []
                updated_at: '2026-08-12T10:41:07+00:00'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          description: 订单不存在（`CHANNEL_ORDER_NOT_FOUND`）
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError'

  /api/v1/channel/orders/{external_order_id}/products/{product}:
    get:
      tags: [授权与订单]
      operationId: getChannelProductOrder
      summary: 查询税务/发票产品状态
      description: |
        按产品粒度返回采集状态、数据集清单与质量信息。

        **只有 `state = SUCCEEDED` 且 `available = true` 时才可以去取数。**
        `FAILED` 表示该产品最终失败，`SKIPPED` 表示本单未执行该产品。
        `SUCCEEDED + available=true` 代表数据可用，不等于所有可选来源都完整；
        1.5.3 的部分结果通过 `qualityStatus`、`collectionResult`、`dataQuality`
        和 `qualityIssues` 明确披露，客户端应一并保存。
        PDF 状态不在这里查询，应调用 `POST /api/channel/data/reports/status`。
      parameters:
        - $ref: '#/components/parameters/ExternalOrderId'
        - name: product
          in: path
          required: true
          description: 产品标识
          schema:
            type: string
            enum: [TAX, INVOICE]
          example: TAX
      responses:
        '200':
          description: 产品状态
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: '#/components/schemas/ProductResult'
                  - $ref: '#/components/schemas/Sm4Envelope'
              example:
                external_order_id: BANK-20260812-0001
                authorization_code: 550e8400-e29b-41d4-a716-446655440000
                job_id: 1e3f2f55-8da4-4ba9-b941-84860eb1d243
                attempt_id: a2ad4ec4-b878-42c5-b623-4d962f350e72
                product: TAX
                state: SUCCEEDED
                available: true
                dataDeliveryMode: STRUCTURED
                dataset_count: 37
                record_count: 12840
                qualityStatus: PASSED
                qualityIssues: []
                datasets:
                  - id: ds-0001
                    name: 企业基础信息
                    product: tax
                    row_count: 1
                artifacts: []
                error_code: null
                error_message: null
                retryable: false
                available_at: '2026-08-12T10:41:07Z'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          description: |
            订单不存在（`CHANNEL_ORDER_NOT_FOUND`），
            或本单未申请该产品（`PRODUCT_NOT_REQUESTED`）。
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError'
        '422':
          description: '`product` 取值非法（`INVALID_PRODUCT`）'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError'

  /api/v1/channel/orders/{external_order_id}/artifacts/{artifact_id}/download:
    get:
      tags: [授权与订单]
      operationId: downloadRawArtifact
      summary: 下载 Worker 原始文件
      description: |
        下载 `WORKER_RAW` 交付模式下的原始表格文件（通常为 XLSX）。

        - 只有对应产品的输出类型数组中包含 `WORKER_RAW` 时才会给出 `downloadUrl`。
        - **必须直接使用响应中的 `downloadUrl`，并携带本渠道 `X-Channel-Key`。**
        - 下载后必须用响应头 `X-Content-SHA256` 校验文件完整性。
        - 平台只开放当前有效批次中与本单采集范围匹配的数据制品；
          MANIFEST、诊断日志、被质量门禁隔离的文件不会开放。
        - 文件随本订单 `data_retention_days` 到期清理，清理后返回 `410`。
        - `output_encryption=SM4` 不改写原始表格文件字节。
      parameters:
        - $ref: '#/components/parameters/ExternalOrderId'
        - name: artifact_id
          in: path
          required: true
          description: 制品 ID，取自订单/产品查询响应的 `artifacts[].id`
          schema:
            type: string
          example: 0f2a5f7c-1c3d-4d16-9f34-6c22b8f9a911
      responses:
        '200':
          description: 文件流
          headers:
            X-Content-SHA256:
              description: 文件内容 SHA-256，用于完整性校验
              schema:
                type: string
                pattern: '^[0-9a-f]{64}$'
            ETag:
              description: 与 SHA-256 一致的强校验标签
              schema:
                type: string
            Accept-Ranges:
              description: 支持断点续传
              schema:
                type: string
                example: bytes
            Cache-Control:
              schema:
                type: string
                example: private, no-store
          content:
            application/octet-stream:
              schema:
                type: string
                format: binary
            application/vnd.openxmlformats-officedocument.spreadsheetml.sheet:
              schema:
                type: string
                format: binary
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          description: |
            订单不存在（`CHANNEL_ORDER_NOT_FOUND`），
            或该制品不可开放（`RAW_ARTIFACT_NOT_AVAILABLE`）。
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError'
        '410':
          description: 文件已按保留期清理（`RAW_ARTIFACT_EXPIRED`）
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError'

  ##########################################################################
  # 2. 税务数据
  ##########################################################################
  /api/channel/data/tax/all:
    post:
      tags: [税务数据]
      operationId: getTaxAll
      summary: 获取税务 ALL
      description: |
        返回税局原始/映射数据。当前固定为 V2 结构：**37 个顶层节点**，
        在原 35 个节点末尾追加 `taxCompanyEnterpriseExtendedInfoDtoPageResult`
        与 `taxDeclarationHandlerInfoDtoPageResult` 两张扩展表。
        请求不需要也不接受版本参数。

        平台 1.5.3 会在首次生成本任务 TAX ALL 快照时记录已发布映射修订和目录哈希；
        同一 `job_id + attempt_id` 的后续查询复用该快照，不受后台后来发布、回滚
        或停用映射的影响。

        成功业务码为 **`200`**。
      security:
        - ChannelKey: []
        - ChannelKey: []
          ChannelTimestamp: []
          ChannelNonce: []
          ChannelSignature: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/DataOrderRef'
            example:
              channelCode: BANK
              channelOrderNo: BANK-20260812-0001
              orderNo: 1e3f2f55-8da4-4ba9-b941-84860eb1d243
      responses:
        '200':
          description: 业务码信封。`code=200` 表示取数成功。
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: '#/components/schemas/TaxAllEnvelope'
                  - $ref: '#/components/schemas/Sm4Envelope'
              examples:
                成功:
                  value:
                    code: 200
                    msg: 操作成功
                    message: 操作成功
                    success: true
                    data:
                      taxCompanyEnterpriseInfoDtoPageResult:
                        - nsrmc: 示例企业有限公司
                          nsrsbh: 91420100XXXXXXXXXX
                      taxDeclarationHandlerInfoDtoPageResult: []
                采集中:
                  value:
                    code: 60003
                    msg: 数据采集中
                    message: 数据采集中
                    success: false
                    data: null
                未选择该输出:
                  value:
                    code: 10001
                    msg: 本任务未选择税务 ALL 输出
                    message: 本任务未选择税务 ALL 输出
                    success: false
                    data: null
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/ChannelForbidden'
        '409':
          $ref: '#/components/responses/ReplayedNonce'

  /api/channel/data/tax/standardTable:
    post:
      tags: [税务数据]
      operationId: getTaxStandard
      summary: 获取税务 STANDARD
      description: |
        返回平台加工后的标准化税务结构 `tax-standard.v2`。

        请求**不提供**模块筛选参数：响应的 `selectedModules` 固定列出完整
        **24 个模块**，`moduleStatus` 与 `data` 覆盖全部模块。无数据模块按契约
        返回空对象或空数组并附带状态（如 `EMPTY`、`INSUFFICIENT_SOURCE`、
        `UNSUPPORTED`），不会伪造记录。

        成功业务码为 **`200`**。
      security:
        - ChannelKey: []
        - ChannelKey: []
          ChannelTimestamp: []
          ChannelNonce: []
          ChannelSignature: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/DataOrderRef'
            example:
              channelCode: BANK
              channelOrderNo: BANK-20260812-0001
              orderNo: 1e3f2f55-8da4-4ba9-b941-84860eb1d243
      responses:
        '200':
          description: 业务码信封。`code=200` 表示取数成功。
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: '#/components/schemas/TaxStandardEnvelope'
                  - $ref: '#/components/schemas/Sm4Envelope'
              example:
                code: 200
                msg: 操作成功
                message: 操作成功
                success: true
                data:
                  schemaVersion: tax-standard.v2
                  generatedAt: '2026-08-12T10:41:07+00:00'
                  taxpayerId: 91420100XXXXXXXXXX
                  companyName: 示例企业有限公司
                  dataAsOf: '2026-07-31'
                  selectedModules: [NSRJBXX, RYXX, TZFXX, NSSBXX, ZZSSBMX, QYSDSSBMX, JKXX, QSXX, SBGZ, SWBG, SBFXX, JCWS, NSXYDJLS, SSWFWZXX, YSHDXX, LRBNB, LRBJB, ZCFZBNB, ZCFZBJB, XJLLBNB, XJLLBJB, MYSJSEHJ, JDSJSETJ, QYCWZBXX]
                  sourceFingerprint: 'sha256:0000000000000000000000000000000000000000000000000000000000000000'
                  sourceWarnings: []
                  fieldWarnings: []
                  moduleStatus:
                    NSRJBXX:
                      status: AVAILABLE
                      recordCount: 1
                      message: ''
                    QSXX:
                      status: EMPTY
                      recordCount: 0
                      message: ''
                    SBFXX:
                      status: INSUFFICIENT_SOURCE
                      recordCount: 0
                      message: 当前 Worker 未采集社保费申报明细
                  data:
                    NSRJBXX:
                      NSRMC: 示例企业有限公司
                      NSRSBH: 91420100XXXXXXXXXX
                    QSXX: []
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/ChannelForbidden'
        '409':
          $ref: '#/components/responses/ReplayedNonce'

  /api/channel/data/tax/results:
    post:
      tags: [税务数据]
      operationId: getTaxResults
      summary: 组合获取税务结果
      description: |
        一次调用取回本单已授权的多种税务输出，避免多次往返。

        `taxDataTypes` 省略时默认取本次授权已选择的全部类型；显式传入时必须是
        授权范围的**子集**，否则返回 `10001`。
        `WORKER_RAW` 返回原始制品清单（含 `downloadUrl`），
        `ALL` / `STANDARD` 返回与对应单接口一致的 `data`。

        任一子结果未就绪时，整体返回该子结果的业务码（如 `60003`），
        不会返回部分数据。成功业务码为 **`200`**。
      security:
        - ChannelKey: []
        - ChannelKey: []
          ChannelTimestamp: []
          ChannelNonce: []
          ChannelSignature: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/TaxResultsRequest'
            example:
              channelCode: BANK
              channelOrderNo: BANK-20260812-0001
              orderNo: 1e3f2f55-8da4-4ba9-b941-84860eb1d243
              taxDataTypes: [ALL, STANDARD]
      responses:
        '200':
          description: 业务码信封。`code=200` 表示取数成功。
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: '#/components/schemas/TaxResultsEnvelope'
                  - $ref: '#/components/schemas/Sm4Envelope'
              example:
                code: 200
                msg: 操作成功
                message: 操作成功
                success: true
                data:
                  ALL:
                    taxCompanyEnterpriseInfoDtoPageResult: []
                  STANDARD:
                    schemaVersion: tax-standard.v2
                    selectedModules: []
                    moduleStatus: {}
                    data: {}
                  WORKER_RAW:
                    artifacts:
                      - id: 0f2a5f7c-1c3d-4d16-9f34-6c22b8f9a911
                        kind: TAX_DATA
                        filename: 001-tax-result.xlsx
                        size: 102400
                        sha256: '0000000000000000000000000000000000000000000000000000000000000000'
                        downloadUrl: https://rpa-api.xiaowu005.xyz/api/v1/channel/orders/BANK-20260812-0001/artifacts/0f2a5f7c-1c3d-4d16-9f34-6c22b8f9a911/download
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/ChannelForbidden'
        '409':
          $ref: '#/components/responses/ReplayedNonce'

  ##########################################################################
  # 3. 发票数据
  ##########################################################################
  /api/channel/data/invoice/all:
    post:
      tags: [发票数据]
      operationId: getInvoiceAll
      summary: 获取发票 ALL（分页）
      description: |
        分页返回税局源列透传的原始发票记录。默认每页 100 条，上限 1,000 条。

        **成功业务码是 `0`（不是 200）** —— 历史客户端据此自动翻页取回剩余分页。

        每条记录额外附带四个双下划线前缀的来源元数据：
        `__sourceDatasetName`、`__sourceArtifactName`、`__sourceRecordOrdinal`、
        `__recordRole`。同一张发票的多个商品行可能对应多条记录，
        统计发票张数时应使用 `invoiceCount` 而非 `total`。

        分页应逐页流式落库，并以 `orderNo + pageNum` 做幂等；某页失败只重试该页。
      security:
        - ChannelKey: []
        - ChannelKey: []
          ChannelTimestamp: []
          ChannelNonce: []
          ChannelSignature: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/InvoiceAllRequest'
            example:
              channelCode: BANK
              channelOrderNo: BANK-20260812-0001
              orderNo: 1e3f2f55-8da4-4ba9-b941-84860eb1d243
              pageNum: 1
              pageSize: 500
      responses:
        '200':
          description: 业务码信封。`code=0` 表示取数成功。
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: '#/components/schemas/InvoiceAllEnvelope'
                  - $ref: '#/components/schemas/Sm4Envelope'
              examples:
                成功:
                  value:
                    code: 0
                    msg: 操作成功
                    message: 操作成功
                    success: true
                    data:
                      current: 1
                      size: 500
                      pages: 6
                      total: 2841
                      searchCount: true
                      optimizeCountSql: true
                      countId: null
                      maxLimit: null
                      orders: []
                      records:
                        - qdfphm: '24422000000012345678'
                          spmc: 技术服务费
                          je: '1000.00'
                          invoiceTypeName: 数电票（增值税专用发票）
                          __recordRole: INVOICE_LINE
                          __sourceDatasetName: 信息汇总表
                          __sourceArtifactName: 002-invoice-result.xlsx
                          __sourceRecordOrdinal: 1
                分页参数错误:
                  value:
                    code: 10001
                    msg: 分页参数格式错误
                    message: 分页参数格式错误
                    success: false
                    data: null
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/ChannelForbidden'
        '409':
          $ref: '#/components/responses/ReplayedNonce'

  /api/channel/data/invoice/standard:
    post:
      tags: [发票数据]
      operationId: getInvoiceStandard
      summary: 获取发票 STANDARD
      description: |
        返回平台加工后的标准化发票结构 `invoice-standard.v2`，
        `selectedModules` 固定列出完整 **13 个模块**。

        STANDARD 是**独立加工结构**，字段必填/可空规则以专用字典为准，
        不能套用原始发票（ALL）的规则。响应固定包含 `SJZL` 加工质量对象，
        披露可靠票数、缺号记录、方向无法判断、重复金额抑制等边界情况。

        成功业务码为 **`200`**。
      security:
        - ChannelKey: []
        - ChannelKey: []
          ChannelTimestamp: []
          ChannelNonce: []
          ChannelSignature: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/DataOrderRef'
            example:
              channelCode: BANK
              channelOrderNo: BANK-20260812-0001
              orderNo: 1e3f2f55-8da4-4ba9-b941-84860eb1d243
      responses:
        '200':
          description: 业务码信封。`code=200` 表示取数成功。
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: '#/components/schemas/InvoiceStandardEnvelope'
                  - $ref: '#/components/schemas/Sm4Envelope'
              example:
                code: 200
                msg: 操作成功
                message: 操作成功
                success: true
                data:
                  schemaVersion: invoice-standard.v2
                  generatedAt: '2026-08-12T10:41:07+00:00'
                  taxpayerId: 91420100XXXXXXXXXX
                  companyName: 示例企业有限公司
                  selectedModules:
                    - UPSTREAM_ANNUAL
                    - TRANSACTION_OVERVIEW
                    - SALES_ANALYSIS
                    - PURCHASE_ANALYSIS
                    - EXPENSE_ANALYSIS
                    - MONTHLY_TOP_DOWNSTREAM_DEALERS
                    - MONTHLY_TOP_DOWNSTREAM_PRODUCTS
                    - MONTHLY_INVOICE_SUMMARY
                    - DOWNSTREAM_INFO
                    - PRODUCT_INFO
                    - TOP_SUPPLIERS
                    - MONTHLY_INPUT_INVOICE_SUMMARY
                    - MONTHLY_OUTPUT_INVOICE_STATISTICS
                  sourceRecordCount: 2841
                  effectiveRecordCount: 2790
                  duplicateRecordCount: 0
                  invoiceCount: 1204
                  SJZL:
                    invoiceCountBasis: IDENTIFIED_KEY_OR_BASE_RECORD
                    invoiceCountComplete: true
                    identifiedInvoiceCount: 1204
                    unclassifiedDirectionRecordCount: 0
                    ignoredProductSummaryRecordCount: 12
                  JYGL:
                    FPZS: 1204
                    XSJEHJ: '12840000.00'
                    CGJEHJ: '9120000.00'
                    FPSEHJ: '1667200.00'
                    MLL: 28.97
                  XSFX:
                    KSRQ: '2023-08-12'
                    JSRQ: '2026-07-31'
                  SXYNDXX: []
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/ChannelForbidden'
        '409':
          $ref: '#/components/responses/ReplayedNonce'

  /api/channel/data/invoice/results:
    post:
      tags: [发票数据]
      operationId: getInvoiceResults
      summary: 组合获取发票结果
      description: |
        一次调用取回本单已授权的多种发票输出。

        `invoiceDataTypes` 省略时默认取本次授权已选择的全部类型；显式传入时必须是
        授权范围的**子集**。`ALL` 子结果仍按分页返回（受 `pageNum` / `pageSize` 控制），
        `STANDARD` 返回标准化结构，`WORKER_RAW` 返回原始制品清单。

        成功业务码为 **`200`**（注意与单接口 `/invoice/all` 的 `0` 不同）。
      security:
        - ChannelKey: []
        - ChannelKey: []
          ChannelTimestamp: []
          ChannelNonce: []
          ChannelSignature: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/InvoiceResultsRequest'
            example:
              channelCode: BANK
              channelOrderNo: BANK-20260812-0001
              orderNo: 1e3f2f55-8da4-4ba9-b941-84860eb1d243
              invoiceDataTypes: [ALL, STANDARD]
              pageNum: 1
              pageSize: 500
      responses:
        '200':
          description: 业务码信封。`code=200` 表示取数成功。
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: '#/components/schemas/InvoiceResultsEnvelope'
                  - $ref: '#/components/schemas/Sm4Envelope'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/ChannelForbidden'
        '409':
          $ref: '#/components/responses/ReplayedNonce'

  ##########################################################################
  # 4. 报告
  ##########################################################################
  /api/channel/data/reports/status:
    post:
      tags: [报告]
      operationId: getReportStatus
      summary: 查询 PDF 报告状态
      description: |
        返回本单申请的每份 PDF 报告的生成状态。报告生成成功后，
        直接使用响应中的 `downloadUrl` 下载，并用返回的 `sha256` 校验文件。

        整体 `status` 为 `PENDING` / `SUCCEEDED` / `PARTIAL` / `FAILED`。
        单份报告在未达最大重试次数前的失败会对外显示为 `PENDING`，
        只有终态失败才显示 `FAILED` 并带上 `errorCode` / `errorMessage`。

        本单未申请任何 PDF 报告时返回业务码 `60002`。
      security:
        - ChannelKey: []
        - ChannelKey: []
          ChannelTimestamp: []
          ChannelNonce: []
          ChannelSignature: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/DataOrderRef'
            example:
              channelCode: BANK
              channelOrderNo: BANK-20260812-0001
              orderNo: 1e3f2f55-8da4-4ba9-b941-84860eb1d243
      responses:
        '200':
          description: 业务码信封。`code=200` 表示查询成功。
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: '#/components/schemas/ReportStatusEnvelope'
                  - $ref: '#/components/schemas/Sm4Envelope'
              example:
                code: 200
                msg: 操作成功
                message: 操作成功
                success: true
                data:
                  status: SUCCEEDED
                  reports:
                    - reportType: TAX_INVOICE_DETAIL
                      status: SUCCEEDED
                      reportVersion: report.v12
                      fileName: 税票报告-详版-BANK-20260812-0001.pdf
                      fileSize: 1843200
                      sha256: '0000000000000000000000000000000000000000000000000000000000000000'
                      pageCount: 26
                      downloadUrl: https://rpa-api.xiaowu005.xyz/api/channel/data/reports/r-0001/download?expires=1786490400&signature=abcdef
                      errorCode: null
                      errorMessage: null
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/ChannelForbidden'
        '409':
          $ref: '#/components/responses/ReplayedNonce'

  /api/channel/data/reports/{report_id}/download:
    get:
      tags: [报告]
      operationId: downloadReport
      summary: 下载 PDF 报告
      description: |
        使用报告状态接口返回的**签名链接**下载 PDF。

        链接自带 `expires` 与 `signature` 查询参数，**不需要**渠道请求头，
        也**不得**自行拼接或改写。下载后必须用响应头 `X-Content-SHA256` 校验文件。
      security: []
      parameters:
        - name: report_id
          in: path
          required: true
          description: 报告 ID
          schema:
            type: string
          example: r-0001
        - name: expires
          in: query
          required: true
          description: 链接过期时间（Unix 秒），由平台签发
          schema:
            type: integer
            format: int64
          example: 1786490400
        - name: signature
          in: query
          required: true
          description: 平台对该链接的 HMAC 签名
          schema:
            type: string
          example: 9f1c0d1a2b3c4d5e6f708192a3b4c5d6e7f8091a2b3c4d5e6f708192a3b4c5d6
      responses:
        '200':
          description: PDF 文件流
          headers:
            X-Content-SHA256:
              schema:
                type: string
                pattern: '^[0-9a-f]{64}$'
            ETag:
              schema:
                type: string
            Accept-Ranges:
              schema:
                type: string
                example: bytes
          content:
            application/pdf:
              schema:
                type: string
                format: binary
        '403':
          description: 签名无效或链接已过期
        '404':
          description: 报告文件不存在

  /api/v1/public/html-reports/{token}:
    get:
      tags: [报告]
      operationId: readHtmlReport
      summary: 查看 HTML 在线报告
      description: |
        `generate_html_reports=true` 时，订单结果与终态回调的 `data` 会增量包含
        `htmlReports`，其中每份报告带有 `accessUrl`。该地址即本接口。

        平台 1.5.3 起，详版和简版使用同一份 ALL 报告事实模型，但按不同章节独立渲染。
        完整红冲组合按蓝字正数与红字负数共同计入，净额为 0；增值税申报与财务报表
        份数按申报批次/所属期去重，不按明细行数累计。

        - 链接是 **Bearer Capability**：持有即可访问，**不携带**渠道请求头，
          客户系统应按敏感凭据保护，不得写入公开日志或第三方分析参数。
        - 链接中不包含 `authorization_code`，生成时固定 **7 天**过期，重复查询不续期。
        - 页面使用 `Cache-Control: no-store`、禁止脚本与外部资源的 CSP，
          默认不允许被 iframe 嵌入。
        - `accessUrl` 使用平台配置的公开域名（生产为 `https://rpa-auth.xiaowu005.xyz`），
          与 API 域名不同，请直接使用返回值，不要自行拼接。
      security: []
      parameters:
        - name: token
          in: path
          required: true
          description: 访问令牌，仅允许 URL 安全字符，长度 3–256
          schema:
            type: string
            minLength: 3
            maxLength: 256
            pattern: '^[A-Za-z0-9._~-]+$'
      responses:
        '200':
          description: HTML 报告页面
          content:
            text/html:
              schema:
                type: string
        '404':
          description: |
            令牌未知或被篡改（`HTML_REPORT_NOT_FOUND`），
            或报告尚未生成完成（`HTML_REPORT_NOT_READY`）。
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError'
        '410':
          description: |
            链接过期、授权被取消/过期，或已被新的授权代次取代
            （`HTML_REPORT_EXPIRED`）。
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError'

  ##########################################################################
  # 5. 大批量发票交付（V2）
  ##########################################################################
  /api/v2/channel/invoices/cursor:
    post:
      tags: [大批量发票交付]
      operationId: getInvoiceCursorPage
      summary: 游标获取发票
      description: |
        以游标方式续拉原始 `invoice.v1` 记录，适合边拉边处理。

        - 首次调用不传 `cursor`；之后每次传入上一批返回的 `nextCursor`。
        - `hasMore = false` 或 `nextCursor = null` 表示已拉完。
        - 默认每批 500 条，服务端上限 2,000 条；游标有效期 24 小时。
        - 本单必须在创建授权时选择 `invoice_delivery_mode` 为 `CURSOR` 或 `PACKAGE`，
          否则返回 `409 INVOICE_DELIVERY_MODE_MISMATCH`。

        **本接口强制要求签名头。** 请求体禁止额外字段（`additionalProperties: false`）。
      security:
        - ChannelKey: []
          ChannelTimestamp: []
          ChannelNonce: []
          ChannelSignature: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/InvoiceCursorRequest'
            examples:
              首次拉取:
                value:
                  channelOrderNo: BANK-20260812-0001
                  orderNo: 1e3f2f55-8da4-4ba9-b941-84860eb1d243
                  limit: 500
              续拉:
                value:
                  channelOrderNo: BANK-20260812-0001
                  orderNo: 1e3f2f55-8da4-4ba9-b941-84860eb1d243
                  cursor: eyJ2IjoxLCJzbmFwc2hvdCI6Ii4uLiJ9.abcdef
                  limit: 500
      responses:
        '200':
          description: 一批发票记录
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: '#/components/schemas/InvoiceCursorResponse'
                  - $ref: '#/components/schemas/Sm4Envelope'
              example:
                success: true
                data:
                  schemaVersion: invoice.v1
                  datasetId: 8c1a0b2e-8b1e-4a35-9a54-1f1b6b0b8e01
                  status: READY
                  total: 2841
                  count: 500
                  invoiceCount: 1204
                  invoiceCountStatus: VERIFIED
                  invoiceCountMethod: INFO_SUMMARY_SHEET
                  recordRoleCounts:
                    INVOICE_HEADER: 1204
                    INVOICE_LINE: 1560
                    WORKBOOK_TOTAL: 77
                  records: []
                  nextCursor: eyJ2IjoxLCJzbmFwc2hvdCI6Ii4uLiJ9.abcdef
                  hasMore: true
                  expiresAt: '2026-08-19T02:00:00Z'
        '400':
          description: |
            交付层错误，常见 `code`：`INVOICE_CURSOR_INVALID`（游标不属于本数据集）。
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          description: 订单或任务号不匹配（`INVOICE_ORDER_NOT_FOUND`）
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError'
        '409':
          description: |
            常见 `code`：`INVOICE_DELIVERY_MODE_MISMATCH`（本单未申请游标交付）、
            `INVOICE_DATASET_NOT_READY`（快照未就绪）、
            `INVOICE_DATASET_EXPIRED` / `INVOICE_CURSOR_EXPIRED`（已过期）、
            `CHANNEL_REQUEST_REPLAYED`（Nonce 重放）。
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError'

  /api/v2/channel/invoices/package/status:
    post:
      tags: [大批量发票交付]
      operationId: getInvoicePackageStatus
      summary: 查询发票分片包状态
      description: |
        查询分片包生成进度。`status` 为 `READY` 后才能调用清单接口。
        调用本接口会触发分片包的准备流程，可安全重复调用。

        本单必须在创建授权时选择 `invoice_delivery_mode = PACKAGE`。

        **本接口强制要求签名头。**
      security:
        - ChannelKey: []
          ChannelTimestamp: []
          ChannelNonce: []
          ChannelSignature: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/InvoiceOrderRequest'
            example:
              channelOrderNo: BANK-20260812-0001
              orderNo: 1e3f2f55-8da4-4ba9-b941-84860eb1d243
      responses:
        '200':
          description: 分片包状态
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: '#/components/schemas/InvoicePackageStatusResponse'
                  - $ref: '#/components/schemas/Sm4Envelope'
              example:
                success: true
                data:
                  mode: PACKAGE
                  schemaVersion: invoice.v1
                  datasetId: 8c1a0b2e-8b1e-4a35-9a54-1f1b6b0b8e01
                  status: READY
                  recordCount: 2841
                  invoiceCount: 1204
                  invoiceCountStatus: VERIFIED
                  exportId: 3f4d1e77-2c9a-4b21-8f60-5d7c0a1b2c3d
                  partCount: 3
                  totalSize: 5242880
                  manifestSha256: '0000000000000000000000000000000000000000000000000000000000000000'
                  manifestEndpoint: /api/v2/channel/invoices/package/manifest
                  cursorEndpoint: /api/v2/channel/invoices/cursor
                  errorCode: null
                  errorMessage: null
                  expiresAt: '2026-08-19T02:00:00Z'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          description: 订单或任务号不匹配（`INVOICE_ORDER_NOT_FOUND`）
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError'
        '409':
          description: |
            常见 `code`：`INVOICE_DELIVERY_MODE_MISMATCH`、
            `INVOICE_DATASET_NOT_READY`、`INVOICE_EXPORT_NOT_READY`、
            `CHANNEL_REQUEST_REPLAYED`。
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError'

  /api/v2/channel/invoices/package/manifest:
    post:
      tags: [大批量发票交付]
      operationId: getInvoicePackageManifest
      summary: 获取发票分片清单
      description: |
        在状态为 `READY` 后获取分片清单。清单中每个分片带有 `sha256` 与签名
        `downloadUrl`，客户端按 `partNo` 顺序下载、逐片校验 SHA-256 后合并。

        **必须直接使用响应中的 `downloadUrl`**，不得自行拼接 URL，
        也不得为下载请求附加渠道签名请求头。

        **本接口强制要求签名头。**
      security:
        - ChannelKey: []
          ChannelTimestamp: []
          ChannelNonce: []
          ChannelSignature: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/InvoiceManifestRequest'
            example:
              channelOrderNo: BANK-20260812-0001
              orderNo: 1e3f2f55-8da4-4ba9-b941-84860eb1d243
              exportId: 3f4d1e77-2c9a-4b21-8f60-5d7c0a1b2c3d
      responses:
        '200':
          description: 分片清单
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: '#/components/schemas/InvoiceManifestResponse'
                  - $ref: '#/components/schemas/Sm4Envelope'
              example:
                success: true
                data:
                  schemaVersion: invoice.v1
                  datasetId: 8c1a0b2e-8b1e-4a35-9a54-1f1b6b0b8e01
                  exportId: 3f4d1e77-2c9a-4b21-8f60-5d7c0a1b2c3d
                  format: JSONL
                  compression: gzip
                  totalRecords: 2841
                  chunkSize: 1000
                  partCount: 3
                  totalSize: 5242880
                  manifestSha256: '0000000000000000000000000000000000000000000000000000000000000000'
                  expiresAt: '2026-08-19T02:00:00Z'
                  parts:
                    - partNo: 1
                      fileName: invoices-part-1.jsonl.gz
                      recordCount: 1000
                      firstSequence: 1
                      lastSequence: 1000
                      fileSize: 1747626
                      sha256: '0000000000000000000000000000000000000000000000000000000000000000'
                      downloadUrl: https://rpa-api.xiaowu005.xyz/api/v2/channel/invoices/packages/3f4d1e77-2c9a-4b21-8f60-5d7c0a1b2c3d/parts/1/download?expires=1786490400&signature=abcdef
        '400':
          description: '`INVOICE_EXPORT_NOT_FOUND` 等交付层错误'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          description: 订单或任务号不匹配（`INVOICE_ORDER_NOT_FOUND`）
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError'
        '409':
          description: |
            常见 `code`：`INVOICE_DELIVERY_MODE_MISMATCH`、
            `INVOICE_EXPORT_NOT_READY`、`INVOICE_EXPORT_EXPIRED`、
            `CHANNEL_REQUEST_REPLAYED`。
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError'

  /api/v2/channel/invoices/packages/{export_id}/parts/{part_no}/download:
    get:
      tags: [大批量发票交付]
      operationId: downloadInvoicePackagePart
      summary: 下载发票分片
      description: |
        使用清单中的签名链接下载单个 gzip 分片。

        链接自带 `expires` 与 `signature`，**不需要**渠道请求头。
        下载后必须用清单中的 `sha256` 校验，并按 `firstSequence` / `lastSequence`
        顺序合并。分片支持断点续传（`Accept-Ranges: bytes`）。
      security: []
      parameters:
        - name: export_id
          in: path
          required: true
          description: 分片包 ID
          schema:
            type: string
            format: uuid
        - name: part_no
          in: path
          required: true
          description: 分片序号，从 1 开始
          schema:
            type: integer
            minimum: 1
        - name: expires
          in: query
          required: true
          description: 链接过期时间（Unix 秒）
          schema:
            type: integer
            format: int64
        - name: signature
          in: query
          required: true
          description: 平台对该链接的 HMAC 签名
          schema:
            type: string
      responses:
        '200':
          description: gzip 压缩的 JSONL 分片
          headers:
            X-Content-SHA256:
              schema:
                type: string
                pattern: '^[0-9a-f]{64}$'
            ETag:
              schema:
                type: string
            Accept-Ranges:
              schema:
                type: string
                example: bytes
          content:
            application/gzip:
              schema:
                type: string
                format: binary
        '403':
          description: 签名无效或链接已过期
        '404':
          description: 分片文件不存在

webhooks:
  authorizationResult:
    post:
      tags: [授权与订单]
      operationId: onAuthorizationResult
      summary: 授权结果通知
      description: |
        用户在授权页完成或放弃授权后，平台向 `callback_url` 推送的通知。
        事件为 `authorization.completed` 或 `authorization.failed`。
        该阶段的回调可以没有 `attempt_id`。
      requestBody:
        required: true
        content:
          application/json:
            schema:
              oneOf:
                - $ref: '#/components/schemas/CallbackPayload'
                - $ref: '#/components/schemas/Sm4Envelope'
            example:
              channelOrderNo: BANK-20260812-0001
              orderNo: 1e3f2f55-8da4-4ba9-b941-84860eb1d243
              notifyType: 1
              event: authorization.completed
              event_id: 550e8400-e29b-41d4-a716-446655440000:authorization.completed
              status: 1
              data: {}
      responses:
        '200':
          description: |
            客户服务端已完成验签、幂等落库并提交事务。返回任意 2xx 即视为成功。
            平台单次投递超时 10 秒，失败最多重试 8 次。

  collectionResult:
    post:
      tags: [授权与订单]
      operationId: onCollectionResult
      summary: 采集结果通知
      description: |
        采集达到终态时推送。事件取决于创建授权时的 `callback_mode`：

        - `AGGREGATE`（默认）：整单一次通知，`order.completed` / `order.failed`。
        - `PER_PRODUCT`：按产品分别通知，`collection.tax.*`、`collection.invoice.*`、
          `collection.pdf.*`。

        终态回调顶层包含 `attempt_id`；开启 HTML 报告时还会增量包含
        `generateHtmlReports=true` 与同一份 `htmlReports`。
        平台 1.5.3 的非阻断部分采集会以成功终态通知，同时在回调数据中携带
        `qualityStatus=WARNING`、`collectionResult=COMPLETED_WITH_WARNINGS`
        及相应质量明细；接收端应保存这些字段，不能只保存事件名。
      requestBody:
        required: true
        content:
          application/json:
            schema:
              oneOf:
                - $ref: '#/components/schemas/CallbackPayload'
                - $ref: '#/components/schemas/Sm4Envelope'
            example:
              channelOrderNo: BANK-20260812-0001
              orderNo: 1e3f2f55-8da4-4ba9-b941-84860eb1d243
              notifyType: 2
              event: order.completed
              event_id: 550e8400-e29b-41d4-a716-446655440000:order.completed
              attempt_id: a2ad4ec4-b878-42c5-b623-4d962f350e72
              status: 1
              data: {}
      responses:
        '200':
          description: 同上。请先验签、再按 `event_id` 去重，最后返回 2xx。


components:
  ##########################################################################
  securitySchemes:
    ChannelKey:
      type: apiKey
      in: header
      name: X-Channel-Key
      description: |
        渠道 API Key。由平台为每个渠道、每个环境分别签发。
        必须保存在客户服务端，禁止写入前端代码或公开日志。
    ChannelTimestamp:
      type: apiKey
      in: header
      name: X-Channel-Timestamp
      description: 请求时间戳（Unix 秒），与服务端时间误差不得超过 300 秒。
    ChannelNonce:
      type: apiKey
      in: header
      name: X-Channel-Nonce
      description: |
        单次请求随机串，最长 128 字符，10 分钟内不得重复；
        重复使用返回 `409 CHANNEL_REQUEST_REPLAYED`。
    ChannelSignature:
      type: apiKey
      in: header
      name: X-Channel-Signature
      description: |
        `HMAC-SHA256(callback_secret, canonical)` 的十六进制小写值，
        可带 `sha256=` 前缀。canonical 串为：

        ```text
        timestamp + "\n" + nonce + "\n" + METHOD + "\n"
          + path + "\n" + raw_query + "\n" + sha256(raw_body)
        ```

        其中 `raw_body` 必须是**实际发送的原始字节**，GET 请求为空串的哈希。

  ##########################################################################
  parameters:
    ExternalOrderId:
      name: external_order_id
      in: path
      required: true
      description: 客户业务订单号，即创建授权时提交的 `external_order_id`
      schema:
        type: string
        minLength: 1
        maxLength: 128
      example: BANK-20260812-0001

  ##########################################################################
  responses:
    Unauthorized:
      description: |
        鉴权失败。常见 `code`：`CHANNEL_AUTH_REQUIRED`、`INVALID_CHANNEL_KEY`、
        `CHANNEL_SIGNATURE_REQUIRED`、`INVALID_CHANNEL_SIGNATURE`、
        `INVALID_CHANNEL_NONCE`、`INVALID_CHANNEL_TIMESTAMP`、
        `CHANNEL_TIMESTAMP_EXPIRED`。
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ApiError'
          example:
            code: INVALID_CHANNEL_SIGNATURE
            message: Channel signature is invalid
            details: {}
    ChannelForbidden:
      description: '`channelCode` 与 `X-Channel-Key` 所属渠道不一致（`CHANNEL_ORDER_FORBIDDEN`）'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ApiError'
    ReplayedNonce:
      description: Nonce 已被使用过（`CHANNEL_REQUEST_REPLAYED`）
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ApiError'
    ValidationError:
      description: 请求体校验失败（`VALIDATION_ERROR`），`details.errors` 列出具体字段
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ApiError'
          example:
            code: VALIDATION_ERROR
            message: Request validation failed
            details:
              errors:
                - type: value_error
                  loc: [body]
                  msg: 'Value error, at least one collection type is required'

  ##########################################################################
  schemas:
    ApiError:
      type: object
      title: 标准错误
      description: 所有 REST 层错误（HTTP 4xx / 5xx）的统一结构，始终为明文。
      required: [code, message]
      properties:
        code:
          type: string
          description: 机器可读的错误码，用于分支处理
          example: CHANNEL_ORDER_NOT_FOUND
        message:
          type: string
          description: 人类可读的错误说明，仅用于排查，不应用于逻辑分支
        details:
          type: object
          description: 补充信息；校验失败时包含 `errors` 数组
          additionalProperties: true

    Sm4Envelope:
      type: object
      title: SM4-GCM 密文信封
      description: |
        订单启用 `output_encryption=SM4` 时，**成功**的 JSON 报文（回调、订单/产品查询、
        数据接口、V2 发票 JSON）整体被替换为本结构；错误响应仍为明文。

        密钥固定为 `SHA-256(callback_secret 的 UTF-8 字节)` 的**前 16 字节**。
        明文为 UTF-8 JSON，序列化使用紧凑分隔符并按对象键排序。
        解密时应先校验 GCM Tag，再解析 JSON。回调场景必须先对原始密文信封
        完成 `X-RPA-Signature` 验签，再解密。

        本开关不改变 PDF、协议、HTML 页面、Worker 原始文件与发票分片的文件字节。
      required: [encrypted, algorithm, version, encoding, iv, ciphertext, tag]
      properties:
        encrypted:
          type: boolean
          const: true
        algorithm:
          type: string
          const: SM4-GCM
        version:
          type: string
          const: tax-rpa.sm4-gcm.v1
        encoding:
          type: string
          const: BASE64
        keyDerivation:
          type: string
          const: SHA256_CALLBACK_SECRET_FIRST_16_BYTES
        iv:
          type: string
          description: Base64 编码的 12 字节随机 IV
        ciphertext:
          type: string
          description: Base64 编码的 JSON 密文
        tag:
          type: string
          description: Base64 编码的 16 字节 GCM 认证标签

    LegacyEnvelope:
      type: object
      title: 业务码信封
      description: |
        `/api/channel/data/*` 系列接口的统一外层结构。
        **判定成功一律使用 `success === true`**，不要硬编码比较 `code`
        （税务/报告成功码为 `200`，发票 ALL 成功码为 `0`）。
      required: [code, msg, message, success]
      properties:
        code:
          type: integer
          description: |
            业务码。`0` / `200` 表示成功；`60003` 采集中；
            `60002` 失败或无可交付数据；`10000` 任务不存在；`10001` 参数或范围错误。
          examples: [200, 0, 60003, 60002, 10000, 10001]
        msg:
          type: string
          description: 业务提示（中文）
        message:
          type: string
          description: 与 `msg` 相同，为兼容不同客户端而重复给出
        success:
          type: boolean
          description: 是否成功。**这是唯一应该用于分支的字段。**
        data:
          description: 业务数据；失败时为 `null`

    DataOrderRef:
      type: object
      title: 订单三元组
      description: |
        数据获取接口的公共请求体。三个编号缺一不可：

        - `channelCode` 必须与 `X-Channel-Key` 所属渠道一致，否则 `403`。
        - `channelOrderNo` 对应创建授权时的 `external_order_id`。
        - `orderNo` 使用订单查询返回的 `job_id`（或 `authorization_code`）。
      required: [channelCode, channelOrderNo, orderNo]
      properties:
        channelCode:
          type: string
          description: 渠道编码
          example: BANK
        channelOrderNo:
          type: string
          maxLength: 128
          description: 客户业务订单号
          example: BANK-20260812-0001
        orderNo:
          type: string
          maxLength: 128
          description: 平台任务编号
          example: 1e3f2f55-8da4-4ba9-b941-84860eb1d243
      additionalProperties: true

    PaginationRequest:
      type: object
      properties:
        pageNum:
          type: integer
          minimum: 1
          default: 1
          description: 页码，从 1 开始
        pageSize:
          type: integer
          minimum: 1
          maximum: 1000
          default: 100
          description: 每页条数，上限 1,000

    InvoiceAllRequest:
      title: 发票 ALL 请求
      allOf:
        - $ref: '#/components/schemas/DataOrderRef'
        - $ref: '#/components/schemas/PaginationRequest'

    TaxResultsRequest:
      title: 税务组合结果请求
      allOf:
        - $ref: '#/components/schemas/DataOrderRef'
        - type: object
          properties:
            taxDataTypes:
              type: array
              description: |
                要取回的税务输出类型。省略时默认使用本次授权已选择的全部类型；
                显式传入时必须是授权范围的子集。
              minItems: 1
              maxItems: 3
              items:
                $ref: '#/components/schemas/OutputType'

    InvoiceResultsRequest:
      title: 发票组合结果请求
      allOf:
        - $ref: '#/components/schemas/DataOrderRef'
        - $ref: '#/components/schemas/PaginationRequest'
        - type: object
          properties:
            invoiceDataTypes:
              type: array
              description: |
                要取回的发票输出类型。省略时默认使用本次授权已选择的全部类型；
                显式传入时必须是授权范围的子集。
              minItems: 1
              maxItems: 3
              items:
                $ref: '#/components/schemas/OutputType'

    OutputType:
      type: string
      title: 输出类型
      enum: [WORKER_RAW, ALL, STANDARD]
      description: |
        - `WORKER_RAW`：Worker 采集到的原始表格文件，通过制品下载接口获取。
        - `ALL`：税局原始/映射数据。
        - `STANDARD`：平台加工后的标准化结构。

    InvoicePeriod:
      type: string
      title: 发票采集期间
      enum:
        - CURRENT_YEAR_PLUS_4
        - CURRENT_YEAR_PLUS_3
        - CURRENT_YEAR_PLUS_2
        - LAST_36_MONTHS
        - LAST_24_MONTHS
      description: |
        | 代码 | 范围 |
        | --- | --- |
        | `CURRENT_YEAR_PLUS_4` | 本年度及往前 4 个完整年度 |
        | `CURRENT_YEAR_PLUS_3` | 本年度及往前 3 个完整年度 |
        | `CURRENT_YEAR_PLUS_2` | 本年度及往前 2 个完整年度 |
        | `LAST_36_MONTHS` | 近 36 个月 |
        | `LAST_24_MONTHS` | 近 24 个月 |

    AuthorizationLinkRequest:
      type: object
      title: 创建授权请求
      required: [external_order_id, enterprise_name, taxpayer_id]
      properties:
        external_order_id:
          type: string
          minLength: 1
          maxLength: 128
          description: 客户业务订单号，同时是本接口的幂等键
          example: BANK-20260812-0001
        enterprise_name:
          type: string
          minLength: 1
          maxLength: 256
          description: 企业名称
          example: 示例企业有限公司
        taxpayer_id:
          type: string
          minLength: 3
          maxLength: 64
          description: 统一社会信用代码 / 纳税人识别号
          example: 91420100XXXXXXXXXX
        success_url:
          type: [string, 'null']
          maxLength: 2048
          description: |
            用户授权完成后返回的浏览器页面。必须是通过平台安全校验的完整公网 HTTPS 地址，
            且不得指向平台自身的管理或授权入口。省略时使用渠道默认值。
        exit_url:
          type: [string, 'null']
          maxLength: 2048
          description: 用户取消、主动结束或授权失败后返回的浏览器页面。省略时使用渠道默认值。
        callback_url:
          type: [string, 'null']
          maxLength: 2048
          description: 平台向客户服务端发送结果通知的地址。省略时使用渠道默认值。
        callback_mode:
          type: string
          enum: [AGGREGATE, PER_PRODUCT]
          default: AGGREGATE
          description: |
            - `AGGREGATE`：整单完成后统一通知（`order.completed` / `order.failed`）。
            - `PER_PRODUCT`：按产品分别通知（`collection.tax.*`、`collection.invoice.*`、`collection.pdf.*`）。

            也接受驼峰写法 `callbackMode`。
        output_encryption:
          type: string
          enum: [NONE, SM4]
          default: NONE
          description: |
            客户 JSON 输出加密开关。`SM4` 时成功报文替换为 SM4-GCM 信封（见 `Sm4Envelope`）。
            也接受驼峰写法 `outputEncryption`。
        collect_tax:
          type: boolean
          default: true
          description: 是否采集税务
        collect_invoice:
          type: boolean
          default: true
          description: 是否采集发票
        tax_data_types:
          type: [array, 'null']
          minItems: 1
          maxItems: 3
          description: |
            税务输出类型，可任意非空组合。省略时兼容 `ALL` 与 `STANDARD` 两种。
            选择了 `collect_tax=false` 时不得传入。也接受 `taxDataTypes`。
          items:
            $ref: '#/components/schemas/OutputType'
        invoice_data_types:
          type: [array, 'null']
          minItems: 1
          maxItems: 3
          description: |
            发票输出类型，可任意非空组合。省略时仅允许 `ALL`。
            选择了 `collect_invoice=false` 时不得传入。也接受 `invoiceDataTypes`。
          items:
            $ref: '#/components/schemas/OutputType'
        invoice_period:
          $ref: '#/components/schemas/InvoicePeriod'
          default: CURRENT_YEAR_PLUS_4
        invoice_delivery_mode:
          type: string
          enum: [LEGACY, CURSOR, PACKAGE]
          default: LEGACY
          description: |
            原始发票（ALL）的交付方式，只影响发票 ALL，不影响 Worker 原始文件或 STANDARD。

            - `LEGACY`：通过 `/api/channel/data/invoice/all` 分页获取。
            - `CURSOR`：通过 V2 游标接口续拉，适合边拉边处理。
            - `PACKAGE`：生成 gzip 分片包，适合大批量离线下载。

            也接受 `invoiceDeliveryMode`。需要 `collect_invoice=true`。
        pdf_report_types:
          type: array
          maxItems: 10
          description: |
            **唯一的 PDF 报告选择参数**。公开详版 `TAX_INVOICE_DETAIL`、
            公开简版 `TAX_INVOICE_SUMMARY` 可独立或同时传入；
            `LEASING_CREDIT_CUSTOM` 是统一的定制版开关，传入后生成平台后台为
            当前渠道配置的全部有效定制报告——客户无需知道内部名称与版本。

            后台已配置但本单未传入时不会生成；当前渠道没有任何有效定制报告
            却传入该编码时返回 `403 REPORT_NOT_ENTITLED`。

            省略或传空数组时不生成 PDF。使用时必须同时采集税务和发票。
            也接受 `pdfReportTypes`。
          items:
            type: string
            enum:
              - TAX_INVOICE_DETAIL
              - TAX_INVOICE_SUMMARY
              - LEASING_CREDIT_CUSTOM
              - TAX_RISK
              - TAX_CREDIT
        generate_html_reports:
          type: boolean
          default: false
          description: |
            是否生成详版、简版报告对应的两份 HTML 在线版。
            与 `pdf_report_types` 相互独立：只开 HTML 不会隐式生成 PDF，
            只申请 PDF 也不会隐式生成 HTML。开启时必须同时采集税务和发票。
            也接受 `generateHtmlReports`。
        data_retention_days:
          type: integer
          minimum: 7
          maximum: 365
          default: 30
          description: |
            本订单数据保留期（天），推荐取 `7`、`30`、`90`、`180`、`365`。
            同时用于云桌面本地采集副本及 ECS 采集结果/原始快照的清理。
            也接受 `dataRetentionDays`。
        expires_in_seconds:
          type: integer
          minimum: 60
          maximum: 86400
          default: 1800
          description: 授权链接有效期（秒）
        extras_data:
          type: object
          description: 客户扩展信息，回调时原样返回
          additionalProperties: true
        tax_url:
          type: [string, 'null']
          maxLength: 2048
          description: |
            指定税局入口地址。一般不需要传入——平台会按纳税人识别号解析归属地区。
            解析失败且渠道无默认值时返回 `409 TAX_URL_MISSING`。
        data_delivery_mode:
          type: string
          enum: [STRUCTURED, WORKER_RAW]
          default: STRUCTURED
          description: |
            **旧客户端兼容字段**。新接入请直接在 `tax_data_types` /
            `invoice_data_types` 中选择 `WORKER_RAW`。
          deprecated: true
        support_long_auth:
          type: boolean
          default: false
          description: 旧客户端兼容字段，始终被归一化为 `false`。
          deprecated: true
      additionalProperties: false

    AuthorizationLinkResponse:
      type: object
      title: 创建授权响应
      required:
        - authorization_code
        - authorization_url
        - external_order_id
        - invoice_period
        - expires_at
      properties:
        authorization_code:
          type: string
          format: uuid
          description: 本次授权代次的标识
        authorization_url:
          type: string
          format: uri
          description: |
            授权页面地址。**客户前端必须直接跳转此地址**，不得自行拼接。
        external_order_id:
          type: string
        invoice_period:
          $ref: '#/components/schemas/InvoicePeriod'
        invoice_start_date:
          type: [string, 'null']
          format: date
          description: 解析后的发票采集起始日期
        invoice_end_date:
          type: [string, 'null']
          format: date
          description: 解析后的发票采集截止日期
        expires_at:
          type: string
          format: date-time
          description: 授权链接过期时间
        dataDeliveryMode:
          type: string
          enum: [STRUCTURED, WORKER_RAW]
        generateHtmlReports:
          type: boolean
        outputEncryption:
          type: string
          enum: [NONE, SM4]

    CollectionState:
      type: string
      title: 订单状态
      enum: [CONNECT, PROCESS, INTERACTION, SUCCESS, ERROR]
      description: |
        | 值 | 含义 | 是否结束 |
        | --- | --- | --- |
        | `CONNECT` | 等待用户提交授权 | 否 |
        | `PROCESS` | 排队、采集、校验或上传中 | 否 |
        | `INTERACTION` | 等待短信或图形验证码 | 否 |
        | `SUCCESS` | 采集完成 | 是 |
        | `ERROR` | 最终失败 | 是 |

    ArtifactItem:
      type: object
      title: Worker 原始制品
      properties:
        id:
          type: string
          description: 制品 ID，用于拼接下载接口路径
        kind:
          type: string
          enum: [TAX_DATA, INVOICE_DATA]
        filename:
          type: string
        size:
          type: integer
          description: 字节数
        sha256:
          type: string
          pattern: '^[0-9a-f]{64}$'
        downloadUrl:
          type: string
          format: uri
          description: |
            仅在对应产品选择了 `WORKER_RAW` 时出现。
            调用时需携带本渠道 `X-Channel-Key`。

    HtmlReportBundle:
      type: object
      title: HTML 在线报告
      description: |
        仅在 `generate_html_reports=true` 时增量出现于订单结果与终态回调的 `data` 中。
        `reports` 固定列出详版与简版两项，便于在未就绪时也能跟踪各自状态。
        平台 1.5.3 中两份报告共享同一 ALL 事实模型，但章节范围不同；红冲净额和
        申报/财务报表份数使用去重后的业务口径，不按原始明细行简单累加。
      properties:
        requested:
          type: boolean
        status:
          type: string
          enum: [PENDING, READY, PARTIAL, FAILED]
          description: 整体状态。HTML 生成是非阻断的，失败不会把税务/发票采集改成失败。
        generatedAt:
          type: [string, 'null']
          format: date-time
        expiresAt:
          type: [string, 'null']
          format: date-time
          description: 固定为生成后 7 天，重复查询不续期
        reports:
          type: array
          items:
            type: object
            properties:
              reportType:
                type: string
                enum: [TAX_INVOICE_DETAIL, TAX_INVOICE_SUMMARY]
              label:
                type: string
              status:
                type: string
                enum: [PENDING, READY, FAILED]
              accessUrl:
                type: [string, 'null']
                format: uri
                description: Bearer Capability 链接，按敏感凭据保护
              expiresAt:
                type: [string, 'null']
                format: date-time
              errorCode:
                type: [string, 'null']
              errorMessage:
                type: [string, 'null']

    OrderResult:
      type: object
      title: 订单查询结果
      properties:
        external_order_id:
          type: string
        authorization_code:
          type: string
          format: uuid
          description: |
            当前授权记录标识。只有平台创建新授权代次时才会变化；1.5.3 中税局会话
            失效后通过原授权页重新录入凭据，会沿用该值和 `job_id`，只滚动 `attempt_id`。
        job_id:
          type: [string, 'null']
          description: 采集任务标识，即数据接口所需的 `orderNo`
        attempt_id:
          type: [string, 'null']
          description: 当前采集尝试；系统重试或原授权页重新录入凭据后会变化
        state:
          $ref: '#/components/schemas/CollectionState'
        dataDeliveryMode:
          type: string
          enum: [STRUCTURED, WORKER_RAW]
        invoice_period:
          $ref: '#/components/schemas/InvoicePeriod'
        invoice_period_label:
          type: string
          description: 发票期间的中文标签，如「近36个月」
        invoice_start_date:
          type: [string, 'null']
          format: date
        invoice_end_date:
          type: [string, 'null']
          format: date
        result:
          type: object
          description: 采集结果摘要（已脱敏）
          additionalProperties: true
        qualityStatus:
          type: [string, 'null']
          description: |
            质量结论，常见值为 `PASSED`、`WARNING`、`UNVERIFIED`。
            订单 `SUCCESS` 时仍可能为 `WARNING`，必须结合质量明细判断覆盖范围。
        qualityIssues:
          type: array
          description: 非阻断缺失、覆盖不足或历史结果未验证等质量问题清单
          items:
            type: object
            additionalProperties: true
        collectionResult:
          type: [string, 'null']
          description: 采集结论；`COMPLETED_WITH_WARNINGS` 表示已有数据可用但存在非阻断质量警告
        dataQuality:
          type: [object, 'null']
          description: |
            结构化质量摘要，可能包含 `status`、`blocking`、`partial`、`usable`、
            `coverage`、`issues` 与 `quarantinedArtifacts`。1.5.3 的部分税务项目未取得时，
            已有数据继续交付，并通过这里披露缺失范围。
          additionalProperties: true
        htmlReports:
          $ref: '#/components/schemas/HtmlReportBundle'
        artifacts:
          type: array
          items:
            $ref: '#/components/schemas/ArtifactItem'
        updated_at:
          type: string
          format: date-time
      additionalProperties: true

    ProductResult:
      type: object
      title: 产品查询结果
      properties:
        external_order_id:
          type: string
        authorization_code:
          type: string
          format: uuid
        job_id:
          type: [string, 'null']
        attempt_id:
          type: [string, 'null']
        product:
          type: string
          enum: [TAX, INVOICE]
        state:
          type: string
          enum: [PENDING, SUCCEEDED, FAILED, SKIPPED]
          description: |
            `PENDING` 表示尚未产生终态；`SUCCEEDED` / `FAILED` / `SKIPPED` 为终态。
        available:
          type: boolean
          description: '**只有 `state=SUCCEEDED` 且本字段为 `true` 时才可以取数。**'
        dataDeliveryMode:
          type: string
          enum: [STRUCTURED, WORKER_RAW]
        dataset_count:
          type: integer
        record_count:
          type: integer
        database:
          type: object
          additionalProperties: true
        qualityStatus:
          type: [string, 'null']
          description: '`PASSED`、`WARNING` 或 `UNVERIFIED`；成功产品也可能带非阻断警告'
        qualityIssues:
          type: array
          description: 产品粒度的质量问题清单
          items:
            type: object
            additionalProperties: true
        collectionResult:
          type: [string, 'null']
          description: '`COMPLETED` 或 `COMPLETED_WITH_WARNINGS`'
        dataQuality:
          type: [object, 'null']
          description: 产品粒度的覆盖、部分结果、可用性与隔离制品摘要
          additionalProperties: true
        datasets:
          type: array
          items:
            type: object
            properties:
              id:
                type: string
              name:
                type: string
              product:
                type: string
              row_count:
                type: integer
        artifacts:
          type: array
          items:
            $ref: '#/components/schemas/ArtifactItem'
        error_code:
          type: [string, 'null']
        error_message:
          type: [string, 'null']
        retryable:
          type: boolean
          description: 是否可由平台自动重试
        available_at:
          type: [string, 'null']
          format: date-time
      additionalProperties: true

    TaxAllEnvelope:
      title: 税务 ALL 响应
      allOf:
        - $ref: '#/components/schemas/LegacyEnvelope'
        - type: object
          properties:
            data:
              type: [object, 'null']
              description: |
                固定 37 个顶层节点，每个节点是一个记录数组或对象。
                完整字段以交付包内的税务 ALL 字段字典为准。
                平台 1.5.3 对同一任务快照固化已发布映射修订与目录哈希，
                后续后台映射发布或回滚不会改写已经生成的结果。
              additionalProperties: true

    ModuleStatus:
      type: object
      title: 模块状态
      description: |
        `moduleStatus` 中每个模块对应的状态对象。**这是对象而不是字符串。**
      properties:
        status:
          type: string
          enum:
            - AVAILABLE
            - EMPTY
            - NOT_COLLECTED
            - INSUFFICIENT_SOURCE
            - PROCESSING_FAILED
          description: |
            | 值 | 含义 |
            | --- | --- |
            | `AVAILABLE` | 有数据 |
            | `EMPTY` | 来源存在且已证明为空 |
            | `NOT_COLLECTED` | 本次采集没有该来源 |
            | `INSUFFICIENT_SOURCE` | 已采集，但来源不足以无损标准化 |
            | `PROCESSING_FAILED` | 标准化过程失败 |
        recordCount:
          type: integer
          description: 该模块的记录数
        message:
          type: string
          description: 补充说明，可能为空串

    TaxStandardData:
      type: object
      title: 税务 STANDARD 数据
      properties:
        schemaVersion:
          type: string
          const: tax-standard.v2
        generatedAt:
          type: string
          format: date-time
        taxpayerId:
          type: string
        companyName:
          type: string
        dataAsOf:
          type: [string, 'null']
          description: 数据截止日期，取自本次结果中最新的业务日期
        selectedModules:
          type: array
          description: 固定列出完整 24 个模块的代码
          items:
            type: string
        sourceFingerprint:
          type: string
          description: 本次标准化所依据来源的指纹，形如 `sha256:<hex>`
        sourceWarnings:
          type: array
          description: 来源层面的边界披露（缺年度、缺表单、记录数不一致等）
          items:
            type: object
            additionalProperties: true
        fieldWarnings:
          type: array
          description: 字段层面的边界披露（同一项目出现多个不同金额等）
          items:
            type: object
            additionalProperties: true
        moduleStatus:
          type: object
          description: 模块代码 → 状态对象
          additionalProperties:
            $ref: '#/components/schemas/ModuleStatus'
        data:
          type: object
          description: 各模块的标准化数据，无数据模块返回空对象或空数组
          additionalProperties: true
      additionalProperties: true

    TaxStandardEnvelope:
      title: 税务 STANDARD 响应
      allOf:
        - $ref: '#/components/schemas/LegacyEnvelope'
        - type: object
          properties:
            data:
              oneOf:
                - $ref: '#/components/schemas/TaxStandardData'
                - type: 'null'

    TaxResultsEnvelope:
      title: 税务组合结果响应
      allOf:
        - $ref: '#/components/schemas/LegacyEnvelope'
        - type: object
          properties:
            data:
              type: [object, 'null']
              description: 键为请求的输出类型
              properties:
                ALL:
                  type: object
                  additionalProperties: true
                STANDARD:
                  $ref: '#/components/schemas/TaxStandardData'
                WORKER_RAW:
                  type: object
                  properties:
                    artifacts:
                      type: array
                      items:
                        $ref: '#/components/schemas/ArtifactItem'

    InvoiceGranularitySummary:
      type: object
      title: 发票粒度摘要
      description: |
        发票 ALL 分页、CURSOR 与 PACKAGE 状态/清单返回的统一摘要，
        在发票快照创建时计算并持久化，翻页不会反复扫描。
      properties:
        invoiceCount:
          type: [integer, 'null']
          description: |
            按可靠票号去重后的**真实发票张数**。无法证明时为 `null`，
            **不得用原始行数替代**。
        invoiceCountStatus:
          type: string
          enum: [VERIFIED, UNVERIFIED]
        invoiceCountMethod:
          type: [string, 'null']
          description: 说明张数的判定来源（信息汇总表、发票基础信息、多 Sheet 身份联合或明确空结果）
        recordRoleCounts:
          type: object
          description: 六类记录角色在完整数据集中的总行数，各分页保持一致
          additionalProperties:
            type: integer

    InvoiceRecord:
      type: object
      title: 原始发票记录
      description: |
        税局源列透传的动态对象，**必须允许新增字段**。
        同一张发票的多个商品行可能对应多条记录。

        交付层会对能够确定一一对应的列增量附加旧接口兼容键
        （如 `数电发票号码 → qdfphm`、`货物或应税劳务名称 → spmc`、`金额 → je`），
        且绝不覆盖制品已提供的同名键。
      properties:
        __recordRole:
          type: string
          enum:
            - INVOICE_HEADER
            - INVOICE_LINE
            - INVOICE_LINE_SUMMARY
            - WORKBOOK_TOTAL
            - SPECIALTY_DETAIL
            - SOURCE_RECORD
          description: |
            | 值 | 含义 |
            | --- | --- |
            | `INVOICE_HEADER` | 「发票基础信息」中的整票记录 |
            | `INVOICE_LINE` | 「信息汇总表」中的真实商品或服务明细 |
            | `INVOICE_LINE_SUMMARY` | 「详见销货清单」等整票汇总占位，不应与真实明细重复累计 |
            | `WORKBOOK_TOTAL` | 原工作簿合计/总计行，不代表一张发票 |
            | `SPECIALTY_DETAIL` | 不动产、旅客、货运、通行费、铁路客票等专项伴随明细 |
            | `SOURCE_RECORD` | 其他历史或未知来源记录 |
        __sourceDatasetName:
          type: string
          description: 原始工作表名称
        __sourceArtifactName:
          type: string
          description: Worker 原始制品文件名
        __sourceRecordOrdinal:
          type: integer
          description: 一基表内行序
      additionalProperties: true

    InvoiceAllPage:
      type: object
      title: 发票 ALL 分页
      properties:
        current:
          type: integer
          description: 当前页码
        size:
          type: integer
          description: 每页条数
        pages:
          type: integer
          description: 总页数
        total:
          type: integer
          description: '**原始行数**总计，不是发票张数'
        records:
          type: array
          items:
            $ref: '#/components/schemas/InvoiceRecord'
        searchCount:
          type: boolean
        optimizeCountSql:
          type: boolean
        countId:
          type: [string, 'null']
        maxLimit:
          type: [integer, 'null']
        orders:
          type: array
          items:
            type: object
            additionalProperties: true
      additionalProperties: true

    InvoiceAllEnvelope:
      title: 发票 ALL 响应
      allOf:
        - $ref: '#/components/schemas/LegacyEnvelope'
        - type: object
          properties:
            code:
              type: integer
              description: '**成功为 `0`**，不是 200'
            data:
              oneOf:
                - $ref: '#/components/schemas/InvoiceAllPage'
                - type: 'null'

    InvoiceStandardData:
      type: object
      title: 发票 STANDARD 数据
      description: |
        与税务 STANDARD 不同：**13 个模块直接挂在本对象顶层**，没有额外的 `data` 包装，
        也没有 `moduleStatus`。

        另请注意 `selectedModules` 列出的是模块的**英文键名**
        （`TRANSACTION_OVERVIEW`、`SALES_ANALYSIS` …），
        而实际数据节点使用**拼音码**（`JYGL`、`XSFX` …）。两者一一对应。
      properties:
        schemaVersion:
          type: string
          const: invoice-standard.v2
        generatedAt:
          type: string
          format: date-time
        taxpayerId:
          type: string
        companyName:
          type: string
        selectedModules:
          type: array
          description: 固定列出完整 13 个模块的英文键名
          items:
            type: string
        sourceRecordCount:
          type: integer
          description: 参与加工的原始记录数
        effectiveRecordCount:
          type: integer
          description: 判定为有效的记录数
        duplicateRecordCount:
          type: integer
          description: 被判定为重复的记录数
        invoiceCount:
          type: integer
          description: 去重后的发票张数
        SJZL:
          type: object
          description: |
            加工质量对象。披露可靠票数、无法判断进销方向、被抑制的重复金额、
            缺年度/交易对方名称/税号、同名多税号冲突、被忽略的整票清单占位等。
          additionalProperties: true
        SXYNDXX:
          type: array
          description: 上游年度信息
          items:
            type: object
            additionalProperties: true
        JYGL:
          type: object
          description: 交易概览
          additionalProperties: true
        XSFX:
          type: object
          description: 销售分析
          additionalProperties: true
        CGFX:
          type: object
          description: 采购分析
          additionalProperties: true
        FYFX:
          type: object
          description: 费用分析
          additionalProperties: true
        YDQSXYJXSXX:
          type: array
          description: 月度前十下游客户
          items:
            type: object
            additionalProperties: true
        YDQSXYCPXX:
          type: array
          description: 月度前十下游商品
          items:
            type: object
            additionalProperties: true
        YDFPHZ:
          type: array
          description: 月度发票汇总
          items:
            type: object
            additionalProperties: true
        XYXX:
          type: array
          description: 下游客户信息（全部，非前十）
          items:
            type: object
            additionalProperties: true
        SPXX:
          type: array
          description: 商品信息（全部，非前十）
          items:
            type: object
            additionalProperties: true
        QSGYSLB:
          type: array
          description: 前十供应商列表
          items:
            type: object
            additionalProperties: true
        AYHZJXPHZ:
          type: array
          description: 按月汇总进项票汇总
          items:
            type: object
            additionalProperties: true
        XXKPAYFLTJ:
          type: array
          description: 销项开票按月分类统计
          items:
            type: object
            additionalProperties: true
      additionalProperties: true

    InvoiceStandardEnvelope:
      title: 发票 STANDARD 响应
      allOf:
        - $ref: '#/components/schemas/LegacyEnvelope'
        - type: object
          properties:
            data:
              oneOf:
                - $ref: '#/components/schemas/InvoiceStandardData'
                - type: 'null'

    InvoiceResultsEnvelope:
      title: 发票组合结果响应
      allOf:
        - $ref: '#/components/schemas/LegacyEnvelope'
        - type: object
          properties:
            data:
              type: [object, 'null']
              properties:
                ALL:
                  $ref: '#/components/schemas/InvoiceAllPage'
                STANDARD:
                  $ref: '#/components/schemas/InvoiceStandardData'
                WORKER_RAW:
                  type: object
                  properties:
                    artifacts:
                      type: array
                      items:
                        $ref: '#/components/schemas/ArtifactItem'

    ReportStatusEnvelope:
      title: PDF 报告状态响应
      allOf:
        - $ref: '#/components/schemas/LegacyEnvelope'
        - type: object
          properties:
            data:
              type: [object, 'null']
              properties:
                status:
                  type: string
                  enum: [PENDING, SUCCEEDED, PARTIAL, FAILED]
                  description: 整体状态
                reports:
                  type: array
                  items:
                    type: object
                    properties:
                      reportType:
                        type: string
                      status:
                        type: string
                        enum: [PENDING, SUCCEEDED, FAILED]
                        description: 未达最大重试次数前的失败对外显示为 `PENDING`
                      reportVersion:
                        type: string
                      fileName:
                        type: [string, 'null']
                      fileSize:
                        type: [integer, 'null']
                      sha256:
                        type: [string, 'null']
                        pattern: '^[0-9a-f]{64}$'
                      pageCount:
                        type: [integer, 'null']
                      downloadUrl:
                        type: [string, 'null']
                        format: uri
                        description: 仅在生成成功时出现；签名链接，直接使用不要拼接
                      errorCode:
                        type: [string, 'null']
                      errorMessage:
                        type: [string, 'null']

    InvoiceOrderRequest:
      type: object
      title: V2 订单二元组
      description: |
        V2 接口的公共请求体。**不接受额外字段**，也不需要 `channelCode`
        （渠道由 API Key 与签名确定）。
      required: [channelOrderNo, orderNo]
      properties:
        channelOrderNo:
          type: string
          minLength: 1
          maxLength: 128
          description: 客户业务订单号；也接受 `channel_order_no`
          example: BANK-20260812-0001
        orderNo:
          type: string
          minLength: 1
          maxLength: 128
          description: 平台任务编号；也接受 `order_no`
          example: 1e3f2f55-8da4-4ba9-b941-84860eb1d243
      additionalProperties: false

    InvoiceCursorRequest:
      type: object
      title: 游标请求
      required: [channelOrderNo, orderNo]
      properties:
        channelOrderNo:
          type: string
          minLength: 1
          maxLength: 128
        orderNo:
          type: string
          minLength: 1
          maxLength: 128
        cursor:
          type: [string, 'null']
          maxLength: 4096
          description: 上一批返回的 `nextCursor`；首次调用省略
        limit:
          type: [integer, 'null']
          minimum: 1
          description: 本批条数，默认 500，服务端上限 2,000；也接受 `pageSize`
      additionalProperties: false

    InvoiceManifestRequest:
      type: object
      title: 分片清单请求
      required: [channelOrderNo, orderNo, exportId]
      properties:
        channelOrderNo:
          type: string
          minLength: 1
          maxLength: 128
        orderNo:
          type: string
          minLength: 1
          maxLength: 128
        exportId:
          type: string
          format: uuid
          minLength: 36
          maxLength: 36
          description: 分片包 ID，取自 `package/status` 响应；也接受 `export_id`
      additionalProperties: false

    InvoiceCursorResponse:
      type: object
      title: 游标响应
      required: [success, data]
      properties:
        success:
          type: boolean
          const: true
        data:
          allOf:
            - type: object
              properties:
                schemaVersion:
                  type: string
                  const: invoice.v1
                datasetId:
                  type: string
                  format: uuid
                status:
                  type: string
                total:
                  type: integer
                  description: 数据集**原始行数**总计
                count:
                  type: integer
                  description: 本批返回条数
                records:
                  type: array
                  items:
                    $ref: '#/components/schemas/InvoiceRecord'
                nextCursor:
                  type: [string, 'null']
                  description: 下一批游标；为 `null` 表示已拉完
                hasMore:
                  type: boolean
                expiresAt:
                  type: [string, 'null']
                  format: date-time
            - $ref: '#/components/schemas/InvoiceGranularitySummary'

    InvoicePackageStatusResponse:
      type: object
      title: 分片包状态响应
      required: [success, data]
      properties:
        success:
          type: boolean
          const: true
        data:
          allOf:
            - type: object
              properties:
                mode:
                  type: string
                  enum: [LEGACY, CURSOR, PACKAGE]
                schemaVersion:
                  type: string
                  const: invoice.v1
                datasetId:
                  type: string
                  format: uuid
                status:
                  type: string
                  enum: [PENDING, BUILDING, READY, FAILED, EXPIRED]
                  description: '`READY` 后方可获取清单'
                recordCount:
                  type: integer
                  description: 原始行数总计
                exportId:
                  type: [string, 'null']
                  format: uuid
                partCount:
                  type: [integer, 'null']
                totalSize:
                  type: [integer, 'null']
                manifestSha256:
                  type: [string, 'null']
                manifestEndpoint:
                  type: [string, 'null']
                cursorEndpoint:
                  type: [string, 'null']
                errorCode:
                  type: [string, 'null']
                errorMessage:
                  type: [string, 'null']
                expiresAt:
                  type: [string, 'null']
                  format: date-time
            - $ref: '#/components/schemas/InvoiceGranularitySummary'

    InvoiceManifestResponse:
      type: object
      title: 分片清单响应
      required: [success, data]
      properties:
        success:
          type: boolean
          const: true
        data:
          allOf:
            - type: object
              properties:
                schemaVersion:
                  type: string
                  const: invoice.v1
                datasetId:
                  type: string
                  format: uuid
                exportId:
                  type: string
                  format: uuid
                format:
                  type: string
                  description: 分片文件格式
                  example: JSONL
                compression:
                  type: string
                  const: gzip
                totalRecords:
                  type: integer
                chunkSize:
                  type: integer
                partCount:
                  type: integer
                totalSize:
                  type: integer
                manifestSha256:
                  type: string
                  pattern: '^[0-9a-f]{64}$'
                expiresAt:
                  type: [string, 'null']
                  format: date-time
                parts:
                  type: array
                  items:
                    type: object
                    properties:
                      partNo:
                        type: integer
                      fileName:
                        type: string
                      recordCount:
                        type: integer
                      firstSequence:
                        type: integer
                      lastSequence:
                        type: integer
                      fileSize:
                        type: integer
                      sha256:
                        type: string
                        pattern: '^[0-9a-f]{64}$'
                      downloadUrl:
                        type: string
                        format: uri
                        description: 签名链接，直接使用，不要拼接，也不要附加渠道请求头
            - $ref: '#/components/schemas/InvoiceGranularitySummary'

    CallbackPayload:
      type: object
      title: 回调通知体
      description: |
        平台向 `callback_url` 发送的 JSON POST。请求头：

        | 头 | 说明 |
        | --- | --- |
        | `X-RPA-Event` | 事件类型 |
        | `X-RPA-Delivery` | 投递记录 ID；重试时保持不变 |
        | `X-RPA-Signature` | Callback Secret 对**原始 Body 字节**的 HMAC-SHA256 |

        客户服务端应先完成验签，再按 `event_id` 去重，并在同一事务中保存回调记录
        与业务状态；事务提交后返回任意 2xx。平台单次投递超时 10 秒，最多尝试 8 次。

        本结构不属于 HTTP 接口，仅作为契约在此声明。
      properties:
        channelOrderNo:
          type: string
        orderNo:
          type: string
        notifyType:
          type: integer
        event:
          type: string
          description: |
            - 授权阶段：`authorization.completed`、`authorization.failed`
            - `AGGREGATE`：`order.completed`、`order.failed`
            - `PER_PRODUCT`：`collection.tax.*`、`collection.invoice.*`、`collection.pdf.*`
        event_id:
          type: string
          description: '**业务幂等键**。客户端必须容忍重复投递与非顺序到达。'
        attempt_id:
          type: [string, 'null']
          description: 采集或产品结果回调包含；纯授权阶段回调可省略
        status:
          type: integer
        generateHtmlReports:
          type: boolean
          description: 仅在开启 HTML 报告时增量出现于终态回调顶层
        data:
          type: object
          additionalProperties: true
      additionalProperties: true
