PAYATHON 2026

Awaitility 等待 payment state 时 10 秒后报错

支付老李

结论

问题不在 Awaitility,而是等待期间始终在检查同一个 rawRes。

Response 是请求完成时生成的响应快照。即使后台随后将 payment state 更新为 Refunded,原来的 rawRes`` 也不会随之变化。因此,Awaitility 在 10 秒内反复读取的都是旧状态,最后会抛出 ConditionTimeoutException`。

每次轮询时都应重新调用查询接口,再检查最新响应。

推荐写法

假设 getOrder(orderId) 会重新请求订单接口:

public static Response verifyLineItemIsRefunded(String orderId) {
    AtomicReference<Response> latestResponse = new AtomicReference<>();

    await()
        .atMost(10, SECONDS)
        .pollInterval(500, MILLISECONDS)
        .untilAsserted(() -> {
            Response response = getOrder(orderId);
            latestResponse.set(response);

            String paymentState = response.jsonPath()
                .getString("returnInfo[0].items[0].paymentState");

            assertThat(paymentState, equalToIgnoringCase("Refunded"));
        });

    logInfo("Line item is refunded");
    return latestResponse.get();
}

需要的静态导入如下:

import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.concurrent.TimeUnit.SECONDS;
import static org.awaitility.Awaitility.await;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalToIgnoringCase;

import java.util.concurrent.atomic.AtomicReference;

getOrder(orderId) 只是示例名称,需要替换为项目中实际用于查询最新订单状态的方法。

如果不需要返回最新 Response

如果这个方法只负责验证状态,可以简化为:

public static void verifyLineItemIsRefunded(String orderId) {
    await()
        .atMost(10, SECONDS)
        .pollInterval(500, MILLISECONDS)
        .untilAsserted(() -> {
            String paymentState = getOrder(orderId)
                .jsonPath()
                .getString("returnInfo[0].items[0].paymentState");

            assertThat(paymentState, equalToIgnoringCase("Refunded"));
        });

    logInfo("Line item is refunded");
}

也可以直接等待布尔条件:

public static void verifyLineItemIsRefunded(String orderId) {
    await()
        .atMost(10, SECONDS)
        .pollInterval(500, MILLISECONDS)
        .until(() -> {
            String paymentState = getOrder(orderId)
                .jsonPath()
                .getString("returnInfo[0].items[0].paymentState");

            return "Refunded".equalsIgnoreCase(paymentState);
        });
}

将常量放在前面调用 equalsIgnoreCase,可以避免 paymentState 为 null 时触发 NullPointerException。

为什么原代码不会成功

原代码的核心逻辑相当于:

await().untilAsserted(() -> check(rawRes));

Awaitility 会反复调用 check(rawRes),但不会重新执行生成 rawRes 的 HTTP 请求。实际流程是:

请求一次 -> 得到旧 Response -> 重复检查同一个 Response -> 10 秒后超时

应该改成:

重新请求 -> 检查最新 Response -> 未满足则稍后再次请求

注意事项

  • ConditionTimeoutException 表示条件在规定时间内一直未通过,不代表 Awaitility 本身失效。
  • 10 秒是否足够,取决于退款处理、消息队列、第三方支付和数据同步的实际耗时,不能只根据单次验证所需的时间判断。
  • 设置合理的 pollInterval,避免短时间内频繁调用接口。
  • 如果单次 HTTP 请求可能长时间阻塞,还需要为 HTTP 客户端设置连接超时和读取超时。
  • 确认 JSON 路径 returnInfo[0].items[0].paymentState 指向目标 line item。订单中有多个 item 时,固定读取 [0] 可能会检查到错误的对象。
  • 如果方法需要返回响应,返回值应是最后一次查询得到的 Response,否则调用方拿到的仍可能是最初的旧响应。

备注:内容仅供参考。