初始化项目
This commit is contained in:
@@ -0,0 +1,315 @@
|
||||
# 农产品收购发票开票平台系统设计
|
||||
|
||||
## 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. 开票日志和失败重试。
|
||||
|
||||
冲红、二维码开票、发票文件下载、微信/支付宝卡包、企业注册入驻可放到二期。
|
||||
|
||||
Reference in New Issue
Block a user