# 供应商上传发票校验

> 上传发票时对图片/PDF 做 OCR 抬头硬校验，抬头与申请单不一致直接拦截；含 PDF 要求、报错文案与抬头比对规则

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

## 要点

- 上传接口 `POST /invoice/task/upload`，入参只有申请单 `id` 和发票地址 `invoiceUrl`。
- 后端对发票图片 / PDF 做 OCR，识别票面 **购买方名称**，与申请单抬头不一致直接拦截，报错文案透出给供应商。数电票原生 PDF 也支持。
- 金额、税号、重复上传均 **不拦截**，只拦抬头。
- 「已开票」状态重新上传发票时，同样会过这套校验。
- 校验由 Nacos 开关 `xm.invoice.ocr-check-enabled` 控制（默认关闭），关闭时不做 OCR，直接按原规则上传。

## 接口定义

### POST /invoice/task/upload · 上传发票

调用方：供应商小程序。鉴权、Header 与其他发票接口一致。

#### 请求参数

| 字段 | 类型 | 必填 | 说明 |
|----|----|----|----|
| `id` | Long | 是 | 开票申请单 ID |
| `invoiceUrl` | String | 是 | 发票图片（jpg/png）或 PDF 的 OSS 地址 |

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

#### 请求示例

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

#### 响应

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

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

## 报错文案速查

| msg 文案 | 触发场景 | 前端处理建议 |
|----|----|----|
| 发票抬头“云南乙花卉有限公司”与申请单抬头“云南甲花卉有限公司”不一致，请核对后重新开具 | OCR 识别出的购买方名称与申请单抬头不一致（动态文案，含两边抬头） | toast / 弹窗展示，引导重新开票 |
| 发票识别失败，请确认图片清晰完整后重试 | 传的不是发票、图片模糊不完整，或未识别出购买方名称 | toast，引导重新拍摄 / 上传 |
| 发票识别失败，请稍后重试 | OCR 服务调用异常 | toast，可重试同一文件 |
| 当前状态不支持上传发票 | 申请单状态既非待开票 / 逾期，也非已开票重传 | toast 并刷新列表 |
| 申请单不存在 | id 错误或不属于当前供应商 | toast 并刷新列表 |

## 抬头比对规则

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

## 联调注意

- 联调前先和后端确认环境的 `xm.invoice.ocr-check-enabled` 已打开，否则任何文件都能上传成功。
- 测试请用真实票面文件，随便找张图会命中「发票识别失败」。
