# 发票查验接口 · AI 接入包

> 你是被客户委托来对接「发票查验接口」的 AI 助手。请严格按本文档,用客户指定的编程语言写出可运行的调用代码。
> 全部票种(数电/增值税/航空/铁路/区块链等)都用**这一个接口**,按票种传不同字段即可,系统自动路由。

## 1. 接口地址
```
POST https://openapi.kailingteck.com/api/v1/verify
Content-Type: application/json
```

## 2. 鉴权(每次请求必带三个头)
- `X-App-Key`: 平台分配的 app_key
- `X-Timestamp`: Unix 秒级时间戳(与服务器时差需 ≤300 秒,否则报防重放)
- `X-Sign`: 签名 = `sha256(app_key + timestamp + 请求体原文 + app_secret)` 的小写十六进制

**签名务必注意**: 参与 sha256 的"请求体"必须和实际发送的 body **逐字节一致**(包括空格)。最稳的做法是:先把 body 序列化成一个字符串,签名和发送都用这同一个字符串。

签名参考(伪代码):
```
body = JSON.stringify(payload)            // 固定下来这个字符串
ts   = floor(now_seconds)
sign = sha256_hex(app_key + ts + body + app_secret)
```

## 3. 请求参数
| 字段 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| fphm | string | 必填 | 发票号码 |
| fpdm | string | 条件 | 发票代码(数电票无代码可不传) |
| kprq | string | 条件 | 开票日期 yyyy-MM-dd |
| je | number | 条件 | 不含税金额(专用发票类必填) |
| jym | string | 条件 | 校验码后 6 位(普通发票类必填;区块链发票为票面短码) |
| jshj | number | 条件 | 价税合计(数电票 / 通用电子发票必填) |
| xfsbh | string | 条件 | 销售方纳税人识别号(区块链 / 通用电子发票必填) |
| dq | string | 条件 | 地区码(仅特殊票种需要,见地区码表) |
| customReqId | string | 可选 | 调用方自定义请求 ID,便于对账与排障 |
| cyList | array | 可选 | 批量查验:多张票字段放入数组(单次最多 50 张);不传则按上述扁平字段查单张 |

### 不同票种传哪些字段(照这张表传即可)
| 票种 | 必填字段 | 地区码 dq |
| --- | --- | --- |
| 数电普通发票 | 发票号码 + 开票日期 + 价税合计 | 不用填 |
| 数电专用发票 | 发票号码 + 开票日期 + 价税合计 | 不用填 |
| 数电纸质(专用发票) | 发票代码 + 发票号码 + 开票日期 + 不含税金额 | 不用填 |
| 数电纸质(普通发票) | 发票代码 + 发票号码 + 开票日期 + 校验码后6位 | 不用填 |
| 增值税专用发票 | 发票代码 + 发票号码 + 开票日期 + 不含税金额 | 不用填 |
| 增值税普通发票 / 电子普票 / 通行费 | 发票代码 + 发票号码 + 开票日期 + 校验码后6位 | 不用填 |
| 区块链发票(深圳/北京/云南) | 发票代码 + 发票号码 + 销方税号 + 校验码 | 4403 / 1100 / 5300 |
| 通用电子发票(广东/浙江宁波) | 发票代码 + 发票号码 + 销方税号 + 价税合计 | 4400 / 3300 |
| 车辆通行费发票(江苏) | 发票代码 + 发票号码 + 开票日期 + 不含税金额 | 3200 |

### 地区码 dq(仅特殊票种需要)
| dq | 地区 | 适用 |
| --- | --- | --- |
| (不传) | 全国 | 增值税 / 数电 / 数电纸质发票,默认通道 |
| 4403 | 深圳 | 区块链发票 |
| 1100 | 北京 | 区块链发票 |
| 5300 | 云南 | 区块链发票 |
| 4400 | 广东 | 通用电子发票 |
| 3300 | 浙江 / 宁波 | 通用电子发票 |
| 3200 | 江苏 | 车辆通行费发票 |

## 4. 响应结构
判断是否查验通过,**看 `data.verified`**(true=真票且要素一致)。不要只看外层 code。

### 公共字段(所有票种一致)
| 字段 | 中文名 | 类型 | 说明 |
| --- | --- | --- | --- |
| code | 业务编码 | string | CYT_00000 为成功,其余见业务编码表 |
| msg | 提示信息 | string |  |
| requestId | 请求标识 | string | 排障可提供 |
| data.verified | 是否查验通过 | boolean | 真票且要素一致 |
| data.channel | 路由通道 | string | 本次自动选用的查验通道 |
| data.result | 逐票结果 | array | 每张票一项 |
| result[].success | 该票是否成功 | boolean |  |
| result[].fphm | 发票号码 | string |  |
| result[].kprq | 开票日期 | string |  |
| result[].fplx | 发票类型代码 | string | 见附录 |
| result[].times | 当日查验次数 | integer |  |
| result[].data | 全票面明细 | object | 字段随票种不同,见下表 |

### 票面明细 result[].data(按票种,字段不同)
**增值税 / 数电发票(普票·专票·数电纸质)**

| 字段 | 中文名 | 类型 |
| --- | --- | --- |
| xfmc | 销售方名称 | string |
| xfsbh | 销售方税号 | string |
| gfmc | 购买方名称 | string |
| gfsbh | 购买方税号 | string |
| je | 不含税金额 | number |
| se | 税额 | number |
| jshj | 价税合计 | number |
| jshjcn | 价税合计(大写) | string |
| fpztDm | 发票状态代码 | string |
| bz | 备注 | string |
| hwxx[] | 货物/服务明细 | array |
| hwxx[].mc | 货物或服务名称 | string |
| hwxx[].slv | 税率 | number |
| hwxx[].se | 明细税额 | number |
| hwxx[].spbm | 税收分类编码 | string |

**航空运输电子客票行程单** —— 税额字段是 zzsse(不是 se);税费拆得很细

| 字段 | 中文名 | 类型 |
| --- | --- | --- |
| lkxm | 旅客姓名 | string |
| sfzjhm | 证件号码 | string |
| pj | 票价 | number |
| ryfjf | 燃油附加费 | number |
| zzssl | 增值税税率 | string |
| zzsse | 增值税税额 | number |
| mhfzjj | 民航发展基金 | number |
| qtsf | 其他税费 | number |
| bxf | 保险费 | number |
| jshj | 合计 | number |
| fpztDm | 发票状态代码 | string |
| hwxx[] | 航段明细 | array |
| hwxx[].cyr | 承运人 | string |
| hwxx[].hbh | 航班号 | string |
| hwxx[].sfz | 始发站 | string |
| hwxx[].mdz | 目的站 | string |
| hwxx[].qfsj | 起飞时间 | string |
| hwxx[].zwdj | 座位等级 | string |

**铁路电子客票** —— 旅客字段是 name(非航空票的 lkxm);税额是 se;车次/席别为平铺字段、无 hwxx

| 字段 | 中文名 | 类型 |
| --- | --- | --- |
| name | 旅客姓名 | string |
| zjh | 证件号码 | string |
| cc | 车次 | string |
| cfz | 出发站 | string |
| ddz | 到达站 | string |
| ccrq | 乘车日期 | string |
| cfsj | 出发时间 | string |
| xw | 席别 | string |
| je | 不含税金额 | number |
| se | 税额 | number |
| slv | 税率 | string |
| jshj | 价税合计(票价) | number |
| fpztDm | 发票状态代码 | string |

**区块链发票(深圳/北京/云南)** —— 局端只验真,字段较少;qkl=true 标识区块链票

| 字段 | 中文名 | 类型 |
| --- | --- | --- |
| xfmc | 销售方名称 | string |
| xfsbh | 销售方税号 | string |
| gfmc | 购买方名称 | string |
| je | 不含税金额 | number |
| se | 税额 | number |
| jshj | 价税合计 | number |
| jym | 校验码 | string |
| fpzt | 发票状态 | string |
| qkl | 区块链标识 | boolean |

> ⚠️ 注意各票种"税额"字段名不同:普通/增值税/铁路票是 `se`,航空客票是 `zzsse`。

## 5. 业务编码
| code | 含义 |
| --- | --- |
| CYT_00000 | 查验成功 / 真票 |
| CYT_10001 | 查无此票 |
| CYT_10002 | 票面要素与底账不一致 |
| CYT_40001 | 请求参数缺失或格式错误 |
| CYT_40100 | 缺少鉴权请求头 |
| CYT_40101 | app_key 不存在或已停用 |
| CYT_40102 | 签名校验失败 |
| CYT_40103 | 请求时间戳过期(防重放) |
| CYT_42900 | 配额已用尽 |
| CYT_50200 | 查验源暂时不可用,请稍后重试 |

## 6. 失败处理(是否重试)
| 情况 | 是否重试 | 说明 |
| --- | --- | --- |
| 查验源/税局服务异常(CYT_50200) | 可重试 | 多为短时波动,建议 15–20 分钟后再试;月底税局停机维护也会偶发 |
| 请求时间戳过期(CYT_40103) | 可重试 | 校准本地时间后重新签名发起即可 |
| 查无此票(CYT_10001) | 不要重试 | 可能离线开票数据未上传,或要素有误;反复查会占用税局当日查验次数 |
| 要素不一致(CYT_10002) | 不要重试 | 核对四要素是否与票面一致,金额是否为不含税金额 |
| 参数错误(CYT_40001) | 不要重试 | 按票种传参对照表核对必填字段 |
| 签名/鉴权失败(CYT_40102/40101) | 不要重试 | 检查 app_key/app_secret 与签名算法 |
| 配额已用尽(CYT_42900) | 不要重试 | 联系商务续费或调高配额 |

## 7. 请求示例(数电普票)
```json
POST https://openapi.kailingteck.com/api/v1/verify
{ "fphm": "26112000002558759236", "kprq": "2026-06-23", "jshj": 377.00 }
```

---

## 给 AI 的任务
请用客户指定的语言(如未指定则用客户项目的主语言),实现一个 `verifyInvoice(fields)` 函数:
1. 按上面"鉴权"算好 X-Timestamp 与 X-Sign;
2. POST 到接口地址,带三个鉴权头;
3. 解析响应,以 `data.verified` 判断真伪,失败时读 `data.reason`;
4. 票面明细按票种从 `data.result[0].data` 取(税额注意 se / zzsse 区别);
5. 失败按第 6 节决定是否重试。
把 app_key / app_secret 留成配置项,不要硬编码。
