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<String, String> 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<String, String> 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<String, String> 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<String, String> queryOrder(String orderNo) {
|
Map<String, String> 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<String, String> data) {
|
if (data == null || !data.containsKey("sign")) {
|
return false;
|
}
|
String sign = data.get("sign");
|
Map<String, String> copy = new TreeMap<>(data);
|
copy.remove("sign");
|
return sign.equals(sign(copy));
|
}
|
|
public Map<String, String> parseNotifyXml(String xml) {
|
return xmlToMap(xml);
|
}
|
|
private Map<String, String> buildJsapiPayParams(String prepayId) {
|
Map<String, String> 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<String, String> 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<String, String> params) {
|
StringBuilder sb = new StringBuilder();
|
for (Map.Entry<String, String> 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<String, String> params) {
|
StringBuilder sb = new StringBuilder("<xml>");
|
for (Map.Entry<String, String> e : params.entrySet()) {
|
sb.append("<").append(e.getKey()).append("><![CDATA[")
|
.append(e.getValue()).append("]]></").append(e.getKey()).append(">");
|
}
|
sb.append("</xml>");
|
return sb.toString();
|
}
|
|
@SuppressWarnings("unchecked")
|
private Map<String, String> xmlToMap(String xml) {
|
Map<String, String> map = new HashMap<>();
|
if (StringUtils.isBlank(xml)) {
|
return map;
|
}
|
Map<String, Object> raw = XmlUtil.xmlToMap(xml);
|
for (Map.Entry<String, Object> e : raw.entrySet()) {
|
map.put(e.getKey(), e.getValue() == null ? null : String.valueOf(e.getValue()));
|
}
|
return map;
|
}
|
}
|