290 lines
12 KiB
Java
290 lines
12 KiB
Java
|
|
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);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
}
|