Files
Ticket/系统设计.md
T
2026-04-30 10:47:26 +08:00

316 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 农产品收购发票开票平台系统设计
## 1. 系统定位
本平台作为“业务系统”和“票通数电发票平台”之间的开票中台:
- 前端 Web:登录、查看待开票农产品收购数据、选择数据、发起开票、处理票通认证、查看开票结果。
- Ktor 后端:统一封装我方业务接口、票通接口、票通加密签名、开票状态机、结果回写、重试与审计。
- 我方业务系统:提供公司、待开票列表、开票前写库、开票结果回写等接口。
- 票通系统:提供数电账号认证、短信登录、实名认证二维码、蓝字发票开具、发票查询、结果推送等接口。
当前文档资料中,本业务主要对应票通“直接开票”场景:调用 `invoiceBlue.pt` 开具蓝字数电发票;开票结果通过票通推送或主动查询同步。票通要求业务报文使用 UTF-8,业务 JSON 先 3DES 加密,外层报文再按字段排序后用 RSA 签名。
## 2. 总体架构
```mermaid
flowchart LR
Web["Web 前端"]
Ktor["Ktor 后端"]
Biz["我方业务系统"]
DB["平台数据库"]
PT["票通 OpenAPI"]
Web -->|"登录/查询/开票/认证"| Ktor
Ktor -->|"公司、待开票、预写库、状态回写"| Biz
Ktor -->|"开票任务、状态、票通账号、审计日志"| DB
Ktor -->|"加密签名请求"| PT
PT -->|"推送结果 callback"| Ktor
Ktor -->|"状态更新"| Web
```
建议 Ktor 后端承担“防重复、状态机、签名加密、票通异常兼容”的职责,前端不要直接调用票通,也不要持有票通密钥。
## 3. 核心业务流程
### 3.1 平台登录与公司上下文
1. 用户登录 Web。
2. Ktor 调用我方登录或用户信息接口,获取 `userId``companyId`、公司名称、纳税人识别号 `taxpayerNum`
3. Ktor 建立平台会话/JWT,前端后续请求都带当前公司上下文。
### 3.2 查询待开票列表
1. 前端调用 `GET /api/invoice-candidates`
2. Ktor 按当前 `companyId` 调用我方“根据公司 id 查询待开票列表”接口。
3. Ktor 对列表做轻量规范化:金额、税率、商品名称、收购方/销售方信息、是否可开票、已锁定状态。
4. 前端展示并支持勾选。
### 3.3 发起开票
1. 前端选中待开票数据,调用 `POST /api/invoice-jobs`
2. Ktor 校验当前公司票通配置和数电账号状态。
3. 如果 `getTaxBureauAccountAuthStatus.pt` 返回需要认证:
- `operationProposed=1`:提示扫码认证;
- `operationProposed=2`:展示扫码和短信两种方式;
- `operationProposed=3`:提示短信登录;
- `operationProposed=0`:继续开票。
4. Ktor 调用我方“开票前写库”接口,锁定这些业务数据,并取得我方开票批次号或业务流水号。
5. Ktor 生成稳定且唯一的 `invoiceReqSerialNo`。同一张发票重试必须复用同一个流水号,避免重复开票。
6. Ktor 按票通字段组装 `invoiceBlue.pt` 业务报文,调用票通。
7. Ktor 记录票通同步返回结果:
- 成功受理或开票中:本地状态置为 `PROCESSING`
- 立即成功:置为 `SUCCESS`,回写我方系统;
- 失败:置为 `FAILED``NEED_AUTH`,回写我方系统。
### 3.4 认证流程
票通文档中认证不是简单登录态,包含登录认证和风险认证。推荐做成统一认证面板。
- 查询认证状态:`getTaxBureauAccountAuthStatus.pt`
- 获取短信验证码:`sendLoginSmsCode.pt`
- 短信登录:`smsLogin.pt`
- 获取实名认证二维码:`getAuthenticationQrcode.pt`
- 查询二维码扫码状态:`queryAuthQrcodeScanStatus.pt`
- 退出电子税局登录:`logout` 类接口,对应文档 2.46
前端行为:
- `operationProposed=1`:调用后端获取二维码,轮询扫码状态。
- `operationProposed=2`:同时展示“扫码认证”和“短信认证”。
- `operationProposed=3`:展示手机号/账号、验证码输入、发送验证码按钮。
- 认证完成后,用户点击“继续开票”,后端用原 `invoiceReqSerialNo` 继续或重试。
### 3.5 开票结果同步
建议同时支持两条链路:
1. 票通推送:实现 `POST /api/callbacks/piaotong/invoice`,接收票通推送发票主要信息或全票面信息。
2. 主动查询:后台任务定时调用 `queryInvoice.pt` 查询 `PROCESSING``NEED_AUTH_RETRYING` 的发票。
票通状态码映射:
| 票通 code | 含义 | 本地状态 |
| --- | --- | --- |
| `0000` | 开票成功 | `SUCCESS` |
| `6666` | 未开票 | `PENDING``PROCESSING` |
| `7777` | 开票中 | `PROCESSING` |
| `9999` | 开票失败 | `FAILED` |
| `3999` | 需要扫码或短信认证 | `NEED_AUTH` |
| `4999` | 红字确认单申请中 | 后续冲红扩展 |
| `5999` | 红字确认单审核中 | 后续冲红扩展 |
结果落库后,Ktor 调用我方“写状态”接口,把成功/失败、票号、数电发票号码、失败原因、票通流水号等回写。
## 4. 后端模块设计
建议 Ktor 后端按以下模块拆分:
```text
server/
src/main/kotlin/com/bbit/agriinvoice/
Application.kt
config/
AppConfig.kt
PiaotongConfig.kt
BizSystemConfig.kt
routes/
AuthRoutes.kt
InvoiceCandidateRoutes.kt
InvoiceJobRoutes.kt
PiaotongAuthRoutes.kt
PiaotongCallbackRoutes.kt
domain/
Company.kt
InvoiceCandidate.kt
InvoiceJob.kt
InvoiceStatus.kt
TaxAccount.kt
service/
LoginService.kt
InvoiceCandidateService.kt
InvoiceIssueService.kt
InvoiceStatusService.kt
PiaotongAuthService.kt
PiaotongCallbackService.kt
integration/
biz/
BizSystemClient.kt
BizDtos.kt
piaotong/
PiaotongClient.kt
PiaotongCrypto.kt
PiaotongDtos.kt
PiaotongEndpoints.kt
repository/
InvoiceJobRepository.kt
TaxAccountRepository.kt
AuditLogRepository.kt
```
关键服务职责:
- `PiaotongCrypto`:3DES 加解密、RSA 签名验签、外层报文构造、响应解密。
- `PiaotongClient`:封装票通 endpoint,所有票通接口只接受/返回业务 DTO。
- `InvoiceIssueService`:开票主流程、幂等控制、状态推进。
- `PiaotongAuthService`:数电账号状态查询、短信登录、二维码认证。
- `InvoiceStatusService`:票通状态映射、本地落库、我方业务系统状态回写。
## 5. Web 页面设计
### 5.1 页面
- 登录页:登录平台并进入公司上下文。
- 待开票列表页:筛选、勾选、查看金额合计、发起开票。
- 开票确认弹窗:展示本次开票单据、购买方/销售方、商品行、价税合计。
- 票通认证弹窗:根据后端返回展示扫码或短信认证。
- 开票结果页:展示处理中、成功、失败、需要认证,支持刷新和重试。
- 系统配置页:维护票通数电账号、开票员、税号、默认商品编码等。
### 5.2 前端状态
列表项建议展示:
- `未开票`
- `已锁定`
- `开票中`
- `待认证`
- `开票成功`
- `开票失败`
前端不要自己判断票通接口细节,只消费后端聚合后的 `status``actionRequired``authOptions``message`
## 6. Ktor 对外接口草案
```http
POST /api/auth/login
GET /api/me
GET /api/invoice-candidates
POST /api/invoice-jobs
GET /api/invoice-jobs/{jobId}
POST /api/invoice-jobs/{jobId}/retry
GET /api/piaotong/accounts/{account}/auth-status
POST /api/piaotong/accounts/{account}/sms-code
POST /api/piaotong/accounts/{account}/sms-login
POST /api/piaotong/accounts/{account}/auth-qrcode
GET /api/piaotong/accounts/{account}/auth-qrcode/{authId}
POST /api/piaotong/accounts/{account}/logout
POST /api/callbacks/piaotong/invoice
```
`POST /api/invoice-jobs` 请求示例:
```json
{
"candidateIds": ["A001", "A002"],
"taxAccount": "18900000000",
"invoiceKind": "82",
"buyer": {
"name": "购买方名称",
"taxpayerNum": "XX0000000000000000"
}
}
```
响应示例:
```json
{
"jobId": "job_20260424153000001",
"status": "NEED_AUTH",
"message": "当前数电账号需要短信或扫码认证",
"authOptions": ["QRCODE", "SMS"]
}
```
## 7. 数据库表建议
### 7.1 invoice_job
| 字段 | 说明 |
| --- | --- |
| id | 平台开票任务 ID |
| company_id | 公司 ID |
| taxpayer_num | 销方纳税人识别号 |
| invoice_req_serial_no | 票通发票请求流水号,唯一 |
| biz_batch_no | 我方系统开票前写库返回批次号 |
| tax_account | 数电账号 |
| invoice_kind | 发票种类,数电普票通常为 `82` |
| status | 本地状态 |
| pt_code | 票通状态码 |
| pt_message | 票通返回信息 |
| invoice_no | 发票号码 |
| all_ele_inv_no | 数电发票号码 |
| invoice_date | 开票日期 |
| total_amount | 合计金额 |
| total_tax | 合计税额 |
| raw_request | 脱敏后的票通业务请求 |
| raw_response | 脱敏后的票通响应 |
| created_at / updated_at | 时间 |
### 7.2 invoice_job_item
保存本次开票关联的我方待开票数据 ID、商品行、金额、税率、数量、税收分类编码。
### 7.3 tax_account
保存公司下可用数电账号、姓名、身份类型、手机号、最近认证状态。密码或密钥类数据必须加密保存,不能明文落库。
### 7.4 audit_log
保存登录、开票、认证、回写、回调、重试等操作审计。
## 8. 票通报文封装
参考现有 Java Demo,后端需要实现:
1. 业务 JSON 使用 UTF-8 序列化。
2. 使用票通提供的 3DES key 加密业务 JSON,得到外层 `content`
3. 外层字段包含:
- `platformCode`
- `signType=RSA`
- `format=JSON`
- `version=1.0`
- `content`
- `timestamp`
- `serialNo`
4. 对外层字段按 key 排序,拼成 `key=value&key=value`,忽略 null,使用 `SHA1WithRSA` 和我方私钥签名。
5. 响应先用票通公钥验签,再用 3DES 解密 `content`
配置项必须放在环境变量或配置中心:
```properties
PIAOTONG_BASE_URL=https://fpkj.vpiaotong.com/tp/openapi
PIAOTONG_PLATFORM_CODE=...
PIAOTONG_PLATFORM_ALIAS=...
PIAOTONG_3DES_KEY=...
PIAOTONG_PRIVATE_KEY=...
PIAOTONG_PUBLIC_KEY=...
```
## 9. 幂等与异常策略
- `invoiceReqSerialNo` 是开票幂等核心。同一张发票重试必须复用,不能重新生成。
- 调用我方“开票前写库”成功后,即使票通调用超时,也不能直接判失败,应进入 `PROCESSING` 并主动查询。
- 票通返回 `3999` 时,不应生成新发票任务,应把原任务置为 `NEED_AUTH`,认证后继续用原流水号重试或查询。
- 我方状态回写失败时,本地记录为 `BIZ_SYNC_FAILED`,后台补偿重试。
- 回调接口必须验签、去重、按状态版本更新,避免乱序回调覆盖成功状态。
## 10. 推荐一期范围
一期先做最小闭环:
1. 登录并获取公司 ID。
2. 查询待开票列表。
3. 选择数据并发起蓝字数电普票开具。
4. 票通短信登录和扫码认证。
5. 开票状态查询和结果回写。
6. 票通回调接收。
7. 开票日志和失败重试。
冲红、二维码开票、发票文件下载、微信/支付宝卡包、企业注册入驻可放到二期。