package com.doumee.core.wx; import cn.hutool.core.util.RandomUtil; import cn.hutool.core.util.XmlUtil; import cn.hutool.crypto.digest.DigestUtil; import cn.hutool.http.HttpUtil; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import java.math.BigDecimal; import java.math.RoundingMode; import java.util.*; /** * 微信公众号 JSAPI 支付(V2) */ @Component @Slf4j public class WxJsapiPayUtil { private static final String UNIFIED_ORDER_URL = "https://api.mch.weixin.qq.com/pay/unifiedorder"; private static final String ORDER_QUERY_URL = "https://api.mch.weixin.qq.com/pay/orderquery"; @Value("${wx.pay.appId:}") private String appId; @Value("${wx.pay.mchId:}") private String mchId; @Value("${wx.pay.mchKey:}") private String mchKey; @Value("${wx.pay.notifyUrl:}") private String notifyUrl; public Map createJsapiOrder(String orderNo, String openid, String body, BigDecimal amountYuan, String clientIp) { int totalFee = amountYuan.multiply(BigDecimal.valueOf(100)).setScale(0, RoundingMode.HALF_UP).intValue(); Map params = new TreeMap<>(); params.put("appid", appId); params.put("mch_id", mchId); params.put("nonce_str", RandomUtil.randomString(32)); params.put("body", StringUtils.defaultIfBlank(body, "商户缴费")); params.put("out_trade_no", orderNo); params.put("total_fee", String.valueOf(totalFee)); params.put("spbill_create_ip", StringUtils.defaultIfBlank(clientIp, "127.0.0.1")); params.put("notify_url", notifyUrl); params.put("trade_type", "JSAPI"); params.put("openid", openid); params.put("sign", sign(params)); String xml = mapToXml(params); String respXml = HttpUtil.post(UNIFIED_ORDER_URL, xml); Map resp = xmlToMap(respXml); if (!"SUCCESS".equals(resp.get("return_code")) || !"SUCCESS".equals(resp.get("result_code"))) { throw new RuntimeException(StringUtils.defaultIfBlank(resp.get("err_code_des"), resp.get("return_msg"))); } String prepayId = resp.get("prepay_id"); return buildJsapiPayParams(prepayId); } public Map queryOrder(String orderNo) { Map params = new TreeMap<>(); params.put("appid", appId); params.put("mch_id", mchId); params.put("out_trade_no", orderNo); params.put("nonce_str", RandomUtil.randomString(32)); params.put("sign", sign(params)); String respXml = HttpUtil.post(ORDER_QUERY_URL, mapToXml(params)); return xmlToMap(respXml); } public boolean verifyNotifySign(Map data) { if (data == null || !data.containsKey("sign")) { return false; } String sign = data.get("sign"); Map copy = new TreeMap<>(data); copy.remove("sign"); return sign.equals(sign(copy)); } public Map parseNotifyXml(String xml) { return xmlToMap(xml); } private Map buildJsapiPayParams(String prepayId) { Map pay = new LinkedHashMap<>(); pay.put("appId", appId); pay.put("timeStamp", String.valueOf(System.currentTimeMillis() / 1000)); pay.put("nonceStr", RandomUtil.randomString(32)); pay.put("package", "prepay_id=" + prepayId); pay.put("signType", "MD5"); Map signMap = new TreeMap<>(); signMap.put("appId", pay.get("appId")); signMap.put("timeStamp", pay.get("timeStamp")); signMap.put("nonceStr", pay.get("nonceStr")); signMap.put("package", pay.get("package")); signMap.put("signType", pay.get("signType")); pay.put("paySign", sign(signMap)); return pay; } private String sign(Map params) { StringBuilder sb = new StringBuilder(); for (Map.Entry e : params.entrySet()) { if (StringUtils.isNotBlank(e.getValue())) { sb.append(e.getKey()).append("=").append(e.getValue()).append("&"); } } sb.append("key=").append(mchKey); return DigestUtil.md5Hex(sb.toString()).toUpperCase(); } private String mapToXml(Map params) { StringBuilder sb = new StringBuilder(""); for (Map.Entry e : params.entrySet()) { sb.append("<").append(e.getKey()).append(">"); } sb.append(""); return sb.toString(); } @SuppressWarnings("unchecked") private Map xmlToMap(String xml) { Map map = new HashMap<>(); if (StringUtils.isBlank(xml)) { return map; } Map raw = XmlUtil.xmlToMap(xml); for (Map.Entry e : raw.entrySet()) { map.put(e.getKey(), e.getValue() == null ? null : String.valueOf(e.getValue())); } return map; } }