weixin-java-pay-demo icon indicating copy to clipboard operation
weixin-java-pay-demo copied to clipboard

支付回调的时候调用parseOrderNotifyV3Result有时候会报这样的错

Open zhudouJiang opened this issue 2 years ago • 1 comments

问题(提问前,请确保阅读过项目首页说明以及SDK wiki文档相关内容)

简要描述

Cannot invoke "com.github.binarywang.wxpay.bean.notify.OriginNotifyResponse.getResource()" because "response" is null| at com.github.binarywang.wxpay.service.impl.BaseWxPayServiceImpl.parseOrderNotifyV3Result(BaseWxPayServiceImpl.java:353)| at com.care.health.pay.recevier.PayNotifyReceiver.notifySuccess(PayNotifyReceiver.java:75)| at jdk.internal.reflect.GeneratedMethodAccessor463.invoke(Unknown Source)| at

版本情况

  • WxJava 版本号: 4.4.0

期待结果

实际情况

这里我们是做了个异步事件不知道是不是跟这个关系

重现步骤

  1. 发起支付
  2. 支付回调
  3. 回调的时候有几率会报这样的错

日志

日志内容如果过多,请将日志放在 pastebin 或者其他地方,并将url地址贴在这里

zhudouJiang avatar Sep 08 '23 12:09 zhudouJiang

您好!这个错误 Cannot invoke "com.github.binarywang.wxpay.bean.notify.OriginNotifyResponse.getResource()" because "response" is null发生在 BaseWxPayServiceImpl.parseOrderNotifyV3Result 方法的第353行,当尝试调用 response.getResource() 时,发现 response 对象本身是 null。这通常意味着在解析微信支付V3版本的回调通知时,未能成功将回调的原始数据(通常是JSON字符串)反序列化为 OriginNotifyResponse 对象。

根据您提供的信息:

  • WxJava 版本号: 4.4.0
  • 问题场景: 支付回调时有几率发生。
  • 异步事件: 您提到可能与异步事件有关。

可能的原因及排查方向:

  1. 回调请求体为空或格式不正确

    • 最直接的原因是微信支付发送过来的回调请求的 body (请求体) 可能在某些情况下是空的,或者不是一个合法的JSON字符串,导致 weixin-java-pay 库在尝试解析时得到一个 nullHttpServletRequest 对象或者无法正确读取到请求体内容,进而导致 responsenull
    • 排查:在您的 PayNotifyReceiver.notifySuccess 方法接收到回调的最开始,以及在调用 parseOrderNotifyV3Result 之前,务必打印接收到的原始回调请求的头部信息和完整的请求体内容。这样可以确认在发生错误时,微信支付实际发送过来的数据是什么。
      // 在 PayNotifyReceiver.notifySuccess 方法中
      // HttpServletRequest request = ...; (获取请求对象)
      // String requestBody = request.getReader().lines().collect(Collectors.joining(System.lineSeparator()));
      // log.info("Received WeChat Pay V3 Notify Request Body: " + requestBody);
      // log.info("Received WeChat Pay V3 Notify Request Headers: " + getAllHeaders(request)); // 一个辅助方法打印所有头部
      
      // ... 然后再调用 wxPayService.parseOrderNotifyV3Result(requestBody, signature, nonce, timestamp, apiV3Key, serialNo)
      // 注意:parseOrderNotifyV3Result 方法通常需要传入请求体字符串和头部中的签名信息等。
      // 您需要确认您是如何将 HttpServletRequest 传递或转换并最终让 BaseWxPayServiceImpl 使用的。
      // 在 4.4.0 版本,parseOrderNotifyV3Result 接收的参数可能是 (String xmlData, String signType, String sign) 或类似,
      // 或者直接是 (HttpServletRequest request, String apiV3Key) 这样的形式,然后内部处理。
      // 您需要确认您调用的是哪个重载方法,以及参数是否正确传递。
      
    • 根据 BaseWxPayServiceImpl.java:353 这个行号,在 weixin-java-pay 4.4.0 版本中,parseOrderNotifyV3Result 方法内部确实有一个步骤是解析请求体为 OriginNotifyResponse。如果传入的请求体字符串本身就有问题,或者解析逻辑出错,response 就会是 null
  2. 异步处理导致请求上下文丢失或提前关闭

    • 如果您在异步线程中处理回调,并且直接传递了原始的 HttpServletRequest 对象,那么当异步任务执行时,原始的HTTP请求可能已经结束,连接已关闭,导致无法再从 request 对象中读取请求体。
    • 排查/解决:在进入异步处理之前,就应该立即读取并保存完整的请求体字符串。然后将这个字符串传递给异步任务进行处理,而不是传递 HttpServletRequest 对象本身。
      // 同步部分
      // String requestBody = request.getReader().lines().collect(Collectors.joining(System.lineSeparator()));
      // String signature = request.getHeader("Wechatpay-Signature");
      // String nonce = request.getHeader("Wechatpay-Nonce");
      // String timestamp = request.getHeader("Wechatpay-Timestamp");
      // String serial = request.getHeader("Wechatpay-Serial");
      // eventPublisher.publishEvent(new WeChatPayNotifyEvent(requestBody, signature, nonce, timestamp, serial));
      
      // 异步事件监听器中
      // public void handleWeChatPayNotify(WeChatPayNotifyEvent event) {
      //     WxPayNotifyV3Result result = wxPayService.parseOrderNotifyV3Result(
      //         event.getRequestBody(),
      //         event.getSignature(),
      //         event.getNonce(),
      //         event.getTimestamp(),
      //         // this.wxPayConfig.getApiV3Key(), // 从配置获取
      //         event.getSerial()
      //     );
      //     // ...
      // }
      
      weixin-java-pay 4.4.0 中,parseOrderNotifyV3Result 的一个典型重载可能是这样的: public WxPayNotifyV3Result parseOrderNotifyV3Result(String notifyData, String signature, String nonce, String timestamp, String apiV3Key, String serialNo) 或者它也可能有一个直接接收 HttpServletRequest 的版本,内部做了读取。关键是确保读取发生在请求有效的时候。
  3. Filter 或 Interceptor 提前消费了请求体

    • 某些框架的 Filter 或 Interceptor (例如日志记录、安全扫描等) 可能会提前读取请求体。标准的 HttpServletRequestInputStream 默认只能读取一次。如果其他组件已经读取了,后续代码再尝试读取就会得到空内容。
    • 排查/解决:检查您的项目中是否有其他 Filter 或 Interceptor 在 PayNotifyReceiver 之前处理了回调请求。如果有,确保它们使用了 HttpServletRequestWrapper 之类的机制来缓存请求体,以便后续可以重复读取。或者,在您的第一个处理点(比如一个专门的Filter)就读取并缓存请求体,然后传递缓存的内容。
  4. 微信支付回调的偶发性问题

    • 虽然概率较低,但不能完全排除微信支付在极少数情况下发送了不完整或空的回调。打印原始日志是确认这一点的唯一方法。
  5. weixin-java-pay 库的特定 Bug (特定于4.4.0版本)

    • 虽然您没有找到相关的 issue,但如果以上都排查过,可以考虑是否是特定版本下的一个未被广泛发现的 bug。不过通常这种核心解析逻辑会比较稳定。

建议的调试步骤:

  1. 详细日志记录:在 PayNotifyReceiver.notifySuccess 方法的最开始,以及调用 parseOrderNotifyV3Result 之前,务必记录下所有相关的HTTP头部(特别是微信支付V3回调要求的 Wechatpay-Signature, Wechatpay-Nonce, Wechatpay-Timestamp, Wechatpay-Serial)和完整的HTTP请求体。

  2. 同步转异步的时机:如果您使用了异步事件,请确保在将任务交给异步处理器之前,已经将所有需要的数据(如请求体字符串、头部签名信息)从 HttpServletRequest 中提取出来。异步处理器应该处理这些提取出来的数据,而不是原始的 HttpServletRequest 对象。

  3. 检查 parseOrderNotifyV3Result 的调用方式:确认您是如何调用 parseOrderNotifyV3Result 的。BaseWxPayServiceImpl 中有多个重载版本。

    • 例如,有一个版本是 parseOrderNotifyV3Result(HttpServletRequest request, String apiV3Key)。如果使用这个版本,库内部会尝试从 request 读取。
    • 还有一个版本是 parseOrderNotifyV3Result(String notifyData, String signature, String nonce, String timestamp, String apiV3Key, String serialNo)。如果使用这个版本,您需要自己先从 HttpServletRequest 中安全地获取 notifyData (请求体字符串) 和其他头部信息。 根据您提供的堆栈信息,错误发生在 BaseWxPayServiceImpl.java:353。您需要查看 4.4.0 版本中该行的具体代码,了解它期望 response 对象是如何被初始化的,以及它的前置依赖是什么。

    weixin-java-pay 4.4.0 的 BaseWxPayServiceImpl 中,parseOrderNotifyV3Result(HttpServletRequest request, String apiV3Key) 方法内部会调用 this.parseOrderNotifyV3Result(request, null, null, null, apiV3Key, null); 然后这个会进一步调用 parseOrderNotifyV3Result(request.getHeader("Wechatpay-Signature"), request.getHeader("Wechatpay-Nonce"), request.getHeader("Wechatpay-Timestamp"), EntityUtils.toString(request.getEntity()), apiV3Key, request.getHeader("Wechatpay-Serial")) (或者类似的读取方式)。

    关键点在于 EntityUtils.toString(request.getEntity())request.getReader() 这类操作。如果请求体已被消费,这里就会拿到空字符串或 null,导致后续 WxPayNotifyV3Result.build() 方法在尝试解析这个空内容为 OriginNotifyResponse 时返回 null

总结:

最可能的原因是请求体在传递给 parseOrderNotifyV3Result 方法时已经为空,或者在异步处理中 HttpServletRequest 对象已失效。请重点排查请求体的读取时机和异步处理的数据传递方式。

binarywang avatar May 14 '25 04:05 binarywang