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 closeOrder(WxPayVo wxPayVo) { Map result = new HashMap<>(); String outTradeNo = wxPayVo.getOutTradeNo(); log.info("开始关单: {}",outTradeNo); // log.info("wxpayconfig:"+wxPayConfig.getAppId()); try { // 构建请求参数 Map 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 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 unifiedOrder(WxPayVo wxPayVo) throws Exception { // 构造统一下单请求参数 Map 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 resultMap = xmlToMap(responseXml); // 检查业务结果 if ("SUCCESS".equals(resultMap.get("return_code")) && "SUCCESS".equals(resultMap.get("result_code"))) { // 构造返回给前端的js-sdk参数 Map 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 paramMap) { try { return WXPayUtil.mapToXml(paramMap); } catch (Exception e) { // 如果WXPayUtil不可用,使用备用实现 return mapToXmlFallback(paramMap); } } /** * 备用的Map转XML实现 */ private String mapToXmlFallback(Map paramMap) { StringBuilder sb = new StringBuilder(); sb.append(""); for (Map.Entry entry : paramMap.entrySet()) { String key = entry.getKey(); String value = entry.getValue(); // 对值进行XML转义 String escapedValue = escapeXml(value); sb.append("<").append(key).append(">"); sb.append(""); sb.append(""); } sb.append(""); return sb.toString(); } /** * XML转义 */ private String escapeXml(String str) { if (str == null || str.isEmpty()) { return str; } return str.replace("]]>", "]]]]>"); } /** * 解析微信支付返回的XML */ public Map xmlToMap(String xmlStr) throws Exception { return WXPayUtil.xmlToMap(xmlStr); } }