PAYATHON 2026

支付退款后未收到回调是否正常?

支付老李

结论

仅凭“全额退款或最后一笔分批退款后没有收到回调”,还不能判断这种情况是否正常。

退款通常是异步处理的。平台是否发送退款回调、回调包含哪些状态,以及全额退款和部分退款是否使用同一种通知,都取决于支付平台和接口协议。退款完成后,平台一般不会再次触发“支付成功回调”,而是发送单独的“退款结果通知”。如果平台不提供退款通知,就需要主动调用退款查询接口来确认最终状态。

业务系统不能只靠回调判断退款是否成功,还应提供主动查询和定时补偿机制。

可能的原因

监听了错误的回调类型

支付成功通知和退款结果通知通常是两套独立机制。订单退款后,支付平台一般不会再次发送支付回调。

请确认系统配置了“退款结果通知地址”,而不只是支付通知地址。

退款通知地址未正确传递或配置

有些支付接口要求在发起退款时传入退款通知地址,另一些平台则要求在商户后台统一配置。如果通知地址为空、格式错误或无法通过公网访问,平台可能无法送达通知。

测试环境和正式环境的配置也要分别检查,避免正式退款请求仍指向测试地址。

最后一笔退款尚未完成

分批退款时,每笔退款通常都有独立的退款单号和处理状态。最后一次提交成功,只能说明平台已经受理退款请求,不代表资金已经退回。

最后一笔退款可能仍在处理中,也可能已经失败或需要人工审核,因此暂时没有成功回调。

回调已经到达,但业务处理失败

常见情况包括:

  • 网关、WAF 或反向代理拦截了请求;
  • HTTPS 证书异常;
  • 服务响应超时;
  • 验签失败;
  • 解密失败;
  • 业务代码抛出异常;
  • 收到回调后,没有按平台要求返回成功响应;
  • 系统使用支付订单号去重,把分批退款中的后续通知误判为重复请求。

分批退款时尤其要注意,幂等键通常应使用“退款单号”或“退款通知事件 ID”,不能只使用支付订单号。

排查步骤

1. 查询每一笔退款的最终状态

逐笔记录并查询以下信息:

  • 商户订单号;
  • 支付平台交易号;
  • 商户退款单号;
  • 支付平台退款单号;
  • 本次退款金额;
  • 累计退款金额;
  • 退款状态;
  • 退款申请时间和完成时间。

分批退款必须逐笔查询,不能仅凭累计退款金额判断最后一笔退款已经成功。

2. 核对接口文档

先确认当前支付产品是否支持退款结果通知,再检查以下内容:

  • 全额退款是否发送通知;
  • 部分退款是否逐笔发送通知;
  • 通知地址在哪里配置;
  • 通知重试规则;
  • 成功响应格式;
  • 签名验证和报文解密方式;
  • 同一订单多次退款时,使用哪个字段区分各笔退款。

问题中没有说明具体的支付平台和 API,因此应以所接入平台当前的接口文档为准。

3. 检查回调链路

按退款发生的时间范围,检查回调入口、反向代理和业务服务的请求日志,确认是否出现非 2xx 响应、超时、验签失败或数据库异常。

也可以在测试环境中使用可控的公网地址接收通知,确认平台是否真正发出了请求。日志中不要记录完整密钥、签名私钥或未经脱敏的支付数据。

4. 建立主动查询补偿

退款请求被受理后,先将退款记录保存为 PROCESSING。如果超过预期时间仍未收到回调,由定时任务主动查询退款结果:

  • 查询成功:更新为 SUCCESS;
  • 查询失败:更新为 FAILED,并记录失败原因;
  • 仍在处理中:保留 PROCESSING,稍后继续查询;
  • 查询不到退款单:核对退款请求是否确实提交成功。

查询频率和最长补偿时间应符合支付平台的限流要求和接口规范。

代码示例

下面的代码是与具体支付平台无关的处理框架。实际字段名、验签方法和成功响应格式需要替换成平台规定的内容。

@PostMapping("/callbacks/refund")
public ResponseEntity<String> handleRefundCallback(
        @RequestHeader Map<String, String> headers,
        @RequestBody String body) {

    // verifySignature 需要按照支付平台规则实现
    if (!verifySignature(headers, body)) {
        return ResponseEntity.status(401).body("INVALID_SIGNATURE");
    }

    RefundNotification notification = parseRefundNotification(body);

    // 分批退款应以退款单号或事件 ID 做幂等控制
    String idempotencyKey = notification.getRefundId();

    refundService.executeInTransaction(() -> {
        if (refundEventRepository.exists(idempotencyKey)) {
            return;
        }

        RefundRecord refund = refundRepository.findByRefundId(
                notification.getRefundId()
        );

        if (refund == null) {
            throw new IllegalStateException("Refund record not found");
        }

        refund.updateStatus(
                notification.getStatus(),
                notification.getRefundAmount()
        );

        refundEventRepository.save(idempotencyKey);
    });

    // 返回值应替换为支付平台要求的成功响应
    return ResponseEntity.ok("SUCCESS");
}

定时补偿可以使用类似的逻辑:

@Scheduled(fixedDelay = 60_000)
public void reconcilePendingRefunds() {
    List<RefundRecord> records =
            refundRepository.findPendingRefundsForReconciliation();

    for (RefundRecord record : records) {
        RefundQueryResult result =
                paymentClient.queryRefund(record.getRefundId());

        switch (result.getStatus()) {
            case SUCCESS:
                refundService.markSuccess(
                        record.getRefundId(),
                        result.getRefundAmount()
                );
                break;
            case FAILED:
                refundService.markFailed(
                        record.getRefundId(),
                        result.getFailureReason()
                );
                break;
            case PROCESSING:
                // 保持处理中,等待下一次查询
                break;
            default:
                refundService.recordUnknownStatus(
                        record.getRefundId(),
                        result.getStatus()
                );
        }
    }
}

注意事项

回调可能重复、延迟或乱序,因此处理逻辑必须支持幂等。分批退款时,每笔退款都应使用唯一的退款单号,各笔退款金额之和不能超过原支付金额。

收到回调后,应先完成验签,再更新业务状态。不能因为 HTTP 请求已经到达就认定退款成功,也不能在退款刚提交时就向用户显示“退款到账”。

如果主动查询确认全额退款和最后一笔分批退款都已成功,只是没有收到通知,应继续检查退款通知配置和回调日志。如果接口文档明确说明相关场景不会发送通知,就应通过主动查询确认最终状态。若文档说明平台应发送通知,但多次复现后仍未收到,可携带商户订单号、退款单号、请求时间和脱敏日志联系支付平台技术支持。

备注:内容仅供参考。