# 供应商上传发票校验

> 上传接口新增 uploadType：图片/PDF 走 OCR 抬头硬校验，二维码取票链接校验税务局域名；含报错文案速查与二维码解析前端职责

- 类型：前端对接
- 分类：开票
- 端：供应商小程序
- 后端服务：supplier
- 更新：2026-09-28
- 网页版：https://doc.xfun.fun/supplier-invoice-upload-api

## 总览：改了什么

- 还是原来的上传接口 `POST /invoice/task/upload`，**新增一个入参 `uploadType`**，前端改动很小。
- **图片/PDF 上传**（原流程）：后端会对发票做 OCR 识别，**抬头与申请单不一致直接拦截**，报错文案透出给供应商。数电票原生 PDF 也支持，直接传 PDF 的 OSS 地址即可。
- **新增二维码上传**：供应商开完数电票后拿到的「扫码下载发票」二维码，前端解析出二维码里的链接后传给后端，后端校验链接是否为税务局域名。
- 金额、税号、重复上传均 **不拦截**，只拦抬头（产品拍板口径）。
- 「已开票」状态重新上传发票时，同样会过这套校验。

## 两种上传方式与前端职责

#### 方式一：发票图片 / PDF（uploadType=1，或不传）

- 前端把发票文件（jpg/png/PDF）上传 OSS 拿到 URL，作为 `invoiceUrl` 提交 —— 与现状完全一致。
- 后端 OCR 识别票面抬头（购买方名称），与申请单抬头比对，不一致返回报错。

> [!WARNING]
> **PDF 注意：** OSS 上传时必须保留 `.pdf` 扩展名（后端按 URL 后缀区分 PDF 与图片识别通道）。如果上传组件会改文件名，确认生成的 URL 以 `.pdf` 结尾。

#### 方式二：发票二维码（uploadType=2） **新增**

- 供应商在税务 App 开票成功后有「扫码下载发票」二维码（往往是截图保存）。
- **前端负责解析二维码**拿到其中的链接文本：拍照/相册选图后用二维码解码（小程序可用 `wx.scanCode` 扫码，或选图后走解码库），把解析出来的 **URL 字符串**作为 `invoiceUrl` 提交。
- 后端只校验该链接是不是税务局域名（规则见下），**不做 OCR**。

> [!NOTE]
> **常见误区：** uploadType=2 时 `invoiceUrl` 传的是 **二维码内容里解析出的取票链接**（`https://…chinatax.gov.cn/…`），**不是**二维码截图的 OSS 图片地址。传成图片地址会报「取票链接无效」。

## 接口定义

### POST /invoice/task/upload · 入参变更

上传发票（原接口）。鉴权、Header 与现状一致。

#### 请求参数

| 字段 | 类型 | 必填 | 说明 |
|----|----|----|----|
| `id` | Long | 是 | 开票申请单 ID |
| `invoiceUrl` | String | 是 | uploadType=1：发票图片/PDF 的 OSS 地址；uploadType=2：二维码解析出的税务局取票链接 |
| `uploadType` | Integer | 否 | **新增** 1 = 发票图片/PDF（默认，不传按 1 处理）；2 = 发票二维码取票链接 |

#### 请求示例：图片 / PDF

```json
{
  "id": 123,
  "invoiceUrl": "https://image-bulky.xunmengvip.com/supplier/invoice/dzfp_2611xxxx.pdf",
  "uploadType": 1
}
```

#### 请求示例：二维码取票链接

```json
{
  "id": 123,
  "invoiceUrl": "https://dppt.yunnan.chinatax.gov.cn/invoice-query/invoice-download?xxx",
  "uploadType": 2
}
```

#### 响应

```json
{ "code": 0, "msg": "success", "data": true }
```

校验不通过时 `code != 0`，`msg` 为面向供应商的完整文案，**直接 toast 展示即可**，无需按 code 分支。

## 报错文案速查

| msg 文案 | 触发场景 | 前端处理建议 |
|----|----|----|
| 发票抬头“××”与申请单抬头“××”不一致，请核对后重新开具 | OCR 识别出的购买方名称与申请单抬头不一致（动态文案，含两边抬头） | toast / 弹窗展示，引导重新开票 |
| 发票识别失败，请确认图片清晰完整后重试 | 传的图不是发票（如二维码截图误走图片通道）、图片模糊不完整 | toast，引导重新拍摄/上传 |
| 发票识别失败，请稍后重试 | OCR 服务临时异常 | toast，可重试同一文件 |
| 取票链接无效，请上传税务局开具的取票二维码 | uploadType=2 且链接不是税务局域名（或误传了图片地址） | toast，检查是否解析对了二维码内容 |
| 当前状态不支持上传发票 | 申请单状态既非待开票/逾期，也非已完成重传 | toast 并刷新列表 |
| 申请单不存在 | id 错误或不属于当前供应商 | toast 并刷新列表 |

## 校验规则细节

#### 抬头比对（uploadType=1）

- 比对前做归一化：**忽略所有空白字符、全角括号（）与半角括号 () 视为相同**，其余必须完全一致。
- 例：申请单抬头「云南甲花卉（集团）有限公司」，票面「云南甲花卉(集团) 有限公司」→ 通过；票面「云南乙花卉有限公司」→ 拦截。

#### 取票链接域名（uploadType=2）

- 必须是 **https**，且 host 为 `chinatax.gov.cn` 或其子域（可带端口）。
- 合法：`https://inv-veri.chinatax.gov.cn/xxx`、`https://dppt.yunnan.chinatax.gov.cn:8443/xxx`
- 非法：`https://chinatax.gov.cn.evil.com/x`、`https://evil.com/chinatax.gov.cn`、`http://…`（非 https）、OSS 图片地址

## 联调注意

- 测试环境已部署本功能；图片 OCR 校验由后端开关控制（`xm.invoice.ocr-check-enabled`），联调前和后端确认测试环境开关已打开。二维码域名校验 **不走开关**，恒生效。
- OCR 校验对真实「电子发票(普通发票) PDF」已实测通过；测试时请用真实票面文件，随便找张图会命中「发票识别失败」。
- 上传方式的选择建议做成两个入口（对应原型「上传发票图片 / 上传发票二维码」），分别固定 uploadType=1 / 2。
