# 业务逻辑与数据模型 (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. 退款幂等
- 同一笔交易只能退款一次 (或限制总退款金额)
