Files
soul/addons/Universal_Payment_Module copy/1_核心设计_通用协议/业务逻辑与模型.md

397 lines
13 KiB
Markdown
Raw Normal View History

# 业务逻辑与数据模型 (Business Logic & Data Model) v4.0
> 定义支付系统的核心数据结构和业务流程
## 📊 数据库表结构
### 1. 订单表 (orders)
存储业务订单信息,与支付解耦。
```sql
CREATE TABLE `orders` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
`sn` VARCHAR(32) NOT NULL COMMENT '订单号 (业务唯一)',
`user_id` VARCHAR(64) NOT NULL COMMENT '用户ID',
`title` VARCHAR(128) NOT NULL COMMENT '订单标题',
`price_amount` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '订单原价 (分)',
`pay_amount` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '应付金额 (分)',
`currency` VARCHAR(8) NOT NULL DEFAULT 'CNY' COMMENT '货币类型',
`status` VARCHAR(20) NOT NULL DEFAULT 'created' COMMENT '状态: created/paying/paid/closed/refunded',
`product_id` VARCHAR(64) DEFAULT NULL COMMENT '商品ID',
`product_type` VARCHAR(32) DEFAULT NULL COMMENT '商品类型',
`extra_data` JSON DEFAULT NULL COMMENT '扩展数据',
`paid_at` DATETIME DEFAULT NULL COMMENT '支付时间',
`closed_at` DATETIME DEFAULT NULL COMMENT '关闭时间',
`expired_at` DATETIME DEFAULT NULL COMMENT '过期时间',
`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_sn` (`sn`),
KEY `idx_user_id` (`user_id`),
KEY `idx_status` (`status`),
KEY `idx_created_at` (`created_at`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单表';
```
### 2. 交易流水表 (pay_trades)
记录每一次支付尝试,一个订单可能有多次交易。
```sql
CREATE TABLE `pay_trades` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
`trade_sn` VARCHAR(32) NOT NULL COMMENT '交易流水号 (系统生成)',
`order_sn` VARCHAR(32) NOT NULL COMMENT '关联订单号',
`user_id` VARCHAR(64) NOT NULL COMMENT '用户ID',
`title` VARCHAR(128) NOT NULL COMMENT '交易标题',
`amount` BIGINT UNSIGNED NOT NULL COMMENT '交易金额 (分)',
`cash_amount` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '现金支付金额 (分)',
`coin_amount` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '虚拟币抵扣金额',
`currency` VARCHAR(8) NOT NULL DEFAULT 'CNY' COMMENT '货币类型',
`platform` VARCHAR(32) NOT NULL COMMENT '支付平台: alipay/wechat/paypal/stripe/usdt/coin',
`platform_type` VARCHAR(32) DEFAULT NULL COMMENT '平台子类型: web/wap/jsapi/native/h5/app',
`platform_sn` VARCHAR(64) DEFAULT NULL COMMENT '平台交易号',
`platform_created_params` JSON DEFAULT NULL COMMENT '发送给平台的参数',
`platform_created_result` JSON DEFAULT NULL COMMENT '平台返回的结果',
`status` VARCHAR(20) NOT NULL DEFAULT 'paying' COMMENT '状态: paying/paid/closed/refunded',
`type` VARCHAR(20) NOT NULL DEFAULT 'purchase' COMMENT '类型: purchase(购买)/recharge(充值)',
`pay_time` DATETIME DEFAULT NULL COMMENT '支付时间',
`notify_data` JSON DEFAULT NULL COMMENT '回调原始数据',
`seller_id` VARCHAR(64) DEFAULT NULL COMMENT '卖家ID (多商户场景)',
`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_trade_sn` (`trade_sn`),
KEY `idx_order_sn` (`order_sn`),
KEY `idx_platform_sn` (`platform_sn`),
KEY `idx_user_id` (`user_id`),
KEY `idx_status` (`status`),
KEY `idx_created_at` (`created_at`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='交易流水表';
```
### 3. 资金流水表 (cashflows)
记录账户资金变动(可选,用于虚拟币/钱包场景)。
```sql
CREATE TABLE `cashflows` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
`sn` VARCHAR(32) NOT NULL COMMENT '流水号',
`user_id` VARCHAR(64) NOT NULL COMMENT '用户ID',
`type` VARCHAR(20) NOT NULL COMMENT '类型: inflow(入账)/outflow(出账)',
`action` VARCHAR(32) NOT NULL COMMENT '动作: recharge/purchase/refund/transfer',
`amount` BIGINT NOT NULL COMMENT '金额 (分,正数入账负数出账)',
`currency` VARCHAR(8) NOT NULL DEFAULT 'CNY',
`balance_before` BIGINT NOT NULL DEFAULT 0 COMMENT '变动前余额',
`balance_after` BIGINT NOT NULL DEFAULT 0 COMMENT '变动后余额',
`trade_sn` VARCHAR(32) DEFAULT NULL COMMENT '关联交易流水号',
`order_sn` VARCHAR(32) DEFAULT NULL COMMENT '关联订单号',
`remark` VARCHAR(256) DEFAULT NULL COMMENT '备注',
`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_sn` (`sn`),
KEY `idx_user_id` (`user_id`),
KEY `idx_trade_sn` (`trade_sn`),
KEY `idx_created_at` (`created_at`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='资金流水表';
```
### 4. 退款记录表 (refunds)
```sql
CREATE TABLE `refunds` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
`refund_sn` VARCHAR(32) NOT NULL COMMENT '退款单号',
`trade_sn` VARCHAR(32) NOT NULL COMMENT '原交易流水号',
`order_sn` VARCHAR(32) NOT NULL COMMENT '原订单号',
`amount` BIGINT UNSIGNED NOT NULL COMMENT '退款金额 (分)',
`reason` VARCHAR(256) DEFAULT NULL COMMENT '退款原因',
`status` VARCHAR(20) NOT NULL DEFAULT 'pending' COMMENT '状态: pending/processing/success/failed',
`platform_refund_sn` VARCHAR(64) DEFAULT NULL COMMENT '平台退款单号',
`refunded_at` DATETIME DEFAULT NULL COMMENT '退款完成时间',
`operator_id` VARCHAR(64) DEFAULT NULL COMMENT '操作人ID',
`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_refund_sn` (`refund_sn`),
KEY `idx_trade_sn` (`trade_sn`),
KEY `idx_order_sn` (`order_sn`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='退款记录表';
```
---
## 🔄 状态机定义
### 订单状态 (Order Status)
```
┌─────────────────────────────────────────────────┐
│ created │
│ │ │
│ ┌───────────┼───────────┐ │
│ ▼ │ ▼ │
│ paying ────────┼───────► closed │
│ │ │ │
│ ▼ │ │
│ paid ─────────┼───────► refunded │
│ │ │
└─────────────────────────────────────────────────┘
状态说明:
- created: 订单已创建,等待支付
- paying: 支付中 (已发起支付请求)
- paid: 已支付
- closed: 已关闭 (超时/主动取消)
- refunded: 已退款
```
### 交易状态 (Trade Status)
```
paying → paid
↓ ↓
closed refunded
状态说明:
- paying: 支付中
- paid: 支付成功
- closed: 交易关闭
- refunded: 已退款
```
---
## 🔢 编号规则
### 订单号 (order_sn)
```
格式: YYYYMMDD + 6位随机数
示例: 202401170001
生成规则:
1. 日期前缀保证每日唯一空间
2. 随机数使用分布式ID生成器
3. 支持前缀自定义 (如区分业务线)
```
### 交易流水号 (trade_sn)
```
格式: T + YYYYMMDD + HHmmss + 5位随机数
示例: T20240117100530123456
生成规则:
1. 前缀 T 标识交易类型
2. 精确到秒的时间戳
3. 5位随机数防碰撞
```
---
## 📋 核心业务流程
### 1. 标准支付流程
```sequence
用户 -> 业务系统: 1. 提交订单
业务系统 -> 支付模块: 2. 创建订单 (create_order)
支付模块 -> 业务系统: 3. 返回 order_sn
用户 -> 支付模块: 4. 选择支付方式并支付 (checkout)
支付模块 -> 支付平台: 5. 创建平台交易
支付平台 -> 支付模块: 6. 返回支付参数
支付模块 -> 用户: 7. 返回支付数据 (二维码/跳转链接)
用户 -> 支付平台: 8. 完成支付
支付平台 -> 支付模块: 9. 异步回调 (notify)
支付模块 -> 支付模块: 10. 验签 + 更新状态
支付模块 -> 业务系统: 11. 触发业务回调 (发货/开通)
```
### 2. 支付回调处理流程
```python
def handle_notify(gateway, data):
# 1. 加载对应的支付网关驱动
driver = PaymentFactory.create(gateway)
# 2. 验证签名
if not driver.verify_sign(data):
raise SignatureError("签名验证失败")
# 3. 解析回调数据
parsed = driver.parse_notify(data)
trade_sn = parsed['trade_sn']
# 4. 幂等性检查
trade = Trade.get_by_sn(trade_sn)
if trade.status == 'paid':
return driver.success_response() # 已处理过,直接返回成功
# 5. 金额校验
if parsed['amount'] != trade.cash_amount:
raise AmountMismatchError("金额不匹配")
# 6. 更新交易状态
trade.update({
'status': 'paid',
'platform_sn': parsed['platform_sn'],
'pay_time': parsed['pay_time'],
'notify_data': data
})
# 7. 更新订单状态
order = Order.get_by_sn(trade.order_sn)
order.update({'status': 'paid', 'paid_at': now()})
# 8. 触发业务回调
dispatch_event('order.paid', order)
# 9. 返回成功响应
return driver.success_response()
```
### 3. 退款流程
```python
def apply_refund(trade_sn, amount, reason):
trade = Trade.get_by_sn(trade_sn)
# 1. 状态检查
if trade.status != 'paid':
raise InvalidStatusError("只有已支付的交易可以退款")
# 2. 创建退款记录
refund = Refund.create({
'refund_sn': generate_refund_sn(),
'trade_sn': trade_sn,
'amount': amount,
'reason': reason,
'status': 'pending'
})
# 3. 调用平台退款接口
driver = PaymentFactory.create(trade.platform)
result = driver.refund({
'trade_sn': trade_sn,
'refund_sn': refund.refund_sn,
'amount': amount
})
# 4. 更新状态
if result.success:
refund.update({'status': 'success', 'refunded_at': now()})
trade.update({'status': 'refunded'})
else:
refund.update({'status': 'failed'})
return refund
```
---
## 🏭 工厂模式设计
```python
class PaymentFactory:
"""支付网关工厂"""
_drivers = {
'alipay': AlipayGateway,
'wechat': WechatGateway,
'paypal': PayPalGateway,
'stripe': StripeGateway,
'usdt': USDTGateway,
'coin': CoinGateway,
}
@classmethod
def create(cls, gateway: str) -> AbstractGateway:
gateway_name = gateway.split('_')[0] # wechat_jsapi -> wechat
if gateway_name not in cls._drivers:
raise ValueError(f"不支持的支付网关: {gateway}")
driver_class = cls._drivers[gateway_name]
return driver_class(config=get_payment_config(gateway_name))
```
```python
class AbstractGateway(ABC):
"""支付网关抽象基类"""
@abstractmethod
def create_trade(self, data: dict) -> dict:
"""创建交易"""
pass
@abstractmethod
def verify_sign(self, data: dict) -> bool:
"""验证签名"""
pass
@abstractmethod
def parse_notify(self, data: dict) -> dict:
"""解析回调数据"""
pass
@abstractmethod
def refund(self, data: dict) -> RefundResult:
"""发起退款"""
pass
@abstractmethod
def query_trade(self, trade_sn: str) -> dict:
"""查询交易"""
pass
@abstractmethod
def close_trade(self, trade_sn: str) -> bool:
"""关闭交易"""
pass
def success_response(self) -> str:
"""回调成功响应"""
return "success"
```
---
## 💰 金额处理规范
### 1. 存储规则
- 数据库统一使用**分**为单位 (BIGINT)
- 避免浮点数精度问题
### 2. 接口规则
- API 输入输出统一使用**元**为单位
- 内部转换: `分 = 元 × 100`
### 3. 转换示例
```python
# 元转分 (API输入 -> 数据库)
def yuan_to_fen(yuan: float) -> int:
return int(round(yuan * 100))
# 分转元 (数据库 -> API输出)
def fen_to_yuan(fen: int) -> float:
return round(fen / 100, 2)
```
---
## 🔐 幂等性设计
### 1. 订单创建幂等
- 使用 `(user_id, product_id, created_date)` 组合判断
- 或使用客户端传入的幂等键 `idempotency_key`
### 2. 支付回调幂等
- 检查交易状态,已支付则直接返回成功
- 使用数据库事务 + 行锁保证并发安全
### 3. 退款幂等
- 同一笔交易只能退款一次 (或限制总退款金额)