微信支付 V3 版回調通知的數據驗證流程:從原始輸入流讀取 XML、Dom4j 解析為有序 Map、按規範重新計算簽名並與微信傳來的 sign 比對。掌握此流程可有效防偽造回調、保障資金安全。
InputStream in = request.getInputStream();
ByteArrayOutputStream out = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len;
while ((len = in.read(buffer)) != -1) {
out.write(buffer, 0, len);
}
String xml = new String(out.toByteArray(), StandardCharsets.UTF_8);
// xml 即為微信回調的原始 XML 字符串
public static SortedMap<String, String> parseXml(String xml) throws DocumentException {
SortedMap<String, String> map = new TreeMap<>(); // 必須有序,簽名依賴鍵排序
Document doc = DocumentHelper.parseText(xml);
Element root = doc.getRootElement();
for (Iterator<Element> it = root.elementIterator(); it.hasNext(); ) {
Element e = it.next();
map.put(e.getName(), e.getText()); // 僅解析第一層元素
}
return map;
}
public static boolean verifySign(SortedMap<String, String> params, String apiKey) {
StringBuilder sb = new StringBuilder();
for (Map.Entry<String, String> entry : params.entrySet()) {
String k = entry.getKey();
String v = entry.getValue();
// 排除 sign、key 字段及空值
if (!"sign".equals(k) && !"key".equals(k) && v != null && !v.isEmpty()) {
sb.append(k).append('=').append(v).append('&');
}
}
sb.append("key=").append(apiKey); // 最後拼接商戶 API Key
String calculatedSign = MD5Util.MD5Encode(sb.toString(), "UTF-8").toUpperCase();
String wxSign = params.get("sign").toUpperCase();
return calculatedSign.equals(wxSign);
}
return_code 與 result_code 均為 SUCCESS,減少無效計算out_trade_no 檢查訂單是否已處理,防止重複回調導致多次發貨FAIL,讓微信重試<xml>
<return_code><![CDATA[SUCCESS]]></return_code>
<return_msg><![CDATA[OK]]></return_msg>
</xml>
此驗證流程為支付回調處理的基石,建議封裝為通用工具類,所有支付相關 Controller 復用。