From 36358accc9febf8150fc5f1cc0589f9132532c01 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 28 Nov 2025 02:00:21 +0000
Subject: [PATCH 1/4] Initial plan
From 089104548a8580dbeeda12bea90e390992f314bc Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 28 Nov 2025 02:12:14 +0000
Subject: [PATCH 2/4] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=B5=8B=E8=AF=95?=
=?UTF-8?q?=E7=94=A8=E4=BE=8B=E9=AA=8C=E8=AF=81=E6=9C=AA=E7=9F=A5=E5=AD=97?=
=?UTF-8?q?=E6=AE=B5=E5=AF=BC=E8=87=B4=E7=AD=BE=E5=90=8D=E9=AA=8C=E8=AF=81?=
=?UTF-8?q?=E5=A4=B1=E8=B4=A5=E7=9A=84=E9=97=AE=E9=A2=98?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Co-authored-by: binarywang <1343140+binarywang@users.noreply.github.com>
---
...xPayOrderNotifyResultToMapCompareTest.java | 105 +++++++++++++++
.../WxPayOrderNotifyResultToMapTest.java | 91 +++++++++++++
.../notify/WxPayOrderNotifySignatureTest.java | 120 ++++++++++++++++++
.../WxPayOrderNotifyUnknownFieldTest.java | 96 ++++++++++++++
4 files changed, 412 insertions(+)
create mode 100644 weixin-java-pay/src/test/java/com/github/binarywang/wxpay/bean/notify/WxPayOrderNotifyResultToMapCompareTest.java
create mode 100644 weixin-java-pay/src/test/java/com/github/binarywang/wxpay/bean/notify/WxPayOrderNotifyResultToMapTest.java
create mode 100644 weixin-java-pay/src/test/java/com/github/binarywang/wxpay/bean/notify/WxPayOrderNotifySignatureTest.java
create mode 100644 weixin-java-pay/src/test/java/com/github/binarywang/wxpay/bean/notify/WxPayOrderNotifyUnknownFieldTest.java
diff --git a/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/bean/notify/WxPayOrderNotifyResultToMapCompareTest.java b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/bean/notify/WxPayOrderNotifyResultToMapCompareTest.java
new file mode 100644
index 0000000000..7f3ffe0315
--- /dev/null
+++ b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/bean/notify/WxPayOrderNotifyResultToMapCompareTest.java
@@ -0,0 +1,105 @@
+package com.github.binarywang.wxpay.bean.notify;
+
+import com.github.binarywang.wxpay.bean.result.BaseWxPayResult;
+import com.github.binarywang.wxpay.util.SignUtils;
+import com.github.binarywang.wxpay.util.XmlConfig;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import java.io.ByteArrayInputStream;
+import java.lang.reflect.Method;
+import java.nio.charset.StandardCharsets;
+import java.util.*;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.xpath.XPathConstants;
+import javax.xml.xpath.XPathFactory;
+import org.w3c.dom.Document;
+import org.w3c.dom.NodeList;
+
+/**
+ * 比较两种 toMap() 方法实现的差异
+ */
+public class WxPayOrderNotifyResultToMapCompareTest {
+
+ @Test
+ public void testCompareToMapMethods() throws Exception {
+ // 从 issue 的截图中提取的 XML 数据
+ String xml = "" +
+ "wx58ff40508696691f" +
+ "ICBC_DEBIT" +
+ "1" +
+ "CNY" +
+ "N" +
+ "1545462911" +
+ "1761723102373" +
+ "o1gdd16CZCi6yYvkn6j9EB_1TObM" +
+ "20251029153140" +
+ "SUCCESS" +
+ "SUCCESS" +
+ "03F5C68CA8F2E30855077FA3FC21EBEA" +
+ "20251029153852" +
+ "1" +
+ "JSAPI" +
+ "4200002882220251029816273963B" +
+ "";
+
+ WxPayOrderNotifyResult result = WxPayOrderNotifyResult.fromXML(xml);
+
+ // WxPayOrderNotifyResult 重写的 toMap(),使用 SignUtils.xmlBean2Map(this)
+ Map beanMap = result.toMap();
+
+ // BaseWxPayResult.toMap() 的方式,直接从 XML 解析
+ Map xmlMap = parseXmlToMap(xml);
+
+ System.out.println("=== WxPayOrderNotifyResult.toMap() (SignUtils.xmlBean2Map) ===");
+ printSignString(beanMap);
+
+ System.out.println("\n=== 直接从 XML 解析的 Map ===");
+ printSignString(xmlMap);
+
+ // 找出差异
+ System.out.println("\n=== 差异 ===");
+ Set allKeys = new TreeSet<>();
+ allKeys.addAll(beanMap.keySet());
+ allKeys.addAll(xmlMap.keySet());
+
+ for (String key : allKeys) {
+ String beanValue = beanMap.get(key);
+ String xmlValue = xmlMap.get(key);
+ if (!Objects.equals(beanValue, xmlValue)) {
+ System.out.println(key + ": beanMap=" + beanValue + ", xmlMap=" + xmlValue);
+ }
+ }
+ }
+
+ private static Map parseXmlToMap(String xml) throws Exception {
+ Map result = new HashMap<>();
+ DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+ factory.setExpandEntityReferences(false);
+ factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
+ Document doc = factory.newDocumentBuilder().parse(new ByteArrayInputStream(xml.getBytes(StandardCharsets.UTF_8)));
+
+ NodeList list = (NodeList) XPathFactory.newInstance().newXPath()
+ .compile("/xml/*")
+ .evaluate(doc, XPathConstants.NODESET);
+
+ int len = list.getLength();
+ for (int i = 0; i < len; i++) {
+ result.put(list.item(i).getNodeName(), list.item(i).getTextContent());
+ }
+ return result;
+ }
+
+ private static void printSignString(Map params) {
+ List noSignParams = Arrays.asList("sign", "key", "xmlString", "xmlDoc", "couponList");
+ StringBuilder sb = new StringBuilder();
+ for (String key : new TreeMap<>(params).keySet()) {
+ String value = params.get(key);
+ if (value != null && !value.isEmpty() && !noSignParams.contains(key)) {
+ sb.append(key).append("=").append(value).append("&");
+ }
+ }
+ sb.append("key=YOUR_MCH_KEY");
+ System.out.println(sb.toString());
+ }
+}
diff --git a/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/bean/notify/WxPayOrderNotifyResultToMapTest.java b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/bean/notify/WxPayOrderNotifyResultToMapTest.java
new file mode 100644
index 0000000000..f26b8c927b
--- /dev/null
+++ b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/bean/notify/WxPayOrderNotifyResultToMapTest.java
@@ -0,0 +1,91 @@
+package com.github.binarywang.wxpay.bean.notify;
+
+import com.github.binarywang.wxpay.bean.result.BaseWxPayResult;
+import com.github.binarywang.wxpay.util.XmlConfig;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import java.util.Map;
+
+/**
+ * 测试 WxPayOrderNotifyResult.toMap() 方法是否正确包含所有 XML 字段
+ */
+public class WxPayOrderNotifyResultToMapTest {
+
+ @Test
+ public void testToMapContainsAllFields() {
+ // 从 issue 的截图中提取的 XML 数据
+ String xml = "" +
+ "wx58ff40508696691f" +
+ "ICBC_DEBIT" +
+ "1" +
+ "CNY" +
+ "N" +
+ "1545462911" +
+ "1761723102373" +
+ "o1gdd16CZCi6yYvkn6j9EB_1TObM" +
+ "20251029153140" +
+ "SUCCESS" +
+ "SUCCESS" +
+ "03F5C68CA8F2E30855077FA3FC21EBEA" +
+ "20251029153852" +
+ "1" +
+ "JSAPI" +
+ "4200002882220251029816273963B" +
+ "";
+
+ WxPayOrderNotifyResult result = WxPayOrderNotifyResult.fromXML(xml);
+ Map map = result.toMap();
+
+ System.out.println("toMap() 结果:");
+ map.entrySet().stream()
+ .sorted(Map.Entry.comparingByKey())
+ .forEach(entry -> System.out.println(" " + entry.getKey() + " = " + entry.getValue()));
+
+ // 验证关键字段是否存在
+ Assert.assertTrue(map.containsKey("is_subscribe"), "toMap() 应该包含 is_subscribe 字段");
+ Assert.assertEquals(map.get("is_subscribe"), "N", "is_subscribe 的值应该是 N");
+
+ Assert.assertTrue(map.containsKey("bank_type"), "toMap() 应该包含 bank_type 字段");
+ Assert.assertEquals(map.get("bank_type"), "ICBC_DEBIT", "bank_type 的值应该是 ICBC_DEBIT");
+ }
+
+ @Test
+ public void testToMapWithFastMode() {
+ String xml = "" +
+ "wx58ff40508696691f" +
+ "ICBC_DEBIT" +
+ "1" +
+ "CNY" +
+ "N" +
+ "1545462911" +
+ "1761723102373" +
+ "o1gdd16CZCi6yYvkn6j9EB_1TObM" +
+ "20251029153140" +
+ "SUCCESS" +
+ "SUCCESS" +
+ "03F5C68CA8F2E30855077FA3FC21EBEA" +
+ "20251029153852" +
+ "1" +
+ "JSAPI" +
+ "4200002882220251029816273963B" +
+ "";
+
+ XmlConfig.fastMode = true;
+ try {
+ WxPayOrderNotifyResult result = BaseWxPayResult.fromXML(xml, WxPayOrderNotifyResult.class);
+ Map map = result.toMap();
+
+ System.out.println("fastMode toMap() 结果:");
+ map.entrySet().stream()
+ .sorted(Map.Entry.comparingByKey())
+ .forEach(entry -> System.out.println(" " + entry.getKey() + " = " + entry.getValue()));
+
+ // 验证关键字段是否存在
+ Assert.assertTrue(map.containsKey("is_subscribe"), "fastMode toMap() 应该包含 is_subscribe 字段");
+ Assert.assertEquals(map.get("is_subscribe"), "N", "is_subscribe 的值应该是 N");
+ } finally {
+ XmlConfig.fastMode = false;
+ }
+ }
+}
diff --git a/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/bean/notify/WxPayOrderNotifySignatureTest.java b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/bean/notify/WxPayOrderNotifySignatureTest.java
new file mode 100644
index 0000000000..ca711f1b19
--- /dev/null
+++ b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/bean/notify/WxPayOrderNotifySignatureTest.java
@@ -0,0 +1,120 @@
+package com.github.binarywang.wxpay.bean.notify;
+
+import com.github.binarywang.wxpay.bean.result.BaseWxPayResult;
+import com.github.binarywang.wxpay.constant.WxPayConstants;
+import com.github.binarywang.wxpay.util.SignUtils;
+import com.github.binarywang.wxpay.util.XmlConfig;
+import org.apache.commons.codec.digest.DigestUtils;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import java.io.ByteArrayInputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.*;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.xpath.XPathConstants;
+import javax.xml.xpath.XPathFactory;
+import org.w3c.dom.Document;
+import org.w3c.dom.NodeList;
+
+/**
+ * 测试签名验证逻辑
+ */
+public class WxPayOrderNotifySignatureTest {
+
+ private static final String MCH_KEY = "testmchkey1234567890123456789012"; // 32位测试密钥
+ private static final List NO_SIGN_PARAMS = Arrays.asList("sign", "key", "xmlString", "xmlDoc", "couponList");
+
+ @Test
+ public void testSignatureVerification() throws Exception {
+ // 创建一个测试用的 XML,并手动计算正确的签名
+ Map params = new LinkedHashMap<>();
+ params.put("appid", "wx58ff40508696691f");
+ params.put("bank_type", "ICBC_DEBIT");
+ params.put("cash_fee", "1");
+ params.put("fee_type", "CNY");
+ params.put("is_subscribe", "N");
+ params.put("mch_id", "1545462911");
+ params.put("nonce_str", "1761723102373");
+ params.put("openid", "o1gdd16CZCi6yYvkn6j9EB_1TObM");
+ params.put("out_trade_no", "20251029153140");
+ params.put("result_code", "SUCCESS");
+ params.put("return_code", "SUCCESS");
+ params.put("time_end", "20251029153852");
+ params.put("total_fee", "1");
+ params.put("trade_type", "JSAPI");
+ params.put("transaction_id", "4200002882220251029816273963B");
+
+ // 计算正确的签名
+ String correctSign = createSign(params, WxPayConstants.SignType.MD5, MCH_KEY);
+ params.put("sign", correctSign);
+
+ // 创建 XML
+ StringBuilder xmlBuilder = new StringBuilder("");
+ for (Map.Entry entry : params.entrySet()) {
+ xmlBuilder.append("<").append(entry.getKey()).append(">")
+ .append(entry.getValue())
+ .append("").append(entry.getKey()).append(">");
+ }
+ xmlBuilder.append("");
+ String xml = xmlBuilder.toString();
+
+ System.out.println("测试 XML:");
+ System.out.println(xml);
+ System.out.println("计算的签名: " + correctSign);
+
+ // 测试普通模式
+ System.out.println("\n=== 普通模式 ===");
+ WxPayOrderNotifyResult result = WxPayOrderNotifyResult.fromXML(xml);
+ Map beanMap = result.toMap();
+
+ System.out.println("toMap() 结果 (用于签名验证):");
+ TreeMap sortedMap = new TreeMap<>(beanMap);
+ for (Map.Entry entry : sortedMap.entrySet()) {
+ if (!NO_SIGN_PARAMS.contains(entry.getKey())) {
+ System.out.println(" " + entry.getKey() + " = " + entry.getValue());
+ }
+ }
+
+ // 验证签名
+ String verifySign = createSign(beanMap, WxPayConstants.SignType.MD5, MCH_KEY);
+ System.out.println("原始签名: " + result.getSign());
+ System.out.println("计算签名: " + verifySign);
+ Assert.assertEquals(verifySign, result.getSign(), "签名应该匹配");
+
+ // 测试 fastMode
+ System.out.println("\n=== Fast Mode ===");
+ XmlConfig.fastMode = true;
+ try {
+ result = BaseWxPayResult.fromXML(xml, WxPayOrderNotifyResult.class);
+ beanMap = result.toMap();
+
+ System.out.println("fastMode toMap() 结果 (用于签名验证):");
+ sortedMap = new TreeMap<>(beanMap);
+ for (Map.Entry entry : sortedMap.entrySet()) {
+ if (!NO_SIGN_PARAMS.contains(entry.getKey())) {
+ System.out.println(" " + entry.getKey() + " = " + entry.getValue());
+ }
+ }
+
+ verifySign = createSign(beanMap, WxPayConstants.SignType.MD5, MCH_KEY);
+ System.out.println("原始签名: " + result.getSign());
+ System.out.println("计算签名: " + verifySign);
+ Assert.assertEquals(verifySign, result.getSign(), "fastMode 签名应该匹配");
+ } finally {
+ XmlConfig.fastMode = false;
+ }
+ }
+
+ private static String createSign(Map params, String signType, String signKey) {
+ StringBuilder toSign = new StringBuilder();
+ for (String key : new TreeMap<>(params).keySet()) {
+ String value = params.get(key);
+ if (value != null && !value.isEmpty() && !NO_SIGN_PARAMS.contains(key)) {
+ toSign.append(key).append("=").append(value).append("&");
+ }
+ }
+ toSign.append("key=").append(signKey);
+ return DigestUtils.md5Hex(toSign.toString()).toUpperCase();
+ }
+}
diff --git a/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/bean/notify/WxPayOrderNotifyUnknownFieldTest.java b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/bean/notify/WxPayOrderNotifyUnknownFieldTest.java
new file mode 100644
index 0000000000..dfbf99d3b9
--- /dev/null
+++ b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/bean/notify/WxPayOrderNotifyUnknownFieldTest.java
@@ -0,0 +1,96 @@
+package com.github.binarywang.wxpay.bean.notify;
+
+import com.github.binarywang.wxpay.bean.result.BaseWxPayResult;
+import com.github.binarywang.wxpay.constant.WxPayConstants;
+import com.github.binarywang.wxpay.util.SignUtils;
+import com.github.binarywang.wxpay.util.XmlConfig;
+import org.apache.commons.codec.digest.DigestUtils;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import java.util.*;
+
+/**
+ * 测试当 XML 包含未知字段时,签名验证是否正常
+ */
+public class WxPayOrderNotifyUnknownFieldTest {
+
+ private static final String MCH_KEY = "testmchkey1234567890123456789012";
+ private static final List NO_SIGN_PARAMS = Arrays.asList("sign", "key", "xmlString", "xmlDoc", "couponList");
+
+ @Test
+ public void testSignatureWithUnknownField() throws Exception {
+ // 创建一个测试用的 XML,包含一个未知字段 (未在 WxPayOrderNotifyResult 中定义)
+ Map params = new LinkedHashMap<>();
+ params.put("appid", "wx58ff40508696691f");
+ params.put("bank_type", "ICBC_DEBIT");
+ params.put("cash_fee", "1");
+ params.put("fee_type", "CNY");
+ params.put("is_subscribe", "N");
+ params.put("mch_id", "1545462911");
+ params.put("nonce_str", "1761723102373");
+ params.put("openid", "o1gdd16CZCi6yYvkn6j9EB_1TObM");
+ params.put("out_trade_no", "20251029153140");
+ params.put("result_code", "SUCCESS");
+ params.put("return_code", "SUCCESS");
+ params.put("time_end", "20251029153852");
+ params.put("total_fee", "1");
+ params.put("trade_type", "JSAPI");
+ params.put("transaction_id", "4200002882220251029816273963B");
+ // 添加一个未知字段
+ params.put("unknown_field", "unknown_value");
+
+ // 计算正确的签名 (包含未知字段)
+ String correctSign = createSign(params, WxPayConstants.SignType.MD5, MCH_KEY);
+ params.put("sign", correctSign);
+
+ // 创建 XML
+ StringBuilder xmlBuilder = new StringBuilder("");
+ for (Map.Entry entry : params.entrySet()) {
+ xmlBuilder.append("<").append(entry.getKey()).append(">")
+ .append(entry.getValue())
+ .append("").append(entry.getKey()).append(">");
+ }
+ xmlBuilder.append("");
+ String xml = xmlBuilder.toString();
+
+ System.out.println("测试 XML (包含未知字段 unknown_field):");
+ System.out.println(xml);
+ System.out.println("正确的签名 (包含未知字段计算): " + correctSign);
+
+ // 解析 XML
+ WxPayOrderNotifyResult result = WxPayOrderNotifyResult.fromXML(xml);
+ Map beanMap = result.toMap();
+
+ System.out.println("\ntoMap() 结果:");
+ TreeMap sortedMap = new TreeMap<>(beanMap);
+ for (Map.Entry entry : sortedMap.entrySet()) {
+ System.out.println(" " + entry.getKey() + " = " + entry.getValue());
+ }
+
+ // 检查 unknown_field 是否存在
+ boolean hasUnknownField = beanMap.containsKey("unknown_field");
+ System.out.println("\ntoMap() 是否包含 unknown_field: " + hasUnknownField);
+
+ // 验证签名
+ String verifySign = createSign(beanMap, WxPayConstants.SignType.MD5, MCH_KEY);
+ System.out.println("原始签名: " + result.getSign());
+ System.out.println("计算签名: " + verifySign);
+
+ // 这个测试应该会失败,因为 toMap() 丢失了 unknown_field
+ Assert.assertTrue(hasUnknownField, "toMap() 应该包含 unknown_field");
+ Assert.assertEquals(verifySign, result.getSign(), "签名应该匹配");
+ }
+
+ private static String createSign(Map params, String signType, String signKey) {
+ StringBuilder toSign = new StringBuilder();
+ for (String key : new TreeMap<>(params).keySet()) {
+ String value = params.get(key);
+ if (value != null && !value.isEmpty() && !NO_SIGN_PARAMS.contains(key)) {
+ toSign.append(key).append("=").append(value).append("&");
+ }
+ }
+ toSign.append("key=").append(signKey);
+ return DigestUtils.md5Hex(toSign.toString()).toUpperCase();
+ }
+}
From 640e771ae592ecda7b140a54ab3c3c9e43376b31 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 28 Nov 2025 02:16:26 +0000
Subject: [PATCH 3/4] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=20V2=20=E6=94=AF?=
=?UTF-8?q?=E4=BB=98=E5=9B=9E=E8=B0=83=E7=AD=BE=E5=90=8D=E9=AA=8C=E8=AF=81?=
=?UTF-8?q?=E5=A4=B1=E8=B4=A5=E7=9A=84=E9=97=AE=E9=A2=98?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
问题:当微信支付 V2 回调 XML 包含未在 WxPayOrderNotifyResult 中定义的字段时,
toMap() 方法使用 SignUtils.xmlBean2Map(this) 会丢失这些字段,导致签名验证失败。
解决方案:修改 WxPayOrderNotifyResult.toMap() 方法,使用父类的 toMap() 方法
直接从原始 XML 解析所有字段,确保包含所有字段用于签名验证。
Co-authored-by: binarywang <1343140+binarywang@users.noreply.github.com>
---
.../bean/notify/WxPayOrderNotifyResult.java | 4 +-
...xPayOrderNotifyResultToMapCompareTest.java | 105 ---------------
.../WxPayOrderNotifyResultToMapTest.java | 91 -------------
.../notify/WxPayOrderNotifySignatureTest.java | 120 ------------------
.../WxPayOrderNotifyUnknownFieldTest.java | 18 ++-
5 files changed, 16 insertions(+), 322 deletions(-)
delete mode 100644 weixin-java-pay/src/test/java/com/github/binarywang/wxpay/bean/notify/WxPayOrderNotifyResultToMapCompareTest.java
delete mode 100644 weixin-java-pay/src/test/java/com/github/binarywang/wxpay/bean/notify/WxPayOrderNotifyResultToMapTest.java
delete mode 100644 weixin-java-pay/src/test/java/com/github/binarywang/wxpay/bean/notify/WxPayOrderNotifySignatureTest.java
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/notify/WxPayOrderNotifyResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/notify/WxPayOrderNotifyResult.java
index 27e8c1e1ec..8f16e5d4e4 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/notify/WxPayOrderNotifyResult.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/notify/WxPayOrderNotifyResult.java
@@ -342,7 +342,9 @@ public static WxPayOrderNotifyResult fromXML(String xmlString) {
@Override
public Map toMap() {
- Map resultMap = SignUtils.xmlBean2Map(this);
+ // 使用父类的 toMap() 方法,直接从原始 XML 解析所有字段,
+ // 确保包含未在 Java Bean 中定义的字段,避免签名验证失败
+ Map resultMap = super.toMap();
if (this.getCouponCount() != null && this.getCouponCount() > 0) {
for (int i = 0; i < this.getCouponCount(); i++) {
WxPayOrderNotifyCoupon coupon = couponList.get(i);
diff --git a/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/bean/notify/WxPayOrderNotifyResultToMapCompareTest.java b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/bean/notify/WxPayOrderNotifyResultToMapCompareTest.java
deleted file mode 100644
index 7f3ffe0315..0000000000
--- a/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/bean/notify/WxPayOrderNotifyResultToMapCompareTest.java
+++ /dev/null
@@ -1,105 +0,0 @@
-package com.github.binarywang.wxpay.bean.notify;
-
-import com.github.binarywang.wxpay.bean.result.BaseWxPayResult;
-import com.github.binarywang.wxpay.util.SignUtils;
-import com.github.binarywang.wxpay.util.XmlConfig;
-import org.testng.Assert;
-import org.testng.annotations.Test;
-
-import java.io.ByteArrayInputStream;
-import java.lang.reflect.Method;
-import java.nio.charset.StandardCharsets;
-import java.util.*;
-import javax.xml.parsers.DocumentBuilderFactory;
-import javax.xml.xpath.XPathConstants;
-import javax.xml.xpath.XPathFactory;
-import org.w3c.dom.Document;
-import org.w3c.dom.NodeList;
-
-/**
- * 比较两种 toMap() 方法实现的差异
- */
-public class WxPayOrderNotifyResultToMapCompareTest {
-
- @Test
- public void testCompareToMapMethods() throws Exception {
- // 从 issue 的截图中提取的 XML 数据
- String xml = "" +
- "wx58ff40508696691f" +
- "ICBC_DEBIT" +
- "1" +
- "CNY" +
- "N" +
- "1545462911" +
- "1761723102373" +
- "o1gdd16CZCi6yYvkn6j9EB_1TObM" +
- "20251029153140" +
- "SUCCESS" +
- "SUCCESS" +
- "03F5C68CA8F2E30855077FA3FC21EBEA" +
- "20251029153852" +
- "1" +
- "JSAPI" +
- "4200002882220251029816273963B" +
- "";
-
- WxPayOrderNotifyResult result = WxPayOrderNotifyResult.fromXML(xml);
-
- // WxPayOrderNotifyResult 重写的 toMap(),使用 SignUtils.xmlBean2Map(this)
- Map beanMap = result.toMap();
-
- // BaseWxPayResult.toMap() 的方式,直接从 XML 解析
- Map xmlMap = parseXmlToMap(xml);
-
- System.out.println("=== WxPayOrderNotifyResult.toMap() (SignUtils.xmlBean2Map) ===");
- printSignString(beanMap);
-
- System.out.println("\n=== 直接从 XML 解析的 Map ===");
- printSignString(xmlMap);
-
- // 找出差异
- System.out.println("\n=== 差异 ===");
- Set allKeys = new TreeSet<>();
- allKeys.addAll(beanMap.keySet());
- allKeys.addAll(xmlMap.keySet());
-
- for (String key : allKeys) {
- String beanValue = beanMap.get(key);
- String xmlValue = xmlMap.get(key);
- if (!Objects.equals(beanValue, xmlValue)) {
- System.out.println(key + ": beanMap=" + beanValue + ", xmlMap=" + xmlValue);
- }
- }
- }
-
- private static Map parseXmlToMap(String xml) throws Exception {
- Map result = new HashMap<>();
- DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
- factory.setExpandEntityReferences(false);
- factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
- Document doc = factory.newDocumentBuilder().parse(new ByteArrayInputStream(xml.getBytes(StandardCharsets.UTF_8)));
-
- NodeList list = (NodeList) XPathFactory.newInstance().newXPath()
- .compile("/xml/*")
- .evaluate(doc, XPathConstants.NODESET);
-
- int len = list.getLength();
- for (int i = 0; i < len; i++) {
- result.put(list.item(i).getNodeName(), list.item(i).getTextContent());
- }
- return result;
- }
-
- private static void printSignString(Map params) {
- List noSignParams = Arrays.asList("sign", "key", "xmlString", "xmlDoc", "couponList");
- StringBuilder sb = new StringBuilder();
- for (String key : new TreeMap<>(params).keySet()) {
- String value = params.get(key);
- if (value != null && !value.isEmpty() && !noSignParams.contains(key)) {
- sb.append(key).append("=").append(value).append("&");
- }
- }
- sb.append("key=YOUR_MCH_KEY");
- System.out.println(sb.toString());
- }
-}
diff --git a/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/bean/notify/WxPayOrderNotifyResultToMapTest.java b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/bean/notify/WxPayOrderNotifyResultToMapTest.java
deleted file mode 100644
index f26b8c927b..0000000000
--- a/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/bean/notify/WxPayOrderNotifyResultToMapTest.java
+++ /dev/null
@@ -1,91 +0,0 @@
-package com.github.binarywang.wxpay.bean.notify;
-
-import com.github.binarywang.wxpay.bean.result.BaseWxPayResult;
-import com.github.binarywang.wxpay.util.XmlConfig;
-import org.testng.Assert;
-import org.testng.annotations.Test;
-
-import java.util.Map;
-
-/**
- * 测试 WxPayOrderNotifyResult.toMap() 方法是否正确包含所有 XML 字段
- */
-public class WxPayOrderNotifyResultToMapTest {
-
- @Test
- public void testToMapContainsAllFields() {
- // 从 issue 的截图中提取的 XML 数据
- String xml = "" +
- "wx58ff40508696691f" +
- "ICBC_DEBIT" +
- "1" +
- "CNY" +
- "N" +
- "1545462911" +
- "1761723102373" +
- "o1gdd16CZCi6yYvkn6j9EB_1TObM" +
- "20251029153140" +
- "SUCCESS" +
- "SUCCESS" +
- "03F5C68CA8F2E30855077FA3FC21EBEA" +
- "20251029153852" +
- "1" +
- "JSAPI" +
- "4200002882220251029816273963B" +
- "";
-
- WxPayOrderNotifyResult result = WxPayOrderNotifyResult.fromXML(xml);
- Map map = result.toMap();
-
- System.out.println("toMap() 结果:");
- map.entrySet().stream()
- .sorted(Map.Entry.comparingByKey())
- .forEach(entry -> System.out.println(" " + entry.getKey() + " = " + entry.getValue()));
-
- // 验证关键字段是否存在
- Assert.assertTrue(map.containsKey("is_subscribe"), "toMap() 应该包含 is_subscribe 字段");
- Assert.assertEquals(map.get("is_subscribe"), "N", "is_subscribe 的值应该是 N");
-
- Assert.assertTrue(map.containsKey("bank_type"), "toMap() 应该包含 bank_type 字段");
- Assert.assertEquals(map.get("bank_type"), "ICBC_DEBIT", "bank_type 的值应该是 ICBC_DEBIT");
- }
-
- @Test
- public void testToMapWithFastMode() {
- String xml = "" +
- "wx58ff40508696691f" +
- "ICBC_DEBIT" +
- "1" +
- "CNY" +
- "N" +
- "1545462911" +
- "1761723102373" +
- "o1gdd16CZCi6yYvkn6j9EB_1TObM" +
- "20251029153140" +
- "SUCCESS" +
- "SUCCESS" +
- "03F5C68CA8F2E30855077FA3FC21EBEA" +
- "20251029153852" +
- "1" +
- "JSAPI" +
- "4200002882220251029816273963B" +
- "";
-
- XmlConfig.fastMode = true;
- try {
- WxPayOrderNotifyResult result = BaseWxPayResult.fromXML(xml, WxPayOrderNotifyResult.class);
- Map map = result.toMap();
-
- System.out.println("fastMode toMap() 结果:");
- map.entrySet().stream()
- .sorted(Map.Entry.comparingByKey())
- .forEach(entry -> System.out.println(" " + entry.getKey() + " = " + entry.getValue()));
-
- // 验证关键字段是否存在
- Assert.assertTrue(map.containsKey("is_subscribe"), "fastMode toMap() 应该包含 is_subscribe 字段");
- Assert.assertEquals(map.get("is_subscribe"), "N", "is_subscribe 的值应该是 N");
- } finally {
- XmlConfig.fastMode = false;
- }
- }
-}
diff --git a/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/bean/notify/WxPayOrderNotifySignatureTest.java b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/bean/notify/WxPayOrderNotifySignatureTest.java
deleted file mode 100644
index ca711f1b19..0000000000
--- a/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/bean/notify/WxPayOrderNotifySignatureTest.java
+++ /dev/null
@@ -1,120 +0,0 @@
-package com.github.binarywang.wxpay.bean.notify;
-
-import com.github.binarywang.wxpay.bean.result.BaseWxPayResult;
-import com.github.binarywang.wxpay.constant.WxPayConstants;
-import com.github.binarywang.wxpay.util.SignUtils;
-import com.github.binarywang.wxpay.util.XmlConfig;
-import org.apache.commons.codec.digest.DigestUtils;
-import org.testng.Assert;
-import org.testng.annotations.Test;
-
-import java.io.ByteArrayInputStream;
-import java.nio.charset.StandardCharsets;
-import java.util.*;
-import javax.xml.parsers.DocumentBuilderFactory;
-import javax.xml.xpath.XPathConstants;
-import javax.xml.xpath.XPathFactory;
-import org.w3c.dom.Document;
-import org.w3c.dom.NodeList;
-
-/**
- * 测试签名验证逻辑
- */
-public class WxPayOrderNotifySignatureTest {
-
- private static final String MCH_KEY = "testmchkey1234567890123456789012"; // 32位测试密钥
- private static final List NO_SIGN_PARAMS = Arrays.asList("sign", "key", "xmlString", "xmlDoc", "couponList");
-
- @Test
- public void testSignatureVerification() throws Exception {
- // 创建一个测试用的 XML,并手动计算正确的签名
- Map params = new LinkedHashMap<>();
- params.put("appid", "wx58ff40508696691f");
- params.put("bank_type", "ICBC_DEBIT");
- params.put("cash_fee", "1");
- params.put("fee_type", "CNY");
- params.put("is_subscribe", "N");
- params.put("mch_id", "1545462911");
- params.put("nonce_str", "1761723102373");
- params.put("openid", "o1gdd16CZCi6yYvkn6j9EB_1TObM");
- params.put("out_trade_no", "20251029153140");
- params.put("result_code", "SUCCESS");
- params.put("return_code", "SUCCESS");
- params.put("time_end", "20251029153852");
- params.put("total_fee", "1");
- params.put("trade_type", "JSAPI");
- params.put("transaction_id", "4200002882220251029816273963B");
-
- // 计算正确的签名
- String correctSign = createSign(params, WxPayConstants.SignType.MD5, MCH_KEY);
- params.put("sign", correctSign);
-
- // 创建 XML
- StringBuilder xmlBuilder = new StringBuilder("");
- for (Map.Entry entry : params.entrySet()) {
- xmlBuilder.append("<").append(entry.getKey()).append(">")
- .append(entry.getValue())
- .append("").append(entry.getKey()).append(">");
- }
- xmlBuilder.append("");
- String xml = xmlBuilder.toString();
-
- System.out.println("测试 XML:");
- System.out.println(xml);
- System.out.println("计算的签名: " + correctSign);
-
- // 测试普通模式
- System.out.println("\n=== 普通模式 ===");
- WxPayOrderNotifyResult result = WxPayOrderNotifyResult.fromXML(xml);
- Map beanMap = result.toMap();
-
- System.out.println("toMap() 结果 (用于签名验证):");
- TreeMap sortedMap = new TreeMap<>(beanMap);
- for (Map.Entry entry : sortedMap.entrySet()) {
- if (!NO_SIGN_PARAMS.contains(entry.getKey())) {
- System.out.println(" " + entry.getKey() + " = " + entry.getValue());
- }
- }
-
- // 验证签名
- String verifySign = createSign(beanMap, WxPayConstants.SignType.MD5, MCH_KEY);
- System.out.println("原始签名: " + result.getSign());
- System.out.println("计算签名: " + verifySign);
- Assert.assertEquals(verifySign, result.getSign(), "签名应该匹配");
-
- // 测试 fastMode
- System.out.println("\n=== Fast Mode ===");
- XmlConfig.fastMode = true;
- try {
- result = BaseWxPayResult.fromXML(xml, WxPayOrderNotifyResult.class);
- beanMap = result.toMap();
-
- System.out.println("fastMode toMap() 结果 (用于签名验证):");
- sortedMap = new TreeMap<>(beanMap);
- for (Map.Entry entry : sortedMap.entrySet()) {
- if (!NO_SIGN_PARAMS.contains(entry.getKey())) {
- System.out.println(" " + entry.getKey() + " = " + entry.getValue());
- }
- }
-
- verifySign = createSign(beanMap, WxPayConstants.SignType.MD5, MCH_KEY);
- System.out.println("原始签名: " + result.getSign());
- System.out.println("计算签名: " + verifySign);
- Assert.assertEquals(verifySign, result.getSign(), "fastMode 签名应该匹配");
- } finally {
- XmlConfig.fastMode = false;
- }
- }
-
- private static String createSign(Map params, String signType, String signKey) {
- StringBuilder toSign = new StringBuilder();
- for (String key : new TreeMap<>(params).keySet()) {
- String value = params.get(key);
- if (value != null && !value.isEmpty() && !NO_SIGN_PARAMS.contains(key)) {
- toSign.append(key).append("=").append(value).append("&");
- }
- }
- toSign.append("key=").append(signKey);
- return DigestUtils.md5Hex(toSign.toString()).toUpperCase();
- }
-}
diff --git a/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/bean/notify/WxPayOrderNotifyUnknownFieldTest.java b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/bean/notify/WxPayOrderNotifyUnknownFieldTest.java
index dfbf99d3b9..13a9b22301 100644
--- a/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/bean/notify/WxPayOrderNotifyUnknownFieldTest.java
+++ b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/bean/notify/WxPayOrderNotifyUnknownFieldTest.java
@@ -1,9 +1,6 @@
package com.github.binarywang.wxpay.bean.notify;
-import com.github.binarywang.wxpay.bean.result.BaseWxPayResult;
import com.github.binarywang.wxpay.constant.WxPayConstants;
-import com.github.binarywang.wxpay.util.SignUtils;
-import com.github.binarywang.wxpay.util.XmlConfig;
import org.apache.commons.codec.digest.DigestUtils;
import org.testng.Assert;
import org.testng.annotations.Test;
@@ -11,7 +8,18 @@
import java.util.*;
/**
- * 测试当 XML 包含未知字段时,签名验证是否正常
+ * 测试当微信支付回调 XML 包含未在 Java Bean 中定义的字段时,签名验证是否正常。
+ *
+ * 问题背景:当微信返回的 XML 包含某些未在 WxPayOrderNotifyResult 中定义的字段时,
+ * 这些字段会被微信服务器用于签名计算。如果 toMap() 方法丢失了这些字段,
+ * 则签名验证会失败,抛出 "参数格式校验错误!" 异常。
+ *
+ *
+ * 解决方案:修改 WxPayOrderNotifyResult.toMap() 方法,使用父类的 toMap() 方法
+ * 直接从原始 XML 解析所有字段,而不是使用 SignUtils.xmlBean2Map(this)。
+ *
+ *
+ * @see Issue链接
*/
public class WxPayOrderNotifyUnknownFieldTest {
@@ -77,7 +85,7 @@ public void testSignatureWithUnknownField() throws Exception {
System.out.println("原始签名: " + result.getSign());
System.out.println("计算签名: " + verifySign);
- // 这个测试应该会失败,因为 toMap() 丢失了 unknown_field
+ // 这个测试验证修复后 toMap() 能正确包含所有字段
Assert.assertTrue(hasUnknownField, "toMap() 应该包含 unknown_field");
Assert.assertEquals(verifySign, result.getSign(), "签名应该匹配");
}
From b7aecc6a4277e3b4bb01a18dba382d9fb02bb51f Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 28 Nov 2025 02:18:17 +0000
Subject: [PATCH 4/4] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E6=B5=8B=E8=AF=95?=
=?UTF-8?q?=E6=96=87=E4=BB=B6=E7=9A=84=20Issue=20=E9=93=BE=E6=8E=A5?=
=?UTF-8?q?=E4=B8=BA=E5=AE=9E=E9=99=85=20Issue=20=E7=BC=96=E5=8F=B7=20#375?=
=?UTF-8?q?0?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Co-authored-by: binarywang <1343140+binarywang@users.noreply.github.com>
---
.../wxpay/bean/notify/WxPayOrderNotifyUnknownFieldTest.java | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/bean/notify/WxPayOrderNotifyUnknownFieldTest.java b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/bean/notify/WxPayOrderNotifyUnknownFieldTest.java
index 13a9b22301..be35523ec4 100644
--- a/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/bean/notify/WxPayOrderNotifyUnknownFieldTest.java
+++ b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/bean/notify/WxPayOrderNotifyUnknownFieldTest.java
@@ -19,7 +19,7 @@
* 直接从原始 XML 解析所有字段,而不是使用 SignUtils.xmlBean2Map(this)。
*
*
- * @see Issue链接
+ * @see Issue #3750
*/
public class WxPayOrderNotifyUnknownFieldTest {