支付回调超时处理,重试队列,回调失败存入队列定时重试

支付回调超时是支付对接中很常见的故障:用户已经付款,但平台的回调请求因为网络波动、应用卡顿或接口异常没有及时到达你的服务器。
如果直接丢弃这次回调,订单就会一直处于“未支付”状态,等到对账时才发现问题。
正确的处理思路是:先把回调数据持久化到一个重试队列,再通过定时任务轮询发送,直到对方返回成功或超过最大重试次数。
下面按实际落地步骤展开。

设计重试队列前需要准备什么

实现这套方案不需要额外购买中间件,用 Redis 或数据库就能完成。
建议提前准备好:

  • 一个可用的 Redis 服务(版本 3.0 以上),用于存放待重试的回调数据;
  • 自己业务环境的队列客户端,比如 Python 的 redis-py、PHP 的 predis,或 Laravel 自带的队列驱动;
  • 一个用来模拟回调的测试接口,方便验证重试效果;
  • 考虑好回调超时上限,通常支付平台允许的同步响应时间为 10~30 秒,具体以平台文档为准。

如果项目还没有接入消息队列,直接用 Redis 的 List 结构最省事,既可以当队列,也能配合定时任务实现延迟重试。

第一步:把回调失败的数据写入队列

在支付回调接口中,先捕获所有异常和超时情况,不能只依赖框架的异常处理。
伪代码如下:

import redis
import json

r = redis.Redis.from_url("redis://localhost:6379/0")

def pay_callback(request):
    try:
        # 原有业务处理:验签、更新订单状态、通知发货等
        process_payment(request)
        return {"code": "SUCCESS"}
    except Exception as e:
        # 回调失败,原始数据保存到队列,等待重试
        payload = {
            "order_no": request.get("order_no"),
            "raw_data": request,
            "retry_count": 0,
            "last_error": str(e)
        }
        r.rpush("pay:callback:retry", json.dumps(payload))
        return {"code": "FAIL"}

这里的关键点是:即使接口返回失败,支付平台短时间内可能会重推,但你不能依赖平台的推送次数
把数据写进自己的 Redis 队列后,就掌握了重试的主动权。

第二步:定时任务扫描并重新发送回调

需要一个独立定时任务,建议每分钟跑一次,从队列左侧取出数据,重新请求自己的业务处理逻辑,而不是再次调用回调接口本身。
以 Python 的 schedule 库为例:

import schedule
import time
import json

def process_retry():
    # 从队列取出待重试数据
    item_json = r.lpop("pay:callback:retry")
    if not item_json:
        return
    item = json.loads(item_json)
    if item["retry_count"] >= 5:
        # 超过最大次数,转入死信队列或记录告警
        r.rpush("pay:callback:dead", item_json)
        return
    try:
        # 再次执行业务处理
        process_payment(item["raw_data"])
    except Exception as e:
        item["retry_count"] += 1
        item["last_error"] = str(e)
        # 重试次数没到,重新放回队列,也可以使用延迟队列控制频率
        r.rpush("pay:callback:retry", json.dumps(item))

schedule.every(1).minutes.do(process_retry)

while True:
    schedule.run_pending()
    time.sleep(1)

定时任务需要保证只有一个实例在运行,否则多个进程同时 lpop 可能产生重复处理。
生产环境建议用 blpop 阻塞读取,并配合分布式锁或单实例部署避免并发消费。

第三步:设置重试次数上限和降级告警

重试不能无限进行。
上面代码中设置了 retry_count >= 5 后转入 pay:callback:dead 队列,这个队列要额外监控。
当死信队列有数据时,说明存在无法自动修复的订单,需要人工介入。
建议在死信写入时发送告警通知(企业微信/钉钉机器人),并记录完整日志,方便排查。

关于重试间隔,如果同一订单连续失败,最好使用指数退避,比如第一次等 1 分钟,第二次等 5 分钟,而不是每单都固定间隔。
Redis 的 ZSET 或消息队列的延迟消息可以实现,这里用 List 实现的是定时间隔,适合初期快速落地。

验证重试是否生效

写一个简单的测试脚本:

redis-cli lpush pay:callback:retry '{"order_no":"123","raw_data":{},"retry_count":0}'

然后手动停止业务处理服务,观察定时任务日志,确认数据被取出、重试次数递增,服务恢复后再次处理成功。
还可以故意把 retry_count 设为 4,触发最后一次重试,检查是否转入死信队列。

避坑时请记住:回调处理逻辑必须支持幂等,同一个订单状态重复更新多次不能出错;
队列消费失败要用 try/catch 包裹,避免异常导致进程退出;
Redis 数据要定期持久化,防止重启丢失。
如果对账后发现漏单,优先查死信队列和业务日志。

回调超时重试的核心就三件事:失败先入库、定时再发送、超限转人工。
按这个流程实现后,支付状态不一致的问题基本能被自动修复,剩下的只在死信队列里等待人工处理。
如果你在集成时遇到重试后依然失败,先检查验签函数是否依赖时间戳,以及回调地址是否被防火墙拦截。

分享到:
上一篇
独立站图片WebP自动转换,Nginx实现图片格式自适应输出
下一篇
WordPress迁移服务器完整流程
1
系统公告

机房迁移升级通知

尊敬的用户: IP 段 103.23.148.x、156.224.29.x 原香港一区线路波动、攻击频繁,平台定于 7 月 5 日凌晨分批迁移至香港 GIA 机房,硬件升级 AMD 铂金机型。 迁移均在凌晨操作,最大程度降低业务影响,迁移期间服务器临时关机; 升级后配置不降低、费用不涨价,数据默认同步迁移; 迁移后 IP 全部更换,请及时修改域名解析、防火墙白名单; 建议提前备份重要数据,有问题可联系在线客服。 感谢理解与支持! 泽御云科技 2026.06.30
服务中心
客服
在线客服
24小时为您服务
咨询
联系我们
联系我们,为您的业务提供专属服务。
24/7 技术支持
如果您遇到寻求进一步的帮助,请过工单与我们进行联系。
24/7 即时支持
泽御云
售前客服
泽御云
泽御云
售后客服
泽御云
技术支持
评价
您对当前页面的整体感受是否满意?
😞
非常不满意
😕
不满意
😐
一般
🙂
满意
😊
非常满意