PAYATHON 2026

支付宝沙箱退款查询接口未返回 refund_status 的原因

支付老李

结论

refund_status 并不是调用 alipay.trade.fastpay.refund.query 时必定返回的字段。沙箱环境中的可选字段可能返回不完整,表现也可能与生产环境不同,但不能根据一次沙箱响应就认定这是固定的接口限制。

还需注意以下几点:

  • code = 10000 只表示退款查询请求处理成功,不能直接理解为“退款成功”。
  • 响应中包含 refund_amount、out_request_no 和 refund_detail_item_list,说明接口已经查到对应的退款记录和资金明细。
  • 响应中有 refund_status = REFUND_SUCCESS 时,可以确认本次退款成功。
  • 没有 refund_status 时,应按兼容场景处理。需要结合退款接口的原始响应、退款金额和交易查询结果进一步确认,程序不能假定该字段一定存在。

为什么没有返回 refund_status

refund_status 是退款查询响应中可能缺省的业务字段。实际返回哪些字段,可能受以下因素影响:

  1. 沙箱环境与生产环境的数据模型并不完全一致,部分可选字段可能未被模拟。
  2. 沙箱中的历史交易或退款记录可能没有生成完整的状态数据。
  3. 不同接口网关、SDK 或响应模型处理空字段的方式不同。字段值为空时,最终 JSON 可能不会包含该字段。
  4. 查询接口成功只代表查询操作成功,具体业务状态仍要根据实际结果判断。

因此,不建议将问题归因于请求参数缺失。refund_status 是响应字段,通常无法通过请求参数要求接口强制返回。

推荐的判断顺序

1. 先判断接口调用是否成功

先检查:

code == "10000"

如果 code 不是 10000,应根据 sub_code 和 sub_msg 处理接口错误,无需继续判断退款状态。

2. 优先使用 refund_status

如果响应中包含:

{
  "refund_status": "REFUND_SUCCESS"
}

可以判定本次退款请求成功。

程序不能只判断字段是否存在,还要检查具体值,以免支付宝后续增加其他状态值时产生误判。

3. 校验退款请求号

查询退款时使用的 out_request_no,必须与发起退款时使用的退款请求号一致。

同一笔交易可能发生多次部分退款。out_trade_no 只能定位交易,具体的退款请求需要通过 out_request_no 区分。请求号不一致时,查到的可能不是预期的那次退款。

4. 缺少状态时进行兼容处理

示例响应中包含:

{
  "code": "10000",
  "out_request_no": "TKA202109271115211251",
  "refund_amount": "1.00",
  "refund_detail_item_list": [
    {
      "amount": "1.00",
      "fund_channel": "ALIPAYACCOUNT"
    }
  ]
}

这些字段至少表明,接口返回了对应退款请求的金额和资金渠道信息。但缺少 refund_status 时,更稳妥的处理方式是先标记为“待确认”,再做以下检查:

  • 查看原始退款接口 alipay.trade.refund 的响应。
  • 核对退款请求号和退款金额。
  • 调用 alipay.trade.query,检查交易的累计退款金额。
  • 对账时,以支付宝账单或生产环境的实际资金结果为准。
  • 如需重试退款,必须继续使用原来的 out_request_no,以免创建新的退款请求。

交易查询中的累计退款金额只能用来辅助确认整笔交易是否发生过退款,无法单独证明某个 out_request_no 对应的退款已经成功。

Java 兼容处理示例

AlipayTradeFastpayRefundQueryResponse response =
        alipayClient.execute(request);

if (!response.isSuccess()) {
    throw new IllegalStateException(
        "退款查询失败,code=" + response.getCode()
        + ", subCode=" + response.getSubCode()
        + ", subMsg=" + response.getSubMsg()
    );
}

String refundStatus = response.getRefundStatus();

if ("REFUND_SUCCESS".equals(refundStatus)) {
    // 明确确认退款成功
    handleRefundSuccess(response);
} else if (refundStatus == null || refundStatus.isEmpty()) {
    // 沙箱或兼容场景:不能仅凭 code=10000 判定退款成功
    log.warn(
        "退款查询未返回 refund_status,outRequestNo={}, refundAmount={}",
        response.getOutRequestNo(),
        response.getRefundAmount()
    );

    // 进入待确认流程:
    // 1. 核对原退款接口响应
    // 2. 查询交易累计退款金额
    // 3. 进行账单或资金核对
    handleRefundPendingConfirmation(response);
} else {
    // 为后续可能增加的状态保留处理分支
    handleUnknownRefundStatus(refundStatus, response);
}

如果直接处理原始 JSON,也应按可选字段读取:

String code = jsonNode.path("code").asText();
String refundStatus = jsonNode.path("refund_status").asText(null);

if (!"10000".equals(code)) {
    // 查询接口失败
} else if ("REFUND_SUCCESS".equals(refundStatus)) {
    // 退款成功
} else {
    // refund_status 缺失或出现未知状态,进入待确认流程
}

注意事项

  • 不要使用 refund_status.equals(...),否则字段缺失时会触发空指针异常。
  • 不要将 code = 10000 直接映射为退款成功。它首先表示查询接口调用成功。
  • 金额应使用 BigDecimal 比较,不能使用 double:
BigDecimal expected = new BigDecimal("1.00");
BigDecimal actual = new BigDecimal(response.getRefundAmount());

if (expected.compareTo(actual) == 0) {
    // 金额一致
}
  • 重试同一个退款请求时,应保持 out_request_no 不变,并确保退款金额等关键参数一致。
  • 沙箱主要用于联调,其字段完整性和资金状态模拟可能与生产环境不同。上线前应参考当前支付宝开放平台接口文档,并以生产环境的小额验证结果为准。
  • 如果生产环境也持续缺少 refund_status,应保存完整的请求、响应、接口名称、调用时间和支付宝 trade_no,再通过支付宝开放平台技术支持核查具体交易记录。

备注:内容仅供参考。