wechatpay-apiv3/wechatpay-apache-httpclient

退款通知签名错误

GongZhengMe opened this issue · 16 comments

你好,我使用这个sdk在接受微信退款通知的时候发现错误。接下来我描述下具体情况
首先我在支付通知这里,是没有任何错误的,无论是validate(request, verifier)还是 validateNotification(notification)都没有任何报错。但是在我使用退款通知时, validate(request, verifier)这一步出现了报错,报错代码段为

 if (!verifier.verify(serialNumber, message, signature)) {
                val errorMessage =
                    String.format("验签失败:serial=[%s] message=[%s] sign=[%s]", serialNumber, String(message), signature)
                throw ValidationException(errorMessage)
            }

我复写了这段代码,将 validate(request, verifier)这个方法注释掉,直接调用validateNotification,与setDecryptData都是可以正常使用的,请问你们是否有遇见这个bug

是否有报错的退款通知的具体数据,我来看看

是否有报错的退款通知的具体数据,我来看看

你好,这些参数是我们的正式支付里面携带的参数,请问我是否方便加你的微信将参数告知你

如果方便的话,可以再发一次请求,然后将报错的退款通知的通知ID发到这儿

Contributor

如果方便的话,可以再发一次请求,然后将报错的退款通知的通知ID发到这儿

这是刚刚发送的退款通知id:f4cadfdb-821b-524b-93c6-9e49e861efc4

我使用自己的测试信息试了一下,没有复现验签失败的场景,你可以先按照下面的思路检查一下:

  1. 确认用于验证签名的平台证书序列号和回调通知的序列号是否一致,不一致会导致验签失败。可以对比回调通知请求头的Wechatpay-Serial跟你设置的verifier中的证书序列号是否一致。
  2. 如果确认verifier中的平台证书无误,确认用于验签的信息是否设置错误,或者是否有编码问题。

好的哈,我以为有bug的原因是因为,微信支付的回调,执行验签,解密都没问题。目前我只有退款回调遇到了这个问题,只有验签有问题,但是解密是ok的。
两个通知的回调,我是公用了一个方法,应该不存在参数设置错误或者编码问题。我回头对一下,你说的情况1,如果实在不行,希望你能给我个邮箱,我把我的所有参数都给你下,你看可以吗

不用给我参数的,咱们在保证隐私安全的情况下一起来解决问题。
我验证了f4cadfdb-821b-524b-93c6-9e49e861efc4这个通知的回调签名,是可以验证通过的。你可以贴下调用代码吗?

我们公司用的语言是kotlin,这里是我的调用代码段,因为公司common包里有jackson所以我自己用了gson来解析,并将下划线字段强转驼峰,来实现的

val nonce = headers["wechatpay-nonce"]
            val timestamp = headers["wechatpay-timestamp"]
            val signature = headers["wechatpay-signature"]
            val verifier = getWechatVerifier(
                wechatPayParam.merchantId!!,
                wechatPayParam.merchantSerialNumber!!,
                wechatPayParam.apiV3Key!!
            )
            val request: NotificationRequest = NotificationRequest.Builder()
                .withSerialNumber(wechatPayParam.wechatPaySerial)
                .withNonce(nonce)
                .withTimestamp(timestamp)
                .withSignature(signature)
                .withBody(Gson().toJson(wxCallbackReqVo))
                .build();

            validate(request, verifier)
            // 因为微信使用的jackson版本过高导致readValue会报错,所以不采用微信sdk里面的方法,而是使用自定的Gson解析
            val gsonBuilder: GsonBuilder = GsonBuilder()
            gsonBuilder.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES);
            val notification = gsonBuilder.create().fromJson(request.body, Notification::class.java)
            validateNotification(notification)
            setDecryptData(notification, wechatPayParam.apiV3Key!!.toByteArray(StandardCharsets.UTF_8))

这里是我的调用代码段,这个代码段在对微信支付回调是ok的

getWechatVerifier这段代码可以贴下吗?
verifier是用什么方式生成的呢,new CertificatesVerifier() 还是使用CertificatesManager获得的?

getWechatVerifier这段代码可以贴下吗? verifier是用什么方式生成的呢,new CertificatesVerifier() 还是使用CertificatesManager获得的?
是根据demo使用的CertificatesManager

 private fun getWechatVerifier(
            merchantId: String,
            merchantSerialNumber: String,
            apiV3Key: String,
        ): Verifier {
            val certificatesManager = CertificatesManager.getInstance();
            // 向证书管理器增加需要自动更新平台证书的商户信息
            certificatesManager.putMerchant(
                merchantId, WechatPay2Credentials(
                    merchantId,
                    PrivateKeySigner(merchantSerialNumber, getPrivateKeyByFile())
                ), apiV3Key.toByteArray(StandardCharsets.UTF_8)
            );
            return certificatesManager.getVerifier(merchantId);
        }

上面的代码我没有看出问题。验签失败,我能想到两个方向:

  1. verifier有问题。你的业务代码中是否设置了日志打印?日志打印级别设置为debug,可以看到是否有异常日志输出,例如verifier获取为空,下载失败等。另外,你可以尝试将verifier改为new CertificatesVerifier()的方式,看下是否能验签通过。以此来确定是否为verifier的问题。
  2. 验签使用的数据有问题。因为你之前提及可以验证通过支付成功回调通知,所以我在想是否跟验签使用的数据有关。可以对比下Gson().toJson(wxCallbackReqVo)生成的body是否和微信支付回调通知的原始报文中的body一致。

上面的代码我没有看出问题。验签失败,我能想到两个方向:

  1. verifier有问题。你的业务代码中是否设置了日志打印?日志打印级别设置为debug,可以看到是否有异常日志输出,例如verifier获取为空,下载失败等。另外,你可以尝试将verifier改为new CertificatesVerifier()的方式,看下是否能验签通过。以此来确定是否为verifier的问题。
  2. 验签使用的数据有问题。因为你之前提及可以验证通过支付成功回调通知,所以我在想是否跟验签使用的数据有关。可以对比下Gson().toJson(wxCallbackReqVo)生成的body是否和微信支付回调通知的原始报文中的body一致。

嗯我今天想了下,可能是这个我绕过jackson用Gson解析导致的问题,现在公司网络有问题,我后续准备检查下body。至于你说的1里面verifier我打断点看过,是有值且有mchid的,应该不是1的问题

不对我刚刚看了下我代码,我是在validateSign后使用的Gson,目前报错的方法是validateSign,还没有到Gson解析那一步。可能我最后只能使用折中的办法,无法好的定位具体问题。而是收到退款回调后,跳过验签,直接解析,将解析得到的退款单号,去调用 单笔退款查询接口,以此来进行我的后续业务操作。这样做我认为也是安全的吧

你在构造NotificationRequest的时候,使用了withBody(Gson().toJson(wxCallbackReqVo))。在NotificationRequest里面,会使用传入的body来生成验签的message信息。所以,如果body有问题,验签会失败的。
另外,如果业务需要上线,我们建议是要解决问题,不要跳过验签,以防请求信息被篡改,会有安全风险。

你在构造NotificationRequest的时候,使用了withBody(Gson().toJson(wxCallbackReqVo))。在NotificationRequest里面,会使用传入的body来生成验签的message信息。所以,如果body有问题,验签会失败的。 另外,如果业务需要上线,我们建议是要解决问题,不要跳过验签,以防请求信息被篡改,会有安全风险。

好的,谢谢提醒,我之后仔细比较排查

谢谢支持,问题我已经解决了,就是Gson的问题,因为退款的resource密文里面有=号,gson默认把=号转成了\u003d,导致签名失败,已解决测试。谢谢你的支持,目前我已经全流程跑通了。