Skip to content

Commit 1f4ed68

Browse files
authored
🎨 #3750 【微信支付】修复 V2 支付回调签名验证失败的问题
1 parent c4f3834 commit 1f4ed68

File tree

2 files changed

+107
-1
lines changed

2 files changed

+107
-1
lines changed

weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/notify/WxPayOrderNotifyResult.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -342,7 +342,9 @@ public static WxPayOrderNotifyResult fromXML(String xmlString) {
342342

343343
@Override
344344
public Map<String, String> toMap() {
345-
Map<String, String> resultMap = SignUtils.xmlBean2Map(this);
345+
// 使用父类的 toMap() 方法,直接从原始 XML 解析所有字段,
346+
// 确保包含未在 Java Bean 中定义的字段,避免签名验证失败
347+
Map<String, String> resultMap = super.toMap();
346348
if (this.getCouponCount() != null && this.getCouponCount() > 0) {
347349
for (int i = 0; i < this.getCouponCount(); i++) {
348350
WxPayOrderNotifyCoupon coupon = couponList.get(i);
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
package com.github.binarywang.wxpay.bean.notify;
2+
3+
import com.github.binarywang.wxpay.constant.WxPayConstants;
4+
import org.apache.commons.codec.digest.DigestUtils;
5+
import org.testng.Assert;
6+
import org.testng.annotations.Test;
7+
8+
import java.util.*;
9+
10+
/**
11+
* 测试当微信支付回调 XML 包含未在 Java Bean 中定义的字段时,签名验证是否正常。
12+
* <p>
13+
* 问题背景:当微信返回的 XML 包含某些未在 WxPayOrderNotifyResult 中定义的字段时,
14+
* 这些字段会被微信服务器用于签名计算。如果 toMap() 方法丢失了这些字段,
15+
* 则签名验证会失败,抛出 "参数格式校验错误!" 异常。
16+
* </p>
17+
* <p>
18+
* 解决方案:修改 WxPayOrderNotifyResult.toMap() 方法,使用父类的 toMap() 方法
19+
* 直接从原始 XML 解析所有字段,而不是使用 SignUtils.xmlBean2Map(this)。
20+
* </p>
21+
*
22+
* @see <a href="https://github.com/binarywang/WxJava/issues/3750">Issue #3750</a>
23+
*/
24+
public class WxPayOrderNotifyUnknownFieldTest {
25+
26+
private static final String MCH_KEY = "testmchkey1234567890123456789012";
27+
private static final List<String> NO_SIGN_PARAMS = Arrays.asList("sign", "key", "xmlString", "xmlDoc", "couponList");
28+
29+
@Test
30+
public void testSignatureWithUnknownField() throws Exception {
31+
// 创建一个测试用的 XML,包含一个未知字段 (未在 WxPayOrderNotifyResult 中定义)
32+
Map<String, String> params = new LinkedHashMap<>();
33+
params.put("appid", "wx58ff40508696691f");
34+
params.put("bank_type", "ICBC_DEBIT");
35+
params.put("cash_fee", "1");
36+
params.put("fee_type", "CNY");
37+
params.put("is_subscribe", "N");
38+
params.put("mch_id", "1545462911");
39+
params.put("nonce_str", "1761723102373");
40+
params.put("openid", "o1gdd16CZCi6yYvkn6j9EB_1TObM");
41+
params.put("out_trade_no", "20251029153140");
42+
params.put("result_code", "SUCCESS");
43+
params.put("return_code", "SUCCESS");
44+
params.put("time_end", "20251029153852");
45+
params.put("total_fee", "1");
46+
params.put("trade_type", "JSAPI");
47+
params.put("transaction_id", "4200002882220251029816273963B");
48+
// 添加一个未知字段
49+
params.put("unknown_field", "unknown_value");
50+
51+
// 计算正确的签名 (包含未知字段)
52+
String correctSign = createSign(params, WxPayConstants.SignType.MD5, MCH_KEY);
53+
params.put("sign", correctSign);
54+
55+
// 创建 XML
56+
StringBuilder xmlBuilder = new StringBuilder("<xml>");
57+
for (Map.Entry<String, String> entry : params.entrySet()) {
58+
xmlBuilder.append("<").append(entry.getKey()).append(">")
59+
.append(entry.getValue())
60+
.append("</").append(entry.getKey()).append(">");
61+
}
62+
xmlBuilder.append("</xml>");
63+
String xml = xmlBuilder.toString();
64+
65+
System.out.println("测试 XML (包含未知字段 unknown_field):");
66+
System.out.println(xml);
67+
System.out.println("正确的签名 (包含未知字段计算): " + correctSign);
68+
69+
// 解析 XML
70+
WxPayOrderNotifyResult result = WxPayOrderNotifyResult.fromXML(xml);
71+
Map<String, String> beanMap = result.toMap();
72+
73+
System.out.println("\ntoMap() 结果:");
74+
TreeMap<String, String> sortedMap = new TreeMap<>(beanMap);
75+
for (Map.Entry<String, String> entry : sortedMap.entrySet()) {
76+
System.out.println(" " + entry.getKey() + " = " + entry.getValue());
77+
}
78+
79+
// 检查 unknown_field 是否存在
80+
boolean hasUnknownField = beanMap.containsKey("unknown_field");
81+
System.out.println("\ntoMap() 是否包含 unknown_field: " + hasUnknownField);
82+
83+
// 验证签名
84+
String verifySign = createSign(beanMap, WxPayConstants.SignType.MD5, MCH_KEY);
85+
System.out.println("原始签名: " + result.getSign());
86+
System.out.println("计算签名: " + verifySign);
87+
88+
// 这个测试验证修复后 toMap() 能正确包含所有字段
89+
Assert.assertTrue(hasUnknownField, "toMap() 应该包含 unknown_field");
90+
Assert.assertEquals(verifySign, result.getSign(), "签名应该匹配");
91+
}
92+
93+
private static String createSign(Map<String, String> params, String signType, String signKey) {
94+
StringBuilder toSign = new StringBuilder();
95+
for (String key : new TreeMap<>(params).keySet()) {
96+
String value = params.get(key);
97+
if (value != null && !value.isEmpty() && !NO_SIGN_PARAMS.contains(key)) {
98+
toSign.append(key).append("=").append(value).append("&");
99+
}
100+
}
101+
toSign.append("key=").append(signKey);
102+
return DigestUtils.md5Hex(toSign.toString()).toUpperCase();
103+
}
104+
}

0 commit comments

Comments
 (0)