Files
gzh-server/src/main/java/com/guahao/common/util/WxPayUtil.java
2026-01-07 10:36:02 +08:00

290 lines
12 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package com.guahao.common.util;
import cn.hutool.json.JSONUtil;
import com.github.wxpay.sdk.WXPayUtil;
import com.guahao.common.Exception.LogicException;
import com.guahao.common.config.WxPayConfig;
import com.guahao.h5.reserve.service.Reserve8Service;
import com.guahao.common.util.WxPayDUtil;
import com.guahao.h5.reserve.vo.WxPayVo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.beans.factory.annotation.Autowired;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.MessageDigest;
import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;
@Component
public class WxPayUtil {
private Logger log = LoggerFactory.getLogger(WxPayUtil.class);
@Autowired
private WxPayConfig wxPayConfig;
/**
* 关闭微信支付订单
* @param wxPayVo 支付参数对象
* @return 关单结果
*/
public Map<String, String> closeOrder(WxPayVo wxPayVo) {
Map<String, String> result = new HashMap<>();
String outTradeNo = wxPayVo.getOutTradeNo();
log.info("开始关单: {}",outTradeNo);
// log.info("wxpayconfig:"+wxPayConfig.getAppId());
try {
// 构建请求参数
Map<String, String> data = new HashMap<>();
data.put("appid", wxPayConfig.getAppId());
data.put("mch_id", wxPayConfig.getMchId());
data.put("out_trade_no", outTradeNo);
data.put("nonce_str", WXPayUtil.generateNonceStr());
log.info("请求参数: {}", data);
// 生成签名
String sign = WXPayUtil.generateSignature(data, wxPayConfig.getApiKey());
data.put("sign", sign);
log.info("请求参数: {}", data);
// 转换为XML
String xmlParam = mapToXml(data);
log.info("关单请求XML: {}", xmlParam);
URL url = new URL("https://api.mch.weixin.qq.com/pay/closeorder");
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
// 设置请求参数
connection.setRequestMethod("POST");
connection.setRequestProperty("Content-Type", "text/xml; charset=utf-8");
connection.setRequestProperty("User-Agent", "Mozilla/5.0 (compatible; WeChat Pay SDK)");
connection.setDoOutput(true);
connection.setDoInput(true);
connection.setConnectTimeout(10000);
connection.setReadTimeout(30000);
// 写入请求体
try (OutputStream os = connection.getOutputStream()) {
os.write(xmlParam.getBytes("UTF-8"));
}
int code = connection.getResponseCode();
log.info("关单HTTP状态码: {}", code);
// 读取响应
StringBuilder response = new StringBuilder();
try (InputStream is = code == 200 ? connection.getInputStream() : connection.getErrorStream();
BufferedReader br = new BufferedReader(new InputStreamReader(is, "UTF-8"))) {
String line;
while ((line = br.readLine()) != null) {
response.append(line);
}
}
String responseXml = response.toString();
log.info("关单响应XML: {}", responseXml);
// 解析响应
Map<String, String> resultMap = xmlToMap(responseXml);
// ✅ 关键:必须同时判断 return_code 和 result_code
if ("SUCCESS".equals(resultMap.get("return_code"))
&& "SUCCESS".equals(resultMap.get("result_code"))) {
log.info("✅ 微信关单成功: {}", outTradeNo);
return resultMap; // 返回 SUCCESS
} else {
// 业务失败
String errorMsg = resultMap.get("return_msg");
String errCodeDes = resultMap.get("err_code_des");
log.error(" 微信关单失败: {} | {} | {}", outTradeNo, errorMsg, errCodeDes);
result.put("return_code", "FAIL");
result.put("return_msg", "关单失败: " + errorMsg + ", 详情: " + errCodeDes);
return result;
}
} catch (Exception e) {
// 正确写法:打印异常类名 + 消息 + 堆栈
String errorMsg = e.getMessage();
log.error(" 调用微信关单接口异常 [订单号: {}] [异常类型: {}] [异常消息: {}]",
outTradeNo,
e.getClass().getSimpleName(),
errorMsg == null ? "null" : errorMsg,
e); // e 放最后,用于输出堆栈
result.put("return_code", "FAIL");
result.put("return_msg", "网络异常: " + (errorMsg == null ? e.getClass().getSimpleName() : errorMsg));
}
return result;
}
/**
* 微信支付统一下单
*
* @param wxPayVo 支付参数
* @return 微信支付响应结果
*/
public Map<String, String> unifiedOrder(WxPayVo wxPayVo) throws Exception {
// 构造统一下单请求参数
Map<String, String> paramMap = new HashMap<>();
paramMap.put("appid", wxPayConfig.getAppId());
paramMap.put("mch_id", wxPayConfig.getMchId());
paramMap.put("nonce_str", wxPayVo.getNonceStr());
paramMap.put("body", wxPayVo.getBody());
paramMap.put("out_trade_no", wxPayVo.getOutTradeNo());
paramMap.put("total_fee", wxPayVo.getTotalFee().toString());
paramMap.put("spbill_create_ip", wxPayVo.getSpbillCreateIp());
paramMap.put("notify_url", "https://nxwj.btlsoln.com/nxgzh/pay/wxpay/notify");
paramMap.put("trade_type", "JSAPI");
paramMap.put("openid", wxPayVo.getOpenid());
// log.info("进入1generateSignature");
log.info("签名参数: " + JSONUtil.toJsonStr(paramMap));
// 生成签名
String sign = WxPayDUtil.generateSignature(paramMap, wxPayConfig.getApiKey());
paramMap.put("sign", sign);
// 转换为XML格式
String xmlParam = mapToXml(paramMap);
log.info("xmlParam: " + xmlParam);
HttpURLConnection connection = null;
BufferedReader reader = null;
try {
// 1. 创建 URL 对象
URL url = new URL("https://api.mch.weixin.qq.com/pay/unifiedorder");
connection = (HttpURLConnection) url.openConnection();
// 2. 设置请求参数
connection.setRequestMethod("POST");
connection.setRequestProperty("Content-Type", "text/xml; charset=utf-8");
connection.setRequestProperty("User-Agent", "Mozilla/5.0 (compatible; WeChat Pay SDK)");
connection.setDoOutput(true);
connection.setDoInput(true);
connection.setConnectTimeout(10000);
connection.setReadTimeout(30000);
// 3. 写入请求体XML
try (OutputStream os = connection.getOutputStream()) {
byte[] input = xmlParam.getBytes("UTF-8");
os.write(input, 0, input.length);
}
// 4. 获取响应码
int code = connection.getResponseCode();
log.info("code:" + code);
if (code == 200) {
// 5. 读取响应内容
StringBuilder response = new StringBuilder();
try (InputStream is = connection.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(is, "UTF-8"))) {
String line;
while ((line = br.readLine()) != null) {
response.append(line);
}
}
String responseXml = response.toString();
log.info("微信支付响应XML: " + responseXml);
// 解析返回结果
Map<String, String> resultMap = xmlToMap(responseXml);
// 检查业务结果
if ("SUCCESS".equals(resultMap.get("return_code")) &&
"SUCCESS".equals(resultMap.get("result_code"))) {
// 构造返回给前端的js-sdk参数
Map<String, String> result = new HashMap<>();
result.put("appId", wxPayConfig.getAppId());
result.put("timeStamp", DateUtils.getTimesStamp());
result.put("nonceStr", wxPayVo.getNonceStr());
result.put("package", "prepay_id=" + resultMap.get("prepay_id"));
result.put("signType", "MD5");
// log.info("进入2generateSignature");
result.put("paySign", WxPayDUtil.generateSignature(result, wxPayConfig.getApiKey()));
result.put("outTradeNo", wxPayVo.getOutTradeNo());
return result; // 包含prepay_id等信息
} else {
String returnMsg = resultMap.get("return_msg");
String errCodeDes = resultMap.get("err_code_des");
throw new LogicException("微信支付统一下单失败:" + returnMsg + (errCodeDes != null ? ", 详细: " + errCodeDes : ""));
}
} else {
// 读取错误流(如 4xx/5xx
try (InputStream es = connection.getErrorStream();
BufferedReader br = new BufferedReader(new InputStreamReader(es, "UTF-8"))) {
StringBuilder errorMsg = new StringBuilder();
String line;
while ((line = br.readLine()) != null) {
errorMsg.append(line);
}
log.error("HTTP错误响应: " + errorMsg);
throw new LogicException("微信支付统一下单失败HTTP状态码 " + code + ", 响应: " + errorMsg);
}
}
} catch (IOException e) {
log.error("调用微信统一下单接口异常", e);
throw new RuntimeException("网络请求失败: " + e.getMessage(), e);
} finally {
if (connection != null) {
connection.disconnect(); // 释放连接
}
}
}
/**
* Map转XML
*/
public String mapToXml(Map<String, String> paramMap) {
try {
return WXPayUtil.mapToXml(paramMap);
} catch (Exception e) {
// 如果WXPayUtil不可用使用备用实现
return mapToXmlFallback(paramMap);
}
}
/**
* 备用的Map转XML实现
*/
private String mapToXmlFallback(Map<String, String> paramMap) {
StringBuilder sb = new StringBuilder();
sb.append("<xml>");
for (Map.Entry<String, String> entry : paramMap.entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
// 对值进行XML转义
String escapedValue = escapeXml(value);
sb.append("<").append(key).append(">");
sb.append("<![CDATA[").append(escapedValue).append("]]>");
sb.append("</").append(key).append(">");
}
sb.append("</xml>");
return sb.toString();
}
/**
* XML转义
*/
private String escapeXml(String str) {
if (str == null || str.isEmpty()) {
return str;
}
return str.replace("]]>", "]]]]><![CDATA[>");
}
/**
* 解析微信支付返回的XML
*/
public Map<String, String> xmlToMap(String xmlStr) throws Exception {
return WXPayUtil.xmlToMap(xmlStr);
}
}