AppID、MchID、API Key (v2/v3)、商戶證書 (apiclient_cert.p12)。public class WxPayMD5 {
public static String sign(String data, String key) {
try {
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] bytes = (data + "&key=" + key).getBytes(StandardCharsets.UTF_8);
byte[] digest = md.digest(bytes);
StringBuilder sb = new StringBuilder();
for (byte b : digest) sb.append(String.format("%02X", b));
return sb.toString();
} catch (Exception e) {
throw new RuntimeException("MD5 簽名失敗", e);
}
}
}
public class WxPayUtil {
public static String toXml(Map<String, String> params) {
// 1. 參數按 ASCII 排序
StringBuilder sb = new StringBuilder("<xml>");
params.entrySet().stream()
.sorted(Map.Entry.comparingByKey())
.forEach(e -> {
String v = e.getValue();
if (v.matches("\\d+")) sb.append("<").append(e.getKey()).append(">").append(v).append("</").append(e.getKey()).append(">");
else sb.append("<").append(e.getKey()).append("><![CDATA[").append(v).append("]]></").append(e.getKey()).append(">");
});
sb.append("</xml>");
return sb.toString();
}
public static Map<String, String> xmlToMap(String xml) {
// 簡易解析,生產建議用 dom4j 或 JAXB
Map<String, String> map = new HashMap<>();
// ... 略
return map;
}
}
@RestController
@RequestMapping("/pay/wx")
public class WxNativePayController {
@Value("${wx.appid}") String appId;
@Value("${wx.mchid}") String mchId;
@Value("${wx.key}") String apiKey;
@Value("${wx.notify-url}") String notifyUrl;
@Value("${wx.cert-path}") String certPath;
@PostMapping("/native")
public Result unifiedOrder(@RequestBody OrderReq req) {
try {
// 1. 組裝業務參數
Map<String, String> params = new HashMap<>();
params.put("appid", appId);
params.put("mch_id", mchId);
params.put("nonce_str", RandomStringUtils.randomAlphanumeric(32));
params.put("body", req.getSubject());
params.put("out_trade_no", req.getOrderNo());
params.put("total_fee", String.valueOf(req.getAmountCents())); // 單位:分
params.put("spbill_create_ip", "127.0.0.1"); // 實際應取客戶端 IP
params.put("notify_url", notifyUrl);
params.put("trade_type", "NATIVE");
// 2. 簽名
String sign = WxPayMD5.sign(WxPayUtil.buildSignString(params), apiKey);
params.put("sign", sign);
// 3. 發送 HTTPS 請求(雙向憑證)
String xmlReq = WxPayUtil.toXml(params);
String xmlResp = HttpClientUtil.postWithCert(
"https://api.mch.weixin.qq.com/pay/unifiedorder",
xmlReq, certPath, mchId
);
// 4. 解析回應
Map<String, String> resp = WxPayUtil.xmlToMap(xmlResp);
if ("SUCCESS".equals(resp.get("return_code")) && "SUCCESS".equals(resp.get("result_code"))) {
String codeUrl = resp.get("code_url"); // 此 URL 可直接生成二維碼
return Result.ok(codeUrl);
} else {
return Result.fail(resp.get("err_code_des"));
}
} catch (Exception e) {
log.error("統一下單失敗", e);
return Result.fail("系統異常");
}
}
}
元 * 100 取整。.p12 憑證建立 SSLContext。sign,防偽造。out_trade_no 或 transaction_id 判斷)。微信 Native 支付核心在於「統一下單 → 獲取 code_url → 前端生成二維碼 → 用戶掃碼 → 後台接收回調」。掌握參數組裝、簽名、雙向憑證請求三大塊,即可穩健接入。建議封裝成通用 SDK,業務層僅需呼叫 payService.nativePay(order)。