# VAT API 统一接口设计文档

---

## 1. 概述

### 1.1 设计目标

将现有的多个分散 API（`vat_query_api.php`、`vat_deferred_query_api.php`、`vat_deferred_6_query_api.php`、`vat_deferred_7_query_api.php`、`vat_Application_Delay_api.php` 等）整合为一个统一入口。通过 `PushType` + `Country` + `ProcessMode` + `Data` 参数区分业务场景，降低调用方对接成本。

核心定位：**文件处理请求接口**，根据业务订单生成对应的文件并上传 OSS，返回文件 URL 列表。

### 1.2 技术选型

| 项目     | 选型                      |
| -------- | ------------------------- |
| 语言     | PHP 7.4+                  |
| 框架     | 原生 PHP（OOP 类封装）    |
| 数据库   | SQL Server（源库 vat_db） |
| PDF 提取 | Python + pdfplumber       |
| 认证方式 | Bearer Token（Authorization Header） |
| 响应格式 | JSON, UTF-8               |

---

## 2. 接口规格

### 2.1 基本信息

| 项目         | 值                                                     |
| ------------ | ------------------------------------------------------ |
| **接口名称** | VAT 统一 API                                           |
| **请求方式** | POST                                                   |
| **Content-Type** | application/json                                   |
| **入口文件** | `server/unified_api.php`                               |
| **本地测试** | `http://localhost/vat_api_de_client/server/unified_api.php` |
| **生产地址** | `https://your-server.com/vat_api_de_client/server/unified_api.php` |

### 2.2 认证方式

采用 `Authorization` 请求头携带 Bearer Token 进行身份验证：

```
Authorization: Bearer <api_key>
```

Token 在服务端配置文件 `config/api_config.php` 中管理，支持多 Token。

### 2.3 请求参数

| 参数          | 类型   | 必填 | 说明                                                         |
| ------------- | ------ | ---- | ------------------------------------------------------------ |
| `PushType`    | string | 是   | 推送类型，见 [3. PushType 枚举](#3-pushtype-枚举)           |
| `Country`     | string | 是   | 国家二字码（DE / FR / NL / ES / BE ...）                     |
| `ProcessMode` | string | 是   | 处理模式：`sync`（实时处理，直接返回文件URL） / `async`（异步处理，返回 job_id） |
| `Data`        | object | 是   | 业务数据，结构随 `PushType` 变化，见下面各场景              |

**请求体示例：**

```json
{
  "PushType": "extract_vat_register",
  "Country": "DE",
  "ProcessMode": "sync",
  "Data": {
    "vat_business_record_id": "5CBCDE28-1FD5-400A-8D3D-27D178B62FE8"
  }
}
```

### 2.4 响应格式

**sync 模式成功响应（实时处理，直接返回文件 URL）：**

```json
{
  "code": 200,
  "msg": "success",
  "ProcessMode": "sync",
  "data": {
    "file1": "http://vat-1259285998.cos.ap-guangzhou.myqcloud.com/vat_factory/de_declar/POVAT20251113000004_DE_Mandat.pdf",
    "file2": "http://vat-1259285998.cos.ap-guangzhou.myqcloud.com/vat_factory/de_declar/POVAT20251113000004_DE_Status.pdf",
    "file3": "http://vat-1259285998.cos.ap-guangzhou.myqcloud.com/vat_factory/de_declar/POVAT20251113000004_DE_ID.pdf",
    "file4": "http://vat-1259285998.cos.ap-guangzhou.myqcloud.com/vat_factory/de_declar/POVAT20251113000004_DE_BusinessLicense.pdf",
    "file5": "",
    "file6": "",
    "file7": "",
    "file8": "",
    "file9": "",
    "file10": ""
  }
}
```

**async 模式成功响应（异步处理，返回 job_id 后续轮询）：**

```json
{
  "code": 200,
  "msg": "success",
  "ProcessMode": "async",
  "data": {
    "job_id": "JOB20260730ABCD1234",
    "message": "任务已提交，请通过 query_job 接口查询结果"
  }
}
```

**失败响应：**

```json
{
  "code": 400,
  "msg": "错误描述信息",
  "data": null
}
```

**认证失败：**

```json
{
  "code": 401,
  "msg": "无效的授权令牌",
  "data": null
}
```

### 2.5 状态码说明

| code | 含义                                                      |
| ---- | --------------------------------------------------------- |
| 200  | 请求成功                                                  |
| 400  | 请求参数错误 / 业务处理失败                               |
| 401  | 认证失败（Token 无效或缺失）                              |
| 405  | 请求方法不允许（非 POST）                                 |
| 500  | 服务器内部错误                                            |

---

## 3. PushType 枚举

| PushType               | 说明                               | 对应原有接口                     |
| ---------------------- | ---------------------------------- | -------------------------------- |
| `process_files`        | 文件处理（注册授权文件生成，统一入口） | 各注册/数据提取 API 的合并       |
| `extract_pdf`          | PDF 文件数据提取（递延税单识别）   | `vat_deferred_7_query_api.php`   |
| `extract_vat_register` | 德国 VAT 注册数据提取 + 五合一生成 | `vat_query_api.php`              |
| `submit_declaration`   | XML 申报提交（ERIC/ELSTER）        | `src/Api/GermanVatApi.php`       |
| `query_declaration`    | 查询申报结果                       | `src/Api/GermanVatApi.php`       |
| `query_job`            | 查询异步任务结果                   | 新增                             |
| `extract_import_data`  | 进口增值税数据提取                 | `vat_deferred_6_query_api.php`   |

---

## 4. 各 PushType 详细说明

### 4.1 process_files — 文件处理（统一入口）

**适用国家：** DE / FR

**说明：** 根据业务订单生成注册授权相关文件，生成后上传 OSS，通过 `data.file1` ~ `data.file10` 返回文件 URL。这是最常用的核心 PushType。

**Data 参数：**

| 字段      | 类型   | 必填 | 说明                                          |
| --------- | ------ | ---- | --------------------------------------------- |
| `modules` | array  | 是   | 动态表单模块定义（字段类型、配置、层级嵌套等） |
| `formInfo`| object | 是   | 动态表单填写值（按 moduleKey 分组，含 field → value 映射） |

**请求示例：**

```json
{
  "PushType": "process_files",
  "Country": "DE",
  "ProcessMode": "sync",
  "Data": {
    "modules": [
      {
        "title": "zcy11111",
        "children": [
          {
            "type": "timePicker",
            "field": "6caa1043-2dca-4a14-ab15-fcf93d481a87",
            "title": "营业执照文件"
          },
          {
            "type": "input",
            "field": "b7b86f6c-2971-4508-993c-0e33925f877f",
            "title": "在先注册号"
          },
          {
            "type": "RadioWithOther",
            "field": "55663139-ed1a-46c0-996a-19cf07f5a5dd",
            "title": "街道门牌号(英文)",
            "options": [
              {"label": "选项一", "value": "1"},
              {"label": "选项二", "value": "2"}
            ]
          },
          {
            "type": "FileUpload",
            "field": "cd36f021-008c-4021-bae9-840ecb6d599b",
            "title": "村镇地址(英文)"
          },
          {
            "type": "group",
            "field": "7a89aa00-c82c-44d4-8ce5-2421e65c060f",
            "title": "街道门牌号",
            "children": [
              {
                "type": "radio",
                "field": "a64e0d42-094a-4612-bf7b-c83c4697fb17",
                "title": "申请人邮箱",
                "options": [
                  {"label": "选项01", "value": "1"},
                  {"label": "选项02", "value": "2"},
                  {"label": "选项03", "value": "3"}
                ]
              },
              {
                "type": "input",
                "field": "Ffinmrq3n6pxinc",
                "title": "输入框"
              }
            ]
          },
          {
            "type": "subForm",
            "field": "b7797041-68a5-41be-8d73-f1e51a973592",
            "title": "联系人名称",
            "children": [
              {
                "type": "input",
                "field": "656f12e4-fffa-42bd-9d5a-e7cf64c6a104",
                "title": "商标特殊意义"
              },
              {
                "type": "datePicker",
                "field": "b0eb77ac-da7d-48ee-9605-191bfc94860b",
                "title": "申请人英文名称"
              }
            ]
          },
          {
            "type": "tableForm",
            "field": "7a17e21b-c2cb-4b41-8324-e922df3bae80",
            "title": "申请人名称(英文)",
            "children": [
              {
                "type": "input",
                "field": "1dd7ff2b-afd8-4439-ade4-43d1363d30ee",
                "title": "自定义名称1111"
              },
              {
                "type": "datePicker",
                "field": "85224275-fba4-4471-a582-740d39683a93",
                "title": "自定义名称1222"
              }
            ]
          }
        ],
        "moduleKey": "c4e71575-daf1-43f5-9e41-d871c81cb436"
      }
    ],
    "formInfo": {
      "c4e71575-daf1-43f5-9e41-d871c81cb436": {
        "55663139-ed1a-46c0-996a-19cf07f5a5dd": "1",
        "6caa1043-2dca-4a14-ab15-fcf93d481a87": "16:26:11",
        "7a17e21b-c2cb-4b41-8324-e922df3bae80": [
          {
            "1dd7ff2b-afd8-4439-ade4-43d1363d30ee": "11",
            "85224275-fba4-4471-a582-740d39683a93": "2026-07-09"
          },
          {
            "1dd7ff2b-afd8-4439-ade4-43d1363d30ee": "22",
            "85224275-fba4-4471-a582-740d39683a93": "2026-07-20"
          }
        ],
        "7a89aa00-c82c-44d4-8ce5-2421e65c060f": [
          {
            "Ffinmrq3n6pxinc": "按时发11111",
            "a64e0d42-094a-4612-bf7b-c83c4697fb17": "2"
          },
          {
            "Ffinmrq3n6pxinc": "大概g11111",
            "a64e0d42-094a-4612-bf7b-c83c4697fb17": "2"
          }
        ],
        "b7797041-68a5-41be-8d73-f1e51a973592": {
          "656f12e4-fffa-42bd-9d5a-e7cf64c6a104": "11111",
          "b0eb77ac-da7d-48ee-9605-191bfc94860b": "2026-01-25"
        },
        "b7b86f6c-2971-4508-993c-0e33925f877f": "11111",
        "cd36f021-008c-4021-bae9-840ecb6d599b": "[{\"fileUrl\":\"common-test/2026/07/18/0d51c6e3-7441-4f37-95c2-b637951b2166.png\",\"fileName\":\"z1c1y.png\"}]"
      }
    }
  }
}
```

**Data 结构说明：**

| 层级         | 说明                                                         |
| ------------ | ------------------------------------------------------------ |
| `modules`    | 表单模块列表，每个模块有独立的 `moduleKey`，包含动态字段定义 |
| `modules[].children` | 模块内的字段列表，支持 `input` / `radio` / `timePicker` / `datePicker` / `FileUpload` / `group` / `subForm` / `tableForm` 等类型 |
| `modules[].children[].field` | 每个字段的全局唯一标识（GUID），用于 `formInfo` 中取值 |
| `formInfo`   | 表单填写值，以 `moduleKey` 为一级 key，字段 `field` 为二级 key 取值 |
| `formInfo.{moduleKey}.{field}` | 不同类型的值格式：`input`→字符串, `radio`→选项value, `tableForm`/`group`→对象数组, `subForm`→对象, `FileUpload`→JSON字符串 |
| `tableForm`  | 表格类型，值为对象数组，每行一个对象                      |
| `group`      | 分组类型，值为对象数组，每组一个对象                      |
| `subForm`    | 子表单类型，值为单个对象                                  |
| `FileUpload` | 文件上传类型，值为 JSON 字符串，解析后含 `fileUrl` 和 `fileName` |

**sync 响应 data（file1~file10）：**

| 字段     | 类型   | 说明                                   |
| -------- | ------ | -------------------------------------- |
| `file1`  | string | 授权书 Mandat PDF/ZIP OSS URL          |
| `file2`  | string | 营业执照 Gewerbeschein PDF OSS URL     |
| `file3`  | string | 法人身份证 ID PDF OSS URL              |
| `file4`  | string | 公司章程 Status PDF OSS URL            |
| `file5`  | string | 五合一 PDF OSS URL（DE 特有）          |
| `file6`  | string | 预留扩展文件 OSS URL                   |
| `file7`  | string | 预留扩展文件 OSS URL                   |
| `file8`  | string | 预留扩展文件 OSS URL                   |
| `file9`  | string | 预留扩展文件 OSS URL                   |
| `file10` | string | 预留扩展文件 OSS URL                   |

> 无对应文件时值为空字符串 `""`，调用方应判空处理。

**响应示例：**

```json
{
  "code": 200,
  "msg": "success",
  "ProcessMode": "sync",
  "data": {
    "file1": "http://vat-1259285998.cos.ap-guangzhou.myqcloud.com/vat_fix/de_declar/POVAT20251113000004_DE_Mandat.pdf",
    "file2": "http://vat-1259285998.cos.ap-guangzhou.myqcloud.com/vat_fix/de_declar/POVAT20251113000004_DE_BusinessLicense.pdf",
    "file3": "http://vat-1259285998.cos.ap-guangzhou.myqcloud.com/vat_fix/de_declar/POVAT20251113000004_DE_ID.pdf",
    "file4": "http://vat-1259285998.cos.ap-guangzhou.myqcloud.com/vat_fix/de_declar/POVAT20251113000004_DE_Status.pdf",
    "file5": "http://vat-1259285998.cos.ap-guangzhou.myqcloud.com/vat_fix/de_declar/POVAT20251113000004_DE_FiveInOne.pdf",
    "file6": "",
    "file7": "",
    "file8": "",
    "file9": "",
    "file10": ""
  }
}
```

**FR 国家的响应示例：**

```json
{
  "code": 200,
  "msg": "success",
  "ProcessMode": "sync",
  "data": {
    "file1": "http://vat-1259285998.cos.ap-guangzhou.myqcloud.com/vat_fix/fr_declar/POVAT20251113000004_FR_Mandat.pdf",
    "file2": "http://vat-1259285998.cos.ap-guangzhou.myqcloud.com/vat_fix/fr_declar/POVAT20251113000004_FR_Status_FR.pdf",
    "file3": "http://vat-1259285998.cos.ap-guangzhou.myqcloud.com/vat_fix/fr_declar/POVAT20251113000004_FR_ID_Final.pdf",
    "file4": "http://vat-1259285998.cos.ap-guangzhou.myqcloud.com/vat_fix/fr_declar/POVAT20251113000004_FR_BusinessLicense.pdf",
    "file5": "",
    "file6": "",
    "file7": "",
    "file8": "",
    "file9": "",
    "file10": ""
  }
}
```

**处理流程：**
1. 根据 `vat_business_record_id` 连表查询业务注册数据
2. 从附件表获取原始文件（营业执照、法人身份证等）
3. 生成授权书 Word → 嵌入签名 → 转 PDF
4. 拼接五合一 PDF（DE 专属）
5. 上传所有文件到 OSS
6. sync 模式直接返回 file1~file10；async 模式返回 job_id

---

### 4.2 extract_pdf — PDF 数据提取

**适用国家：** DE

**Data 参数：**

| 字段      | 类型   | 必填 | 说明                    |
| --------- | ------ | ---- | ----------------------- |
| `file_id` | string | 是   | 附件表 `Base_AnnexesFile.F_Id` |

**请求示例：**

```json
{
  "PushType": "extract_pdf",
  "Country": "DE",
  "ProcessMode": "sync",
  "Data": {
    "file_id": "c2e60d47-1a25-4e15-8b18-fb494da90a6a"
  }
}
```

**sync 响应 data：**

| 字段         | 类型   | 说明                         |
| ------------ | ------ | ---------------------------- |
| `file1`      | string | PDF 文件 OSS URL             |
| `file_id`    | string | 文件 ID / MRN 号             |
| `date`       | string | 申报日期（DD/MM/YYYY）       |
| `b00_amount` | string | B00 税额合计                 |
| `destination`| string | 目的地国家代码               |

---

### 4.3 extract_vat_register — 德国 VAT 注册数据提取 + 五合一生成

**适用国家：** DE

> 注：此 PushType 功能与 `process_files`（DE）重复，已合并到 `process_files` 中，此处保留仅为兼容旧调用方。新接入建议直接使用 `process_files`。

**Data 参数：**

| 字段                   | 类型   | 必填 | 说明                            |
| ---------------------- | ------ | ---- | ------------------------------- |
| `vat_business_record_id` | string | 是 | EPRBusinessRecord 表 ID（GUID）|

**sync 响应 data：**

| 字段     | 类型   | 说明                           |
| -------- | ------ | ------------------------------ |
| `file5`  | string | 五合一 PDF 上传 OSS 后的 URL   |

---

### 4.4 submit_declaration — XML 申报提交

| 字段           | 类型   | 必填 | 说明                                     |
| -------------- | ------ | ---- | ---------------------------------------- |
| `declaration_type` | string | 是 | 申报类型：`UStVA` / `USt` / `ZMDO`   |
| `year`         | int    | 是   | 申报年份                                 |
| `xml_data`     | object | 否   | XML 填充数据（不传则用默认模板）         |
| `is_test`      | bool   | 否   | 是否测试模式，默认 `false`（生产环境）   |

**请求示例：**

```json
{
  "PushType": "submit_declaration",
  "Country": "DE",
  "Data": {
    "declaration_type": "UStVA",
    "year": 2026,
    "xml_data": {
      "steuernummer": "12345678901",
      "zeitraum": "2026-01",
      "umsatz_steuerbar": "50000.00"
    },
    "is_test": false
  }
}
```

**响应 data：**

| 字段           | 类型   | 说明                       |
| -------------- | ------ | -------------------------- |
| `transfer_ticket` | string | ERIC 传输票据（凭证号）|
| `pdf_url`      | string | 申报回执 PDF OSS URL       |
| `status`       | string | 提交状态：`success` / `pending` / `error` |
| `error_code`   | string | 错误码（仅失败时返回）     |

**处理流程：**
1. 根据 `declaration_type` + `year` 选取 XML 模板
2. 填充 `xml_data` 到模板
3. 调用 ERIC API（Python `run_ericdemo_year.py`）提交
4. 解析回执 PDF，上传 OSS
5. 返回结果

---

### 4.4 query_declaration — 查询申报结果

**适用国家：** DE

**Data 参数：**

| 字段             | 类型   | 必填 | 说明                     |
| ---------------- | ------ | ---- | ------------------------ |
| `transfer_ticket`| string | 是   | ERIC 传输票据            |
| `declaration_type`| string | 是  | 申报类型：`UStVA` / `USt` / `ZMDO` |

**请求示例：**

```json
{
  "PushType": "query_declaration",
  "Country": "DE",
  "Data": {
    "transfer_ticket": "ERIC20260601XXXXXX",
    "declaration_type": "UStVA"
  }
}
```

**响应 data：**

| 字段       | 类型   | 说明                       |
| ---------- | ------ | -------------------------- |
| `status`   | string | `processed` / `pending` / `error` |
| `pdf_url`  | string | 处理结果 PDF OSS URL       |
| `error_code` | string | 错误码（仅失败时）         |

---

### 4.5 extract_import_data — 进口增值税数据提取

**适用国家：** DE

**Data 参数：**

| 字段      | 类型   | 必填 | 说明                    |
| --------- | ------ | ---- | ----------------------- |
| `file_id` | string | 是   | 附件表 `Base_AnnexesFile.F_Id` |

**请求示例：**

```json
{
  "PushType": "extract_import_data",
  "Country": "DE",
  "Data": {
    "file_id": "c2e60d47-1a25-4e15-8b18-fb494da90a6a"
  }
}
```

**响应 data：**

| 字段              | 类型   | 说明                     |
| ----------------- | ------ | ------------------------ |
| `import_amount`   | string | 进口增值税金额           |
| `import_date`     | string | 进口日期                 |
| `customs_doc_id`  | string | 海关文件编号             |

---

### 4.6 query_job — 查询异步任务结果

**说明：** 当 `ProcessMode=async` 时，`process_files` 等 PushType 返回 `job_id`。调用方通过此 PushType 轮询任务结果。

**Data 参数：**

| 字段     | 类型   | 必填 | 说明                     |
| -------- | ------ | ---- | ------------------------ |
| `job_id` | string | 是   | 异步任务 ID              |

**请求示例：**

```json
{
  "PushType": "query_job",
  "Country": "DE",
  "ProcessMode": "sync",
  "Data": {
    "job_id": "JOB20260730ABCD1234"
  }
}
```

**响应 data：**

| 字段       | 类型   | 说明                                                |
| ---------- | ------ | --------------------------------------------------- |
| `status`   | string | `pending`（处理中）/ `done`（完成）/ `error`（失败） |
| `file1` ~ `file10` | string | 文件 OSS URL（仅 `done` 状态返回）                |
| `error_msg`| string | 错误消息（仅 `error` 状态返回）                     |

```json
{
  "code": 200,
  "msg": "success",
  "ProcessMode": "async",
  "data": {
    "status": "done",
    "file1": "http://vat-1259285998.cos.ap-guangzhou.myqcloud.com/vat_fix/de_declar/POVAT20251113000004_DE_Mandat.pdf",
    "file2": "http://vat-1259285998.cos.ap-guangzhou.myqcloud.com/vat_fix/de_declar/POVAT20251113000004_DE_BusinessLicense.pdf",
    "file3": "",
    "file4": "",
    "file5": "",
    "file6": "",
    "file7": "",
    "file8": "",
    "file9": "",
    "file10": ""
  }
}
```

---

## 5. ProcessMode 处理模式说明

| 模式    | 说明                                                         | 响应内容                                      |
| ------- | ------------------------------------------------------------ | --------------------------------------------- |
| `sync`  | 同步实时处理，等待文件生成完成后返回。适合文件少、处理快的场景。 | `data.file1` ~ `data.file10` 直接返回文件 URL |
| `async` | 异步处理，立即返回 `job_id`，后台生成文件。适合文件多、耗时长的场景。 | `data.job_id` + `data.message`                 |

### 5.1 使用流程

```
sync 模式:
  请求 → 服务端处理（同步等待）→ 返回 file1~file10 URL

async 模式:
  请求 → 返回 job_id → 轮询 query_job（每 10s）→ done 时返回 file1~file10 URL
```

### 5.2 async 模式回调

async 模式完成后，服务端会主动 HTTP POST 回调到请求中指定的回调地址：

```json
POST <callback_url>
{
  "job_id": "JOB20260730ABCD1234",
  "status": "done",
  "data": {
    "file1": "...",
    "file2": "...",
    ...
    "file10": "..."
  }
}
```

---

## 6. 认证设计

### 6.1 Token 管理

Token 配置存储在 `config/api_config.php`：

```php
define('UNIFIED_API_TOKENS', serialize([
    'prod_token_abc123...',   // 生产环境主 Token
    'test_token_xyz789...',   // 测试环境 Token
]));
```

### 6.2 验证流程

```
请求到达 → 检查 Authorization Header → 提取 Bearer Token
  → 比对配置中的 Token 列表 → 匹配成功则继续
  → 匹配失败返回 401
```

### 6.3 安全建议

- 生产环境 Token 建议 32 位以上随机字符串
- 定期轮换 Token
- 建议配合 IP 白名单使用
- 生产环境强制 HTTPS

---

## 7. 错误码汇总

| code | msg 示例                                   | 触发场景                 |
| ---- | ------------------------------------------ | ------------------------ |
| 401  | 无效的授权令牌                             | Token 缺失或无效         |
| 400  | 缺少 PushType 参数                         | PushType 未传            |
| 400  | 缺少 Country 参数                          | Country 未传             |
| 400  | 缺少 ProcessMode 参数                      | ProcessMode 未传         |
| 400  | 不支持的 ProcessMode: xxx                  | ProcessMode 取值错误     |
| 400  | 缺少 Data 参数                             | Data 未传                |
| 400  | 缺少有效的 vat_business_record_id 参数      | Data 字段校验失败        |
| 400  | 缺少有效的 file_id 参数                     | Data 字段校验失败        |
| 400  | PDF 文件识别失败: 目的地不是德国(DE)        | PDF 提取逻辑校验         |
| 400  | 文件不是 PDF 格式                          | 文件格式不匹配           |
| 400  | 不支持的 PushType: xxx                     | PushType 不在枚举中      |
| 400  | 不支持的 Country: xxx                      | Country 未实现           |
| 500  | pdfplumber 库未安装                        | Python 依赖缺失          |

---

## 8. 代码结构设计

### 8.1 目录结构

```
vat_api_de_client/
├── server/
│   └── unified_api.php              ← 新增：统一 API 入口
├── src/
│   ├── Service/
│   │   └── UnifiedApiService.php    ← 新增：核心路由 + 业务编排
│   ├── Api/
│   │   ├── BaseApi.php              （已有）
│   │   ├── GermanVatApi.php         （已有）
│   │   ├── UstApi.php               （已有）
│   │   ├── UstVaApi.php             （已有）
│   │   └── ZmdoApi.php              （已有）
│   └── Config/
│       └── Config.php               （已有）
├── dmon/
│   ├── test7.py                     （已有）PDF 提取
│   └── vat_De.php                   （已有）德国 VAT 数据
├── config/
│   └── api_config.php               ← 修改：新增 UNIFIED_API_TOKENS
├── api_python/
│   └── run_ericdemo_year.py         （已有）ERIC 提交
└── doc/
    └── unified_api_design.md        ← 本文档
```

### 8.2 核心类设计

#### UnifiedApiService（新增）

```php
class UnifiedApiService {
    // Token 验证
    public function verifyToken(string $token): bool;

    // 请求路由（含 ProcessMode 分发）
    public function route(string $pushType, string $country, string $processMode, array $data): array;

    // 各 PushType 处理器
    private function handleProcessFiles(string $country, array $data): array;
    private function handleExtractPdf(string $country, array $data): array;
    private function handleExtractVatRegister(string $country, array $data): array;
    private function handleSubmitDeclaration(string $country, array $data): array;
    private function handleQueryDeclaration(string $country, array $data): array;
    private function handleQueryJob(string $country, array $data): array;
    private function handleExtractImportData(string $country, array $data): array;
}
```

#### unified_api.php（新增入口）

```php
// 入口伪代码
$raw = json_decode(file_get_contents('php://input'), true);

// 1. Token 校验
$token = extractBearerToken($_SERVER['HTTP_AUTHORIZATION'] ?? '');
if (!$service->verifyToken($token)) {
    response(401, '无效的授权令牌');
}

// 2. 参数校验
if (empty($raw['PushType'])) response(400, '缺少 PushType 参数');
if (empty($raw['Country']))  response(400, '缺少 Country 参数');
if (empty($raw['Data']))     response(400, '缺少 Data 参数');

// 3. 路由分发
$result = $service->route($raw['PushType'], $raw['Country'], $raw['Data']);

// 4. 返回结果
response($result['code'], $result['msg'], $result['data']);
```

### 8.3 路由分发逻辑

```
PushType                   → 调用方法
─────────────────────────────────────────
process_files              → handleProcessFiles()
extract_pdf                → handleExtractPdf()
extract_vat_register       → handleExtractVatRegister()
submit_declaration         → handleSubmitDeclaration()
query_declaration          → handleQueryDeclaration()
query_job                  → handleQueryJob()
extract_import_data        → handleExtractImportData()
```

---

## 9. 与现有 API 的兼容迁移

| 旧接口                            | 对应新 PushType               |
| --------------------------------- | ----------------------------- |
| `vat_deferred_7_query_api.php`    | `extract_pdf`                 |
| `vat_deferred_6_query_api.php`    | `extract_import_data`         |
| `vat_query_api.php`               | `process_files`（DE）         |
| `vat_Application_Delay_api.php`   | `process_files`（DE，含延期） |
| `epr_weee_contract_api.php`（RPAP）| `process_files`（FR）         |
| `src/Api/GermanVatApi.php` 的各方法 | `submit_declaration` / `query_declaration` |

> 迁移期间新旧接口并行运行，旧接口逐步标记 `@deprecated`，稳定后再下线。

---

## 10. 测试用例

### 10.1 cURL 示例

```bash
# 文件处理（sync 模式，最常用）
curl -X POST http://localhost/vat_api_de_client/server/unified_api.php \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer your_token_here" \
  -d '{
    "PushType": "process_files",
    "Country": "DE",
    "ProcessMode": "sync",
    "Data": {"vat_business_record_id": "5CBCDE28-1FD5-400A-8D3D-27D178B62FE8"}
  }'

# 文件处理（async 模式）
curl -X POST http://localhost/vat_api_de_client/server/unified_api.php \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer your_token_here" \
  -d '{
    "PushType": "process_files",
    "Country": "FR",
    "ProcessMode": "async",
    "Data": {"vat_business_record_id": "5CBCDE28-1FD5-400A-8D3D-27D178B62FE8"}
  }'

# 查询异步任务结果
curl -X POST http://localhost/vat_api_de_client/server/unified_api.php \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer your_token_here" \
  -d '{
    "PushType": "query_job",
    "Country": "DE",
    "ProcessMode": "sync",
    "Data": {"job_id": "JOB20260730ABCD1234"}
  }'

# PDF 数据提取
curl -X POST http://localhost/vat_api_de_client/server/unified_api.php \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer your_token_here" \
  -d '{
    "PushType": "extract_pdf",
    "Country": "DE",
    "ProcessMode": "sync",
    "Data": {"file_id": "c2e60d47-1a25-4e15-8b18-fb494da90a6a"}
  }'
```

### 10.2 Python 调用示例

```python
import requests

url = "http://localhost/vat_api_de_client/server/unified_api.php"
headers = {
    "Content-Type": "application/json",
    "Authorization": "Bearer your_token_here"
}

# sync 模式 — 文件处理
payload = {
    "PushType": "process_files",
    "Country": "DE",
    "ProcessMode": "sync",
    "Data": {"vat_business_record_id": "5CBCDE28-1FD5-400A-8D3D-27D178B62FE8"}
}

resp = requests.post(url, json=payload, headers=headers)
result = resp.json()
if result["code"] == 200 and result["ProcessMode"] == "sync":
    for i in range(1, 11):
        url = result["data"].get(f"file{i}", "")
        if url:
            print(f"file{i}: {url}")
```

---

## 11. 后续扩展

| 扩展项       | 说明                                         |
| ------------ | -------------------------------------------- |
| FR 国家支持  | `process_files`（FR）已通过 `epr_weee_contract_api.php` 支持 |
| NL 国家支持  | 增加荷兰 VAT 申报接口                        |
| 日志记录     | 每次请求记录 `PushType` + `Country` + `ProcessMode` + 耗时到日志 |
| 限流机制     | Token 级别 QPS 限制，防止滥用                |
| async 回调   | 异步任务完成后 HTTP POST 回调到指定 URL      |
| Swagger 文档 | 生成 OpenAPI 3.0 规范文档                    |

---

## 附录 A：Country 枚举

| 代码 | 国家       | 已支持 PushType                              |
| ---- | ---------- | -------------------------------------------- |
| DE   | 德国       | 全部                                        |
| FR   | 法国       | `process_files`                              |
| NL   | 荷兰       | 预留                                         |
| ES   | 西班牙     | 预留                                         |
| BE   | 比利时     | 预留                                         |
