如何设计一个支付路由系统

Published on with 0 views and 0 comments

如何设计一个支付路由系统

从路由策略到代码实现的完整指南,含流程图、核心代码和应用场景。

一、什么是支付路由

支付路由是支付系统的"调度中心"。一笔支付请求进来后,路由系统会根据成功率、成本、渠道稳定性、商户配置、用户属性等维度,决定把这笔交易交给哪个支付渠道(微信、支付宝、银联、外卡等)处理。

简单说:支付路由 = 在多个支付渠道中选择最合适的那一个

二、为什么需要支付路由

没有路由时,系统通常只能固定走一个渠道。一旦这个渠道:

  • 网络抖动导致成功率下降
  • 通道维护或限额
  • 成本变高(比如节假日费率上调)
  • 针对特定商户/卡种被限制

整个支付体验就会直接崩盘。

有了路由系统后,可以实现:

  • 高可用:一个渠道挂了,自动切到另一个
  • 降成本:根据费率选择最便宜的渠道
  • 高成功率:优先选择当前成功率最高的渠道
  • 合规与风控:避开高风险渠道或受限场景

三、支付路由的决策维度

设计路由策略时,通常会考虑以下几个维度:

维度说明举例
成功率渠道近期支付成功率微信支付 99.2%,支付宝 98.5%
成本渠道费率银联 0.38%,支付宝 0.55%
响应速度渠道平均耗时微信 120ms,外卡 800ms
渠道状态是否可用/维护通道 A 正在维护
商户配置商户指定渠道某商户只开微信
用户属性用户使用的支付方式用户选择支付宝
卡种/地区银行卡类型、国家地区境外卡需走外卡通道
限额单笔/日累计限额通道 A 单笔上限 5 万

这些维度可以抽象成路由规则评分权重

四、系统架构

flowchart LR
    A[商户/用户发起支付] --> B[支付路由引擎]
    B --> C[规则匹配]
    C --> D[渠道评分]
    D --> E[选择最优渠道]
    E --> F[调用支付渠道]
    F --> G[渠道返回结果]
    G --> H[更新渠道状态/统计]
    H --> B

    subgraph 数据源
        I[渠道配置表]
        J[成功率监控]
        K[成本配置]
        L[熔断状态]
    end

    I --> B
    J --> B
    K --> B
    L --> B

架构核心组件:

  • 路由引擎:执行路由决策
  • 规则配置:商户白名单、渠道可用性、限额
  • 监控统计:实时统计各渠道成功率、耗时、成本
  • 熔断降级:渠道异常时自动隔离
  • 渠道适配器:统一封装各渠道接口

五、路由决策流程

flowchart TD
    Start([支付请求]) --> CheckRules{规则匹配}
    CheckRules -->|命中硬规则| Filter[过滤可用渠道]
    CheckRules -->|未命中| ReturnDefault[返回默认渠道]
    Filter --> Score[多维度评分]
    Score --> Rank[排序]
    Rank --> Select[选择Top1]
    Select --> Call[调用渠道]
    Call --> Result{结果}
    Result -->|成功| Success[返回成功]
    Result -->|失败| Retry{是否重试?}
    Retry -->|是| RetryNext[选择下一个渠道]
    RetryNext --> Call
    Retry -->|否| Fail[返回失败]
    Result -->|异常| UpdateCircuit[更新熔断状态]
    UpdateCircuit --> RetryNext

    Success --> End([结束])
    Fail --> End

流程说明:

  1. 规则匹配:先过滤掉不可用、不符合商户配置、限额超限的渠道
  2. 多维度评分:对可用渠道按成功率、成本、耗时等指标打分
  3. 排序选择:选择评分最高的渠道
  4. 调用渠道:发起真实支付请求
  5. 失败重试:失败后按排序选择下一个渠道
  6. 熔断更新:连续失败的渠道进入熔断状态,暂时隔离

六、路由策略详解

1. 优先级路由(静态路由)

按固定优先级选择渠道,适合业务初期或合规要求明确的场景。

# 优先级路由示例
def priority_route(channels, order):
    for ch in order:
        if ch in channels and channels[ch].is_available:
            return ch
    return None

2. 成本最优路由

选择费率最低的可用渠道,适合对成本敏感的场景。

# 成本最优路由示例
def cost_optimal_route(channels):
    available = [c for c in channels if c.is_available]
    return min(available, key=lambda c: c.fee_rate)

3. 成功率最优路由

选择近期成功率最高的渠道,适合对支付成功率敏感的场景。

4. 综合加权评分路由

把多个维度加权打分,得到综合评分,再排序选择。这是最常用的策略。

评分公式:

score = 成功率 × w1 + (1 - 成本占比) × w2 + 响应速度得分 × w3 + 渠道稳定性 × w4

其中权重根据业务目标调整:

  • 追求成功率:w1 调高
  • 追求成本:w2 调高
  • 追求体验:w3 调高

七、核心代码实现

下面是一个简化但可直接运行的 Python 版支付路由引擎。

7.1 数据模型

from dataclasses import dataclass
from typing import List, Dict, Optional
from enum import Enum

class ChannelStatus(Enum):
    AVAILABLE = "available"
    DEGRADED = "degraded"
    CIRCUIT_OPEN = "circuit_open"  # 熔断

@dataclass
class PaymentChannel:
    channel_id: str          # 渠道ID,如 wechat、alipay
    name: str                # 渠道名称
    fee_rate: float          # 费率,如 0.0038
    success_rate: float      # 近5分钟成功率 0.0-1.0
    avg_latency_ms: int      # 平均响应耗时
    status: ChannelStatus
    max_amount: float        # 单笔限额
    priority: int = 100      # 优先级,数字越小越优先

@dataclass
class PaymentRequest:
    amount: float
    currency: str
    user_id: str
    merchant_id: str
    pay_method: str          # 用户选择的支付方式
    card_region: str = "CN"  # 卡/账户地区

7.2 路由规则过滤

def filter_channels(
    channels: List[PaymentChannel],
    request: PaymentRequest,
    merchant_config: Dict[str, List[str]]
) -> List[PaymentChannel]:
    """
    过滤掉不可用、不满足商户配置、超限额的渠道
    """
    allowed = merchant_config.get(request.merchant_id, [])

    result = []
    for ch in channels:
        # 1. 渠道必须可用
        if ch.status == ChannelStatus.CIRCUIT_OPEN:
            continue

        # 2. 商户白名单
        if allowed and ch.channel_id not in allowed:
            continue

        # 3. 支付方式匹配
        if ch.channel_id != request.pay_method and request.pay_method != "auto":
            continue

        # 4. 单笔限额
        if request.amount > ch.max_amount:
            continue

        result.append(ch)

    return result

7.3 加权评分

def score_channel(ch: PaymentChannel) -> float:
    """
    综合评分:成功率40%、成本30%、响应速度20%、稳定性10%
    """
    success_score = ch.success_rate * 100 * 0.4

    # 成本越低得分越高,这里用 1 - 费率/0.01 做归一化
    cost_score = max(0, (1 - ch.fee_rate / 0.01)) * 100 * 0.3

    # 响应速度:100ms以内满分,超过1s得0分
    latency_score = max(0, (1000 - ch.avg_latency_ms) / 1000) * 100 * 0.2

    # 稳定性:degraded状态扣一半分
    stability_score = 100 * 0.1 if ch.status == ChannelStatus.AVAILABLE else 50 * 0.1

    return success_score + cost_score + latency_score + stability_score

7.4 路由引擎

class PaymentRouter:
    def __init__(self, channels: List[PaymentChannel], merchant_config: Dict):
        self.channels = channels
        self.merchant_config = merchant_config

    def route(self, request: PaymentRequest) -> Optional[PaymentChannel]:
        # 1. 过滤
        candidates = filter_channels(self.channels, request, self.merchant_config)
        if not candidates:
            return None

        # 2. 评分排序
        candidates.sort(key=score_channel, reverse=True)

        # 3. 返回Top1
        return candidates[0]

    def route_with_fallback(self, request: PaymentRequest) -> List[PaymentChannel]:
        """
        返回一个有序列表,用于主渠道失败时重试
        """
        candidates = filter_channels(self.channels, request, self.merchant_config)
        candidates.sort(key=score_channel, reverse=True)
        return candidates

7.5 熔断器

import time
from collections import deque

class CircuitBreaker:
    def __init__(self, threshold=5, timeout=60):
        self.threshold = threshold     # 连续失败阈值
        self.timeout = timeout         # 熔断后恢复时间(秒)
        self.failures = 0
        self.last_failure_time = 0
        self.state = "CLOSED"        # CLOSED, OPEN, HALF_OPEN

    def record_success(self):
        self.failures = 0
        self.state = "CLOSED"

    def record_failure(self):
        self.failures += 1
        self.last_failure_time = time.time()
        if self.failures >= self.threshold:
            self.state = "OPEN"

    def allow_request(self):
        if self.state == "CLOSED":
            return True
        if self.state == "OPEN":
            if time.time() - self.last_failure_time > self.timeout:
                self.state = "HALF_OPEN"
                return True
            return False
        return True  # HALF_OPEN 允许试探

7.6 完整运行示例

if __name__ == "__main__":
    channels = [
        PaymentChannel("wechat", "微信支付", 0.0038, 0.992, 120, ChannelStatus.AVAILABLE, 50000),
        PaymentChannel("alipay", "支付宝", 0.0055, 0.985, 150, ChannelStatus.AVAILABLE, 50000),
        PaymentChannel("unionpay", "银联", 0.0028, 0.978, 300, ChannelStatus.DEGRADED, 100000),
    ]

    merchant_config = {
        "m_1001": ["wechat", "alipay"],  # 该商户只开通微信和支付宝
    }

    router = PaymentRouter(channels, merchant_config)

    req = PaymentRequest(
        amount=3000.0,
        currency="CNY",
        user_id="u_123",
        merchant_id="m_1001",
        pay_method="auto",
    )

    selected = router.route(req)
    if selected:
        print(f"路由选择: {selected.name} (score={score_channel(selected):.2f})")
    else:
        print("无可用渠道")

运行结果大致:

路由选择: 微信支付 (score=45.68)

八、应用场景

场景 1:电商平台大促

双 11、618 期间流量激增,单一渠道容易被打满或限流。路由系统可以:

  • 根据渠道实时成功率动态分流
  • 把高并发订单分散到多个渠道
  • 单个渠道失败时自动重试下一个

场景 2:跨境支付

不同国家/地区支持的支付方式差异很大:

  • 中国用户:微信、支付宝
  • 欧美用户:Visa、Mastercard、PayPal
  • 东南亚用户:GrabPay、DANA

路由系统根据用户地区、卡组织、币种选择最合适的渠道。

场景 3:聚合支付服务商

聚合支付平台接入了几十个渠道,需要把每笔交易路由到最优渠道:

  • 按商户签约情况过滤
  • 按成本选择最便宜的渠道
  • 按渠道健康状态实时熔断

场景 4:降本增效

对于交易量大、毛利薄的业务,路由系统可以根据费率选择成本最低的渠道:

  • 银行卡走银联(0.28%)
  • 扫码支付走微信(0.38%)
  • 余额支付走自有资金通道(0 成本)

场景 5:高可用兜底

支付渠道偶尔会出现网络波动或接口异常。路由系统:

  • 实时监控渠道健康状态
  • 连续失败时触发熔断
  • 自动切换到备用渠道,保证用户支付成功

九、生产环境的建议

  1. 监控先行:路由的效果依赖实时数据,必须建立成功率、耗时、成本的监控。
  2. 灰度策略:新渠道上线先用小流量验证,再逐步放大。
  3. 规则可配置:路由策略和权重尽量做成配置化,不要硬编码。
  4. 兜底机制:所有渠道都失败时,要有默认兜底或人工处理方案。
  5. 幂等性:重试下一个渠道时,必须保证幂等,避免重复扣款。

十、总结

支付路由不是简单的"选一个渠道",而是一个综合考虑成功率、成本、稳定性、商户需求、用户体验的决策系统。

一个好的支付路由系统应该具备:

  • 灵活的规则配置
  • 实时数据驱动的评分机制
  • 自动熔断和失败重试
  • 完整的监控和可观测性

从简单静态路由,到动态加权评分,再到智能机器学习路由,可以逐步演进。初期建议先用规则过滤 + 加权评分 + 熔断重试,已经能满足绝大多数业务场景。


写在最后:支付系统无小事,路由设计要在稳定、成本、体验之间找到平衡,切勿为了过度优化而牺牲可靠性。