Files
soul/addons/Universal_Payment_Module copy/1_核心设计_通用协议/业务逻辑与模型.md
卡若 b60edb3d47 feat: 完整重构小程序匹配功能 + 修复UI对齐 + 文章数据API
主要更新:
1. 按H5网页端完全重构匹配功能(match页面)
   - 4种匹配类型: 创业合伙/资源对接/导师顾问/团队招募
   - 资源对接等类型弹出手机号/微信号输入框
   - 去掉重新匹配按钮,改为返回按钮

2. 修复所有卡片对齐和宽度问题
   - 目录页附录卡片居中
   - 首页阅读进度卡片满宽度
   - 我的页面菜单卡片对齐
   - 推广中心分享卡片统一宽度

3. 修复目录页图标和文字对齐
   - section-icon固定40rpx宽高
   - section-title与图标垂直居中

4. 更新真实完整文章标题(62篇)
   - 从book目录读取真实markdown文件名
   - 替换之前的简化标题

5. 新增文章数据API
   - /api/db/chapters - 获取完整书籍结构
   - 支持按ID获取单篇文章内容
2026-01-21 15:49:12 +08:00

13 KiB
Raw Blame History

业务逻辑与数据模型 (Business Logic & Data Model) v4.0

定义支付系统的核心数据结构和业务流程

📊 数据库表结构

1. 订单表 (orders)

存储业务订单信息,与支付解耦。

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)

记录每一次支付尝试,一个订单可能有多次交易。

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)

记录账户资金变动(可选,用于虚拟币/钱包场景)。

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)

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. 标准支付流程

用户 -> 业务系统: 1. 提交订单
业务系统 -> 支付模块: 2. 创建订单 (create_order)
支付模块 -> 业务系统: 3. 返回 order_sn

用户 -> 支付模块: 4. 选择支付方式并支付 (checkout)
支付模块 -> 支付平台: 5. 创建平台交易
支付平台 -> 支付模块: 6. 返回支付参数
支付模块 -> 用户: 7. 返回支付数据 (二维码/跳转链接)

用户 -> 支付平台: 8. 完成支付
支付平台 -> 支付模块: 9. 异步回调 (notify)
支付模块 -> 支付模块: 10. 验签 + 更新状态
支付模块 -> 业务系统: 11. 触发业务回调 (发货/开通)

2. 支付回调处理流程

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. 退款流程

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

🏭 工厂模式设计

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))
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. 转换示例

# 元转分 (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. 退款幂等

  • 同一笔交易只能退款一次 (或限制总退款金额)