编程 支付回调设计:幂等、状态机与补偿兜底

2026-08-30 21:06:03

支付回调设计:幂等、状态机与补偿兜底

支付回调是最容易翻车的接口,没有之一。渠道会重试、会迟到、会带伪造报文来试探。硬扛扛不住,只能拆层,把每一类问题关在自己的笼子里。

1. 安全校验层:进业务之前,先挡掉大部分脏数据

这层只做校验。校验不过直接返回失败,不进业务逻辑。

  • 验签:防伪造,签名不对直接拒绝
  • 订单号合法性:订单号格式、存在性必须校验
  • 金额强校验:回调金额与订单原金额不一致,直接拒绝,不做任何模糊匹配
  • 时间戳防重放:超过容忍时间窗口的回调直接丢弃
def pre_check(request):
    if not verify_sign(request):
        return FAIL
    if not order_exists(request.order_no):
        return FAIL
    if request.amount != order.amount:
        return FAIL
    if is_replay(request.timestamp):
        return FAIL
    return PASS

2. 幂等防重层:终态不处理,中间态可更新

幂等 Key 用商户订单号 + 渠道交易号,二者组合作为唯一标识。

流程:

  1. 先查订单状态
  2. 是终态 → 直接返回成功,不重复处理
  3. 是中间态 → 加分布式锁(或数据库唯一索引)后继续走状态流转

核心记住八个字:终态不处理,中间态可更新。

key = f"{request.merchant_order_no}:{request.channel_txn_id}"

with distributed_lock(key):
    order = get_order(key)
    if order.status in TERMINAL_STATES:
        return SUCCESS      # 幂等命中,直接返回
    # 中间态,继续往下走

3. 状态机防护层:只允许正向流转,禁止回写

严卡状态机,禁止逆向回写,禁止旧状态覆盖新状态。以下规则是硬约束:

  • 支付成功后,不再接收关闭支付失败这类逆向终态回调
  • 订单关闭后,不再接收支付成功回调
  • 退款完成后,拒绝重复退款回调
# 状态名仅为示意,按各系统实际状态机替换
ALLOWED_TRANSITIONS = {
    "UNPAID":  {"PAID", "CLOSED"},
    "PAID":    {"REFUNDING", "REFUNDED"},
    "CLOSED":  set(),
}

def transition(from_state, to_state):
    if to_state not in ALLOWED_TRANSITIONS.get(from_state, set()):
        return REJECT   # 旧状态覆盖新状态,拒绝
    return ACCEPT

4. 异步解耦层:接口只做轻动作,重活全走 MQ

回调接口绝不同步执行 heavy 业务。标准流程:

接收 → 校验 → 幂等判断 → 快速落库/落日志 → 发 MQ → 立即返回渠道成功

先存报文,后执行业务。原始报文必须永久留存,出问题随时可溯源。

def handle_notify(payload):
    if pre_check(payload) != PASS:
        return FAIL

    if idempotent_hit(payload):
        return SUCCESS

    if transition(payload) != ACCEPT:
        return FAIL

    save_raw_message(payload)   # 先落库,永久留存
    mq.send(payload)            # 异步消费,执行后续业务

    return SUCCESS              # 立即返回,不拖通道

5. 补偿兜底层:永远不要信回调 100% 可达

回调可能丢、可能迟到、可能被渠道重试机制搞乱顺序,所以必须有一套补偿链路兜底:

  • 定时主动查询:扫描超时未变更状态的订单,主动向渠道发起查单
  • 日终三方对账:订单、支付流水、渠道账单三方比对,修复不一致

补偿不是拿来兜低频事故的,是拿来兜“回调不可靠”这个常态的。

回调响应规范

响应策略直接决定渠道重试行为。规则很简单:

  • 业务成功 → 返回渠道指定的成功字符串,渠道停止重试
  • 业务失败 → 返回失败标识,渠道继续重试
  • 幂等命中 → 同样返回成功,渠道不再重试

严禁随意返回、返回 500、返回空响应——渠道会把一切非成功响应当成失败,然后无限重试,直到把你打挂。

六条铁律

  1. 回调必验签、必校验金额、必校验订单状态
  2. 必做幂等,终态不重复处理
  3. 业务异步化,接口只做接收、落库、校验、发消息
  4. 严格状态机,禁止旧状态覆盖新状态
  5. 绝不只依赖回调,必须有主动轮询补偿
  6. 全链路日志留存,每一笔回调变更可追溯
复制全文 生成海报 支付 回调 幂等 状态机 设计

推荐文章

程序员茄子在线接单