- 系统架构文档 (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)
15 KiB
15 KiB
交易域 (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)
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, // 乐观锁版本
}
核心方法:
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)
从银行同步的交易流水。
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)
pub enum TransactionStatus {
Created, // 已创建
Pending, // 待处理
BankSubmitted, // 已提交银行
Success, // 成功
Failed, // 失败
Timeout, // 超时
Reversed, // 已冲正
// 兼容旧状态
Processing, // 处理中 → BankSubmitted
Confirmed, // 已确认 → Success
Mismatch, // 不匹配
}
状态转移规则:
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)
pub enum TransactionType {
Transfer, // 转账
Deposit, // 充值
Withdrawal, // 提现
Fee, // 手续费
Interest, // 利息
Adjustment, // 调整
Other(String), // 其他
}
4.3 交易方向 (TransactionDirection)
pub enum TransactionDirection {
Inbound, // 入账
Outbound, // 出账
}
4.4 匹配状态 (MatchStatus)
pub enum MatchStatus {
Unmatched, // 未匹配
Matched, // 已匹配
Mismatch, // 不匹配
}
五、交易处理流程
5.1 出金流程
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 入金流程
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 冲正流程
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
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
#[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
#[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 表
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 表
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 乐观锁
使用版本号防止并发更新冲突:
// 更新时检查版本
UPDATE system_transaction
SET status = ?, version = version + 1
WHERE id = ? AND version = ?
10.2 幂等性保证
- txn_no 唯一:系统交易号全局唯一
- source_key 去重:外部入账通过 SourceKey 去重
- 状态机约束:只允许合法的状态转移
十一、错误处理
| 错误类型 | 说明 | 处理方式 |
|---|---|---|
| InsufficientBalance | 余额不足 | 拒绝交易 |
| InvalidStateTransition | 非法状态转移 | 返回错误 |
| DuplicateTransaction | 重复交易 | 返回已有交易 |
| BankTimeout | 银行超时 | 创建补偿任务 |
| OptimisticLockFailed | 乐观锁冲突 | 重试 |