docs: 发票模块完整API文档(管理端+小程序端)
包含全部10个接口的详细定义:请求参数、响应字段、校验规则、 调用示例、状态流转、关联字典、前端页面对接建议。 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
这个提交包含在:
父节点
2bf8b99038
当前提交
18ff6a008b
@ -0,0 +1,396 @@
|
||||
# 发票模块完整 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`
|
||||
正在加载...
x
在新工单中引用
屏蔽一个用户