hl-api-changelog/changelogs/2026-03/2026-03-25_invoice_full_api.md
API Changelog Bot 18ff6a008b docs: 发票模块完整API文档(管理端+小程序端)
包含全部10个接口的详细定义:请求参数、响应字段、校验规则、
调用示例、状态流转、关联字典、前端页面对接建议。

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 09:50:01 +08:00

397 行
11 KiB
Markdown

此文件含有模棱两可的 Unicode 字符

此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。

# 发票模块完整 API 文档(管理端 + 小程序端)
> 发票功能:用户对已支付订单申请开票 → 管理员审核开票 → 用户下载电子发票。支持换开最多3次
---
## 一、状态流转
```
用户申请 → PENDING待开票
├─ 管理员开票完成 → ISSUED已开票
└─ 管理员标记失败 → FAILED开票失败→ 管理员重新开票 → ISSUED
ISSUED → 用户换开 → 原发票 VOIDED已作废+ 新发票 PENDING
ISSUED → 管理员作废 → VOIDED
```
## 二、关联字典
| 字典 dict_type | 说明 | 枚举值 |
|---|---|---|
| `invoice_status` | 发票状态 | `PENDING`=待开票, `ISSUED`=已开票, `FAILED`=开票失败, `VOIDED`=已作废 |
| `invoice_title_type` | 抬头类型 | `PERSONAL`=个人, `COMPANY`=企业 |
## 三、通用响应结构 InvoiceVO
所有发票接口返回的数据结构(管理端直接返回 `InvoiceVO`,小程序端经 Feign 透传为 `Map<String, Object>`,字段相同):
| 字段 | 类型 | 说明 |
|---|---|---|
| `id` | String | 发票ID |
| `orderId` | String | 订单ID |
| `orderNo` | String | 订单编号 |
| `amount` | String | 发票金额(元) |
| `titleType` | String | 抬头类型:`PERSONAL`=个人 / `COMPANY`=企业(字典 `invoice_title_type` |
| `invoiceTitle` | String | 发票抬头(个人姓名或企业名称) |
| `taxNumber` | String | 纳税人识别号(企业发票有值) |
| `email` | String | 接收邮箱 |
| `invoiceType` | String | 发票类型,固定为"电子普通发票" |
| `invoiceContent` | String | 发票内容,固定为"明细" |
| `status` | String | 发票状态(字典 `invoice_status` |
| `fileUrl` | String | 发票文件URL已开票后有值 |
| `failReason` | String | 开票失败原因(失败时有值) |
| `contactPhone` | String | 联系电话 |
| `remark` | String | 备注信息 |
| `bankName` | String | 开户行(企业发票) |
| `bankAccount` | String | 银行账号(企业发票) |
| `companyAddress` | String | 企业地址(企业发票) |
| `companyPhone` | String | 企业电话(企业发票) |
| `invoiceNo` | String | 发票号码(已开票后有值) |
| `issuedAt` | String | 开票时间,格式 `yyyy-MM-dd HH:mm:ss` |
| `issuedBy` | String | 开票操作人管理员ID |
| `userId` | String | 申请用户ID |
| `originalInvoiceId` | String | 原发票ID换开时有值,指向被作废的原发票 |
| `reissueCount` | Integer | 换开次数0~3 |
| `createdAt` | String | 申请时间 |
| `updatedAt` | String | 更新时间 |
---
## 四、小程序端接口C端用户
> 网关路径前缀:`/mp/invoice`
> 需要用户登录(携带 Authorization: Bearer token
### 4.1 申请开票
```
POST /mp/invoice/apply
```
**使用场景**:用户在订单详情页点击「申请发票」,填写开票信息后提交
**请求参数** (JSON Body)
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| `orderId` | String | ✅ | 订单ID |
| `invoiceTitle` | String | ✅ | 发票抬头(个人姓名/企业名称,最长100字 |
| `titleType` | String | 否 | 抬头类型,默认 `PERSONAL`。可选:`PERSONAL`=个人, `COMPANY`=企业 |
| `taxNumber` | String | 企业必填 | 纳税人识别号,最长30位。`titleType=COMPANY` 时必填 |
| `email` | String | 否 | 接收邮箱邮箱格式校验,最长100字 |
| `contactPhone` | String | 否 | 联系电话,最长20位 |
| `remark` | String | 否 | 备注信息,最长500字 |
| `bankName` | String | 否 | 开户行企业发票选填,最长100字 |
| `bankAccount` | String | 否 | 银行账号企业发票选填,最长30位 |
| `companyAddress` | String | 否 | 企业地址企业发票选填,最长200字 |
| `companyPhone` | String | 否 | 企业电话企业发票选填,最长20位 |
**响应**`Result<InvoiceVO>`(见第三节)
**校验规则**
- 订单必须属于当前用户
- 订单状态须为:已支付 / 已确认 / 待出发 / 旅行中 / 已完成 / 已付定金
- 同一订单不可重复申请(已有待开票或已开票的发票时拒绝)
- 开票金额自动取订单实付金额(`paidAmount`),如无则取总价(`totalPrice`
- 发票类型固定「电子普通发票」,内容固定「明细」
**调用示例**
```json
// 个人发票
{
"orderId": "1902000000000001",
"invoiceTitle": "张三",
"titleType": "PERSONAL",
"email": "zhangsan@example.com",
"contactPhone": "13800138000"
}
// 企业发票
{
"orderId": "1902000000000001",
"invoiceTitle": "呼伦贝尔XX旅行社有限公司",
"titleType": "COMPANY",
"taxNumber": "91150000MA00000X00",
"email": "finance@company.com",
"bankName": "中国银行海拉尔支行",
"bankAccount": "1234567890123456789",
"companyAddress": "呼伦贝尔市海拉尔区XX路XX号",
"companyPhone": "0470-1234567"
}
```
---
### 4.2 发票详情按发票ID
```
GET /mp/invoice/{id}
```
**使用场景**:从发票列表点进详情页
**路径参数**
| 参数 | 类型 | 说明 |
|---|---|---|
| `id` | Long | 发票ID |
**响应**`Result<InvoiceVO>`
**校验规则**:只能查看自己的发票
---
### 4.3 通过订单ID查询发票
```
GET /mp/invoice/order/{orderId}
```
**使用场景**:订单详情页判断是否已开票。返回 `data=null` 表示未开票,可显示「申请发票」按钮
**路径参数**
| 参数 | 类型 | 说明 |
|---|---|---|
| `orderId` | Long | 订单ID |
**响应**`Result<InvoiceVO>`(未开票时 `data``null`
---
### 4.4 发票换开
```
POST /mp/invoice/{invoiceId}/reissue
```
**使用场景**:已开票后发现抬头/税号填错,点击「换开发票」修改信息重新开具
**路径参数**
| 参数 | 类型 | 说明 |
|---|---|---|
| `invoiceId` | Long | 原发票ID |
**请求参数** (JSON Body)
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| `invoiceTitle` | String | ✅ | 新发票抬头,2~100字 |
| `titleType` | String | ✅ | 新抬头类型:`PERSONAL` / `COMPANY` |
| `taxNumber` | String | 企业必填 | 新纳税人识别号 |
| `email` | String | 否 | 新接收邮箱 |
**响应**`Result<InvoiceVO>`(返回新创建的发票记录,原发票自动作废)
**校验规则**
- 只能对状态为 `ISSUED`(已开票)的发票换开
- 换开次数上限 **3次**reissueCount >= 3 时拒绝)
- 企业抬头必须填税号
- 换开后:原发票状态变为 `VOIDED`,新发票状态为 `PENDING``originalInvoiceId` 指向原发票
**调用示例**
```json
{
"invoiceTitle": "呼伦贝尔XX旅行社有限公司",
"titleType": "COMPANY",
"taxNumber": "91150000MA00000X00",
"email": "finance@company.com"
}
```
---
## 五、管理端接口(后台管理员)
> 网关路径前缀:`/admin/invoice`
> 需要管理员登录(携带 Admin-Token
### 5.1 上传发票文件
```
POST /admin/invoice/upload
```
**使用场景**管理员在开票前先上传发票PDF/图片文件
**请求参数** (form-data)
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| `file` | File | ✅ | 发票文件,支持 PDF / JPG / JPEG / PNG,最大 10MB |
**响应**
```json
{
"code": 200,
"message": "success",
"data": {
"fileUrl": "https://oss.xxx.com/invoice/20260325/abc123.pdf"
}
}
```
---
### 5.2 发票分页列表
```
GET /admin/invoice/page
```
**使用场景**:发票管理列表页,支持筛选
**请求参数** (Query)
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
| `status` | String | 否 | - | 按状态筛选(字典 `invoice_status``PENDING` / `ISSUED` / `FAILED` / `VOIDED` |
| `orderNo` | String | 否 | - | 按订单编号模糊搜索 |
| `userId` | Long | 否 | - | 按用户ID筛选 |
| `page` | int | 否 | 1 | 页码最小1 |
| `pageSize` | int | 否 | 20 | 每页条数1~100 |
**响应**
```json
{
"code": 200,
"message": "success",
"data": {
"records": [ InvoiceVO, ... ],
"total": 100,
"current": 1,
"size": 20
}
}
```
---
### 5.3 发票详情
```
GET /admin/invoice/{id}
```
**路径参数**`id` — 发票ID
**响应**`Result<InvoiceVO>`
---
### 5.4 开票完成
```
PUT /admin/invoice/{id}/issue
```
**使用场景**:管理员线下开票完成后,上传发票文件并填写发票号码,标记开票完成
**路径参数**`id` — 发票ID
**请求参数** (JSON Body)
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| `fileUrl` | String | ✅ | 发票文件URL通过 5.1 上传接口获取) |
| `invoiceNo` | String | ✅ | 发票号码12位数字 |
**响应**`Result<InvoiceVO>`
**校验规则**:只有 `PENDING`(待开票)或 `FAILED`(开票失败)状态的发票可操作
**调用示例**
```json
{
"fileUrl": "https://oss.xxx.com/invoice/20260325/abc123.pdf",
"invoiceNo": "012345678901"
}
```
---
### 5.5 开票失败
```
PUT /admin/invoice/{id}/fail
```
**使用场景**:发票信息有误,管理员标记失败并填写原因,用户可修正后重新申请
**路径参数**`id` — 发票ID
**请求参数** (JSON Body)
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| `failReason` | String | ✅ | 失败原因 |
**响应**`Result<InvoiceVO>`
**校验规则**:只有 `PENDING`(待开票)状态的发票可操作
**调用示例**
```json
{
"failReason": "发票抬头与纳税人识别号不匹配,请核实后重新申请"
}
```
---
### 5.6 作废发票
```
PUT /admin/invoice/{id}/void
```
**使用场景**:管理员主动作废已开票的发票
**路径参数**`id` — 发票ID
**请求参数**:无
**响应**`Result<InvoiceVO>`
**校验规则**:已作废的发票不可重复作废
---
## 六、前端页面对接建议
### 小程序端页面
**订单详情页**
- 调用 `GET /mp/invoice/order/{orderId}` 判断是否已开票
- `data=null` → 显示「申请发票」按钮(订单状态需满足条件)
- `data.status=PENDING` → 显示「开票中」
- `data.status=ISSUED` → 显示「查看发票」+「换开发票」按钮
- `data.status=FAILED` → 显示失败原因 + 「重新申请」按钮
- `data.status=VOIDED` → 显示「已作废」
**申请开票页**
- 默认「个人」抬头,切换「企业」后展开额外字段(税号必填 + 开户行/银行账号/地址/电话选填)
- 金额自动显示(不可编辑,由后端计算)
**发票详情页**
- 显示全部字段信息
- `status=ISSUED` 时显示发票文件下载链接(`fileUrl`)和发票号码(`invoiceNo`
- `status=ISSUED``reissueCount < 3` 时显示「换开」按钮
### 管理端页面
**发票列表页**
- 顶部筛选栏:状态下拉(全部/待开票/已开票/开票失败/已作废)+ 订单号搜索框
- 列表列:订单号、用户、金额、抬头、状态、申请时间、操作
- 操作按钮:
- `PENDING` → 「开票」「标记失败」
- `FAILED` → 「重新开票」
- `ISSUED` → 「作废」
- `VOIDED` → 无操作
**开票操作**
1. 先调 `POST /admin/invoice/upload` 上传发票文件 → 拿到 `fileUrl`
2. 再调 `PUT /admin/invoice/{id}/issue` 传入 `fileUrl` + `invoiceNo`