- 系统架构文档 (architecture/README.md) - 6个领域文档: - 账户域 (01-account.md) - 账务域 (02-ledger.md) - 交易域 (03-transaction.md) - 对账域 (04-reconciliation.md) - 补偿域 (05-compensation.md) - 积分域 (06-points.md) - API 参考文档 (api/README.md) - 前后端对接清单 (integration/frontend-backend.md)
482 lines
15 KiB
Markdown
482 lines
15 KiB
Markdown
# 交易域 (Transaction Domain)
|
||
|
||
## 一、领域概述
|
||
|
||
交易域负责处理系统内的所有资金流转,包括转账、充值、提现等操作。该域实现了完整的交易状态机和三键幂等体系,确保交易的安全性和一致性。
|
||
|
||
## 二、核心概念
|
||
|
||
### 2.1 三键幂等体系
|
||
|
||
| 键名 | 字段 | 说明 | 唯一性 |
|
||
|------|------|------|--------|
|
||
| JZTxId | txn_no | 狱政交易号 | 系统全局唯一 |
|
||
| BankTxId | bank_ref_no | 银行交易号 | 银行返回,用于对账 |
|
||
| SourceKey | source_key | 来源幂等键 | 外部入账去重 |
|
||
|
||
**SourceKey 格式**:
|
||
```
|
||
{银行流水号}_{金额}_{记账日}_{对方户名归一化}
|
||
```
|
||
|
||
### 2.2 交易状态机
|
||
|
||
```
|
||
┌──────────────────────────────────────┐
|
||
│ │
|
||
▼ │
|
||
┌─────────┐ ┌─────────┐ ┌──────────────┐ ┌───────┴───┐
|
||
│ Created │───►│ Pending │───►│ BankSubmitted│───►│ Success │
|
||
└─────────┘ └─────────┘ └──────────────┘ └───────────┘
|
||
│ │ │
|
||
│ │ │
|
||
▼ ▼ ▼
|
||
┌─────────┐ ┌─────────┐ ┌──────────┐
|
||
│ Failed │ │ Timeout │──────►│ Reversed │
|
||
└─────────┘ └─────────┘ └──────────┘
|
||
```
|
||
|
||
**状态说明**:
|
||
|
||
| 状态 | 说明 | 后续操作 |
|
||
|------|------|----------|
|
||
| Created | 已创建(初始状态) | 继续处理或取消 |
|
||
| Pending | 待处理(已建立在途) | 提交银行 |
|
||
| BankSubmitted | 已提交银行 | 等待回执 |
|
||
| Success | 成功(银行确认) | 结转在途 |
|
||
| Failed | 失败(银行拒绝) | 回退在途 |
|
||
| Timeout | 超时(无回执) | 等待对账或重试 |
|
||
| Reversed | 已冲正 | 终态 |
|
||
|
||
## 三、核心实体
|
||
|
||
### 3.1 系统交易 (SystemTransaction)
|
||
|
||
```rust
|
||
pub struct SystemTransaction {
|
||
pub id: i64,
|
||
pub txn_no: String, // 狱政交易号 (JZTxId)
|
||
pub txn_type: TransactionType, // 交易类型
|
||
pub from_account_id: Option<i64>, // 转出账户ID
|
||
pub to_account_id: Option<i64>, // 转入账户ID
|
||
pub amount: Decimal, // 金额
|
||
pub status: TransactionStatus, // 状态
|
||
pub bank_ref_no: Option<String>, // 银行交易号 (BankTxId)
|
||
pub source_key: Option<String>, // 来源幂等键 (SourceKey)
|
||
pub remark: Option<String>, // 备注
|
||
pub created_at: DateTime<Utc>, // 创建时间
|
||
pub confirmed_at: Option<DateTime<Utc>>, // 确认时间
|
||
pub submitted_at: Option<DateTime<Utc>>, // 提交银行时间
|
||
pub version: i32, // 乐观锁版本
|
||
}
|
||
```
|
||
|
||
**核心方法**:
|
||
|
||
```rust
|
||
impl SystemTransaction {
|
||
// 检查是否可以提交到银行
|
||
pub fn can_submit(&self) -> bool {
|
||
matches!(self.status, TransactionStatus::Pending | TransactionStatus::Created)
|
||
}
|
||
|
||
// 检查是否需要对账
|
||
pub fn needs_reconciliation(&self) -> bool {
|
||
matches!(self.status,
|
||
TransactionStatus::BankSubmitted |
|
||
TransactionStatus::Timeout |
|
||
TransactionStatus::Processing
|
||
)
|
||
}
|
||
|
||
// 检查是否为终态
|
||
pub fn is_terminal(&self) -> bool {
|
||
self.status.is_terminal()
|
||
}
|
||
|
||
// 检查是否超时
|
||
pub fn is_timeout(&self, timeout_seconds: i64) -> bool {
|
||
if self.status != TransactionStatus::BankSubmitted {
|
||
return false;
|
||
}
|
||
if let Some(submitted_at) = self.submitted_at {
|
||
let elapsed = Utc::now().signed_duration_since(submitted_at);
|
||
return elapsed.num_seconds() > timeout_seconds;
|
||
}
|
||
false
|
||
}
|
||
|
||
// 尝试状态转移
|
||
pub fn try_transition(&mut self, target: TransactionStatus) -> Result<(), String> {
|
||
if self.status.can_transition_to(target) {
|
||
self.status = target;
|
||
self.version += 1;
|
||
Ok(())
|
||
} else {
|
||
Err(format!("无效的状态转移: {:?} -> {:?}", self.status, target))
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### 3.2 银行交易 (BankTransaction)
|
||
|
||
从银行同步的交易流水。
|
||
|
||
```rust
|
||
pub struct BankTransaction {
|
||
pub id: i64,
|
||
pub bank_ref_no: String, // 银行参考号
|
||
pub physical_account_id: i64, // 实体账户ID
|
||
pub txn_type: String, // 交易类型
|
||
pub direction: TransactionDirection, // 交易方向
|
||
pub amount: Decimal, // 金额
|
||
pub counterparty_name: Option<String>,// 对手方名称
|
||
pub counterparty_account: Option<String>,// 对手方账号
|
||
pub txn_time: DateTime<Utc>, // 交易时间
|
||
pub sync_time: DateTime<Utc>, // 同步时间
|
||
pub match_status: MatchStatus, // 匹配状态
|
||
pub matched_txn_no: Option<String>, // 匹配的系统交易号
|
||
pub remark: Option<String>, // 摘要
|
||
}
|
||
```
|
||
|
||
## 四、枚举类型
|
||
|
||
### 4.1 交易状态 (TransactionStatus)
|
||
|
||
```rust
|
||
pub enum TransactionStatus {
|
||
Created, // 已创建
|
||
Pending, // 待处理
|
||
BankSubmitted, // 已提交银行
|
||
Success, // 成功
|
||
Failed, // 失败
|
||
Timeout, // 超时
|
||
Reversed, // 已冲正
|
||
// 兼容旧状态
|
||
Processing, // 处理中 → BankSubmitted
|
||
Confirmed, // 已确认 → Success
|
||
Mismatch, // 不匹配
|
||
}
|
||
```
|
||
|
||
**状态转移规则**:
|
||
|
||
```rust
|
||
impl TransactionStatus {
|
||
pub fn can_transition_to(&self, target: Self) -> bool {
|
||
match (self, target) {
|
||
// Created -> Pending
|
||
(Self::Created, Self::Pending) => true,
|
||
// Pending -> BankSubmitted | Failed
|
||
(Self::Pending, Self::BankSubmitted) => true,
|
||
(Self::Pending, Self::Failed) => true,
|
||
// BankSubmitted -> Success | Failed | Timeout
|
||
(Self::BankSubmitted, Self::Success) => true,
|
||
(Self::BankSubmitted, Self::Failed) => true,
|
||
(Self::BankSubmitted, Self::Timeout) => true,
|
||
// Timeout -> Success | Failed (对账确认)
|
||
(Self::Timeout, Self::Success) => true,
|
||
(Self::Timeout, Self::Failed) => true,
|
||
// Success -> Reversed (冲正)
|
||
(Self::Success, Self::Reversed) => true,
|
||
_ => false,
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### 4.2 交易类型 (TransactionType)
|
||
|
||
```rust
|
||
pub enum TransactionType {
|
||
Transfer, // 转账
|
||
Deposit, // 充值
|
||
Withdrawal, // 提现
|
||
Fee, // 手续费
|
||
Interest, // 利息
|
||
Adjustment, // 调整
|
||
Other(String), // 其他
|
||
}
|
||
```
|
||
|
||
### 4.3 交易方向 (TransactionDirection)
|
||
|
||
```rust
|
||
pub enum TransactionDirection {
|
||
Inbound, // 入账
|
||
Outbound, // 出账
|
||
}
|
||
```
|
||
|
||
### 4.4 匹配状态 (MatchStatus)
|
||
|
||
```rust
|
||
pub enum MatchStatus {
|
||
Unmatched, // 未匹配
|
||
Matched, // 已匹配
|
||
Mismatch, // 不匹配
|
||
}
|
||
```
|
||
|
||
## 五、交易处理流程
|
||
|
||
### 5.1 出金流程
|
||
|
||
```mermaid
|
||
sequenceDiagram
|
||
participant Client
|
||
participant TxnService
|
||
participant LedgerService
|
||
participant BankClient
|
||
participant CompService
|
||
|
||
Client->>TxnService: 1. 发起转账
|
||
TxnService->>TxnService: 2. 创建交易 (Created)
|
||
TxnService->>LedgerService: 3. 优先级扣款
|
||
LedgerService-->>TxnService: 扣款结果
|
||
TxnService->>LedgerService: 4. 建立在途
|
||
TxnService->>TxnService: 5. 更新状态 (Pending)
|
||
TxnService->>BankClient: 6. 提交银行
|
||
TxnService->>TxnService: 7. 更新状态 (BankSubmitted)
|
||
|
||
alt 银行成功
|
||
BankClient-->>TxnService: 成功回执
|
||
TxnService->>LedgerService: 8a. 结转在途
|
||
TxnService->>TxnService: 9a. 更新状态 (Success)
|
||
else 银行失败
|
||
BankClient-->>TxnService: 失败回执
|
||
TxnService->>LedgerService: 8b. 回退在途
|
||
TxnService->>TxnService: 9b. 更新状态 (Failed)
|
||
else 超时
|
||
Note over TxnService: 等待超时
|
||
TxnService->>CompService: 8c. 创建补偿任务
|
||
TxnService->>TxnService: 9c. 更新状态 (Timeout)
|
||
end
|
||
|
||
TxnService-->>Client: 返回结果
|
||
```
|
||
|
||
### 5.2 入金流程
|
||
|
||
```mermaid
|
||
sequenceDiagram
|
||
participant Bank
|
||
participant TxnService
|
||
participant LedgerService
|
||
|
||
Bank->>TxnService: 1. 银行入账通知
|
||
TxnService->>TxnService: 2. 检查 SourceKey 幂等
|
||
|
||
alt 重复入账
|
||
TxnService-->>Bank: 返回已处理
|
||
else 新入账
|
||
TxnService->>TxnService: 3. 创建交易 (Success)
|
||
TxnService->>LedgerService: 4. 增加个人余额
|
||
TxnService->>LedgerService: 5. 同步银行余额
|
||
TxnService-->>Bank: 处理成功
|
||
end
|
||
```
|
||
|
||
### 5.3 冲正流程
|
||
|
||
```mermaid
|
||
sequenceDiagram
|
||
participant Operator
|
||
participant TxnService
|
||
participant LedgerService
|
||
|
||
Operator->>TxnService: 1. 发起冲正
|
||
TxnService->>TxnService: 2. 检查是否可冲正
|
||
|
||
alt 可以冲正
|
||
TxnService->>LedgerService: 3. 反向记账
|
||
TxnService->>TxnService: 4. 更新状态 (Reversed)
|
||
TxnService-->>Operator: 冲正成功
|
||
else 不可冲正
|
||
TxnService-->>Operator: 冲正失败
|
||
end
|
||
```
|
||
|
||
## 六、领域服务
|
||
|
||
### 6.1 TransactionService
|
||
|
||
```rust
|
||
impl TransactionService {
|
||
// ========== 交易创建 ==========
|
||
|
||
// 创建转账交易
|
||
pub async fn transfer(&self, req: TransferRequest) -> Result<SystemTransaction>;
|
||
|
||
// 创建充值交易
|
||
pub async fn deposit(&self, req: DepositRequest) -> Result<SystemTransaction>;
|
||
|
||
// 创建提现交易
|
||
pub async fn withdraw(&self, req: WithdrawRequest) -> Result<SystemTransaction>;
|
||
|
||
// ========== 交易查询 ==========
|
||
|
||
// 获取交易详情
|
||
pub async fn get_transaction(&self, id: i64) -> Result<SystemTransaction>;
|
||
|
||
// 根据交易号查询
|
||
pub async fn get_by_txn_no(&self, txn_no: &str) -> Result<SystemTransaction>;
|
||
|
||
// 查询交易列表
|
||
pub async fn list_transactions(&self, query: TransactionQuery) -> Result<Vec<SystemTransaction>>;
|
||
|
||
// ========== 状态管理 ==========
|
||
|
||
// 提交到银行
|
||
pub async fn submit_to_bank(&self, id: i64) -> Result<String>;
|
||
|
||
// 确认交易
|
||
pub async fn confirm_transaction(&self, id: i64, bank_ref_no: &str) -> Result<()>;
|
||
|
||
// 标记失败
|
||
pub async fn mark_failed(&self, id: i64, reason: &str) -> Result<()>;
|
||
|
||
// 标记超时
|
||
pub async fn mark_timeout(&self, id: i64) -> Result<()>;
|
||
|
||
// 冲正交易
|
||
pub async fn reverse_transaction(&self, id: i64) -> Result<()>;
|
||
|
||
// ========== 幂等检查 ==========
|
||
|
||
// 检查 SourceKey 是否存在
|
||
pub async fn check_source_key(&self, source_key: &str) -> Result<Option<SystemTransaction>>;
|
||
}
|
||
```
|
||
|
||
## 七、仓储接口
|
||
|
||
### 7.1 SystemTransactionRepository
|
||
|
||
```rust
|
||
#[async_trait]
|
||
pub trait SystemTransactionRepository: Send + Sync {
|
||
async fn create(&self, req: &CreateSystemTransactionRequest) -> Result<SystemTransaction>;
|
||
async fn find_by_id(&self, id: i64) -> Result<Option<SystemTransaction>>;
|
||
async fn find_by_txn_no(&self, txn_no: &str) -> Result<Option<SystemTransaction>>;
|
||
async fn find_by_bank_ref_no(&self, bank_ref_no: &str) -> Result<Option<SystemTransaction>>;
|
||
async fn find_by_source_key(&self, source_key: &str) -> Result<Option<SystemTransaction>>;
|
||
async fn find_by_status(&self, status: TransactionStatus) -> Result<Vec<SystemTransaction>>;
|
||
async fn find_pending(&self) -> Result<Vec<SystemTransaction>>;
|
||
async fn find_needs_reconciliation(&self) -> Result<Vec<SystemTransaction>>;
|
||
async fn query(&self, query: &TransactionQuery) -> Result<Vec<SystemTransaction>>;
|
||
async fn update_status(&self, id: i64, status: TransactionStatus) -> Result<()>;
|
||
async fn set_bank_ref_no(&self, id: i64, bank_ref_no: &str) -> Result<()>;
|
||
async fn set_submitted_at(&self, id: i64) -> Result<()>;
|
||
async fn confirm(&self, id: i64, bank_ref_no: &str) -> Result<()>;
|
||
}
|
||
```
|
||
|
||
### 7.2 BankTransactionRepository
|
||
|
||
```rust
|
||
#[async_trait]
|
||
pub trait BankTransactionRepository: Send + Sync {
|
||
async fn create(&self, txn: &BankTransaction) -> Result<i64>;
|
||
async fn find_by_bank_ref_no(&self, bank_ref_no: &str) -> Result<Option<BankTransaction>>;
|
||
async fn find_unmatched(&self) -> Result<Vec<BankTransaction>>;
|
||
async fn update_match_status(&self, id: i64, status: MatchStatus, matched_txn_no: Option<&str>) -> Result<()>;
|
||
}
|
||
```
|
||
|
||
## 八、API 接口
|
||
|
||
| 方法 | 路径 | 说明 |
|
||
|------|------|------|
|
||
| POST | /api/v1/transactions/transfer | 发起转账 |
|
||
| POST | /api/v1/transactions/deposit | 发起充值 |
|
||
| POST | /api/v1/transactions/withdraw | 发起提现 |
|
||
| GET | /api/v1/transactions/:id | 获取交易详情 |
|
||
| GET | /api/v1/transactions | 获取交易列表 |
|
||
|
||
## 九、数据库表结构
|
||
|
||
### 9.1 system_transaction 表
|
||
|
||
```sql
|
||
CREATE TABLE system_transaction (
|
||
id BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||
txn_no VARCHAR(64) NOT NULL UNIQUE,
|
||
txn_type VARCHAR(32) NOT NULL,
|
||
from_account_id BIGINT,
|
||
to_account_id BIGINT,
|
||
amount DECIMAL(20, 2) NOT NULL,
|
||
status VARCHAR(32) DEFAULT 'created',
|
||
bank_ref_no VARCHAR(64),
|
||
source_key VARCHAR(256),
|
||
remark TEXT,
|
||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||
confirmed_at TIMESTAMP NULL,
|
||
submitted_at TIMESTAMP NULL,
|
||
version INT DEFAULT 0,
|
||
|
||
INDEX idx_status (status),
|
||
INDEX idx_from_account (from_account_id),
|
||
INDEX idx_to_account (to_account_id),
|
||
INDEX idx_bank_ref_no (bank_ref_no),
|
||
INDEX idx_source_key (source_key),
|
||
INDEX idx_created_at (created_at)
|
||
);
|
||
```
|
||
|
||
### 9.2 bank_transaction 表
|
||
|
||
```sql
|
||
CREATE TABLE bank_transaction (
|
||
id BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||
bank_ref_no VARCHAR(64) NOT NULL UNIQUE,
|
||
physical_account_id BIGINT NOT NULL,
|
||
txn_type VARCHAR(32) NOT NULL,
|
||
direction VARCHAR(16) NOT NULL,
|
||
amount DECIMAL(20, 2) NOT NULL,
|
||
counterparty_name VARCHAR(128),
|
||
counterparty_account VARCHAR(64),
|
||
txn_time TIMESTAMP NOT NULL,
|
||
sync_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||
match_status VARCHAR(32) DEFAULT 'unmatched',
|
||
matched_txn_no VARCHAR(64),
|
||
remark TEXT,
|
||
|
||
INDEX idx_physical_account (physical_account_id),
|
||
INDEX idx_match_status (match_status),
|
||
INDEX idx_txn_time (txn_time)
|
||
);
|
||
```
|
||
|
||
## 十、并发控制
|
||
|
||
### 10.1 乐观锁
|
||
|
||
使用版本号防止并发更新冲突:
|
||
|
||
```rust
|
||
// 更新时检查版本
|
||
UPDATE system_transaction
|
||
SET status = ?, version = version + 1
|
||
WHERE id = ? AND version = ?
|
||
```
|
||
|
||
### 10.2 幂等性保证
|
||
|
||
1. **txn_no 唯一**:系统交易号全局唯一
|
||
2. **source_key 去重**:外部入账通过 SourceKey 去重
|
||
3. **状态机约束**:只允许合法的状态转移
|
||
|
||
## 十一、错误处理
|
||
|
||
| 错误类型 | 说明 | 处理方式 |
|
||
|----------|------|----------|
|
||
| InsufficientBalance | 余额不足 | 拒绝交易 |
|
||
| InvalidStateTransition | 非法状态转移 | 返回错误 |
|
||
| DuplicateTransaction | 重复交易 | 返回已有交易 |
|
||
| BankTimeout | 银行超时 | 创建补偿任务 |
|
||
| OptimisticLockFailed | 乐观锁冲突 | 重试 |
|
||
|