package com.ruoyi.im.util; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import java.io.BufferedReader; import java.io.InputStreamReader; import java.io.UnsupportedEncodingException; import java.math.BigDecimal; import java.net.HttpURLConnection; import java.net.URL; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.*; @Component public class PayService { @Value("${pay.mch-id}") private String mchId; @Value("${pay.key}") private String key; @Value("${pay.channelCode}") private String channelCode; @Value("${pay.base-url}") private String baseUrl; /** * 创建支付订单 - 适配新支付系统(修正版) */ public String createOrder(BigDecimal amount, String orderNo,String payProductId,String callBackUrl) { try { Map params = new HashMap<>(); // 必需参数 params.put("userCode", mchId); // 商户号 params.put("channelCode", payProductId); // 通道码 params.put("orderId", orderNo); // 订单号 params.put("orderMoney", amount); // 金额(格式化金额) params.put("callbackUrl", callBackUrl+"/userPolicy/notify"); // 回调地址 // 可选参数(根据业务需要添加) // params.put("returnUrl", returnUrl); // params.put("clientIp", clientIp); // params.put("currency", currency); // params.put("gameId", gameId); // params.put("gameIp", gameIp); // 生成签名 String sign = generateNewSign(params); params.put("sign", sign); // 构建GET请求URL String url = buildGetUrl(baseUrl, params); // 发送GET请求,设置正确的Content-Type和可能的token return sendGet(url); } catch (Exception e) { return "{\"retCode\":\"FAIL\",\"retMsg\":\"" + e.getMessage() + "\"}"; } } /** * 发送GET请求 - 修正版 */ private String sendGet(String url) { try { HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection(); connection.setRequestMethod("GET"); connection.setConnectTimeout(10000); // 10秒连接超时 connection.setReadTimeout(10000); // 10秒读取超时 // 设置请求头 connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); connection.setRequestProperty("Accept", "*/*"); int responseCode = connection.getResponseCode(); if (responseCode == HttpURLConnection.HTTP_OK) { BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream())); String inputLine; StringBuilder response = new StringBuilder(); while ((inputLine = in.readLine()) != null) { response.append(inputLine); } in.close(); return response.toString(); } else { return "{\"retCode\":\"FAIL\",\"retMsg\":\"HTTP error code: " + responseCode + "\"}"; } } catch (Exception e) { return "{\"retCode\":\"FAIL\",\"retMsg\":\"" + e.getMessage() + "\"}"; } } /** * 新支付系统签名生成 - 最终修正版 */ private String generateNewSign(Map params) { try { // 1. 参数名ASCII码从小到大排序(字典序) List keys = new ArrayList<>(params.keySet()); Collections.sort(keys); // 2. 如果参数的值为空不参与签名 StringBuilder stringA = new StringBuilder(); for (String key : keys) { Object value = params.get(key); // 值为空不参与签名,且sign参数不参与签名 if (value != null && !"".equals(value.toString().trim()) && !"sign".equals(key)) { if (stringA.length() > 0) { stringA.append("&"); } stringA.append(key).append("=").append(value.toString()); } } // 3. 在stringA最后拼接上key(根据示例,应该是直接拼接key值,没有key=前缀) // 示例:callbackUrl=http://baidu.com&channelCode=0000&orderId=P12312321123&orderMoney=2.01&userCode=10008&key=8c1511c3413106ac529f6ec9ad095c08 // 注意:示例中实际上是有"&key="的,但文档描述是"在stringA最后拼接上key" // 按照示例,应该是:stringA + "&key=" + key String stringSignTemp = stringA.toString() + "&key=" + key; // 4. 对stringSignTemp进行MD5运算,再将得到的字符串所有字符转换为小写 String md5Result = md5(stringSignTemp); return md5Result.toLowerCase(); } catch (Exception e) { throw new RuntimeException("生成签名失败: " + e.getMessage()); } } /** * 金额格式化(确保金额格式正确) */ private String formatAmount(BigDecimal amount) { // 根据接口描述:金额,单位元,小数点的0可随意设置,比如100.10或100.00或100都可以 // 这里我们统一格式化为2位小数 return String.format("%.2f", amount); } /** * 构建GET请求URL */ private String buildGetUrl(String baseUrl, Map params) { try { StringBuilder urlBuilder = new StringBuilder(baseUrl); boolean firstParam = true; for (Map.Entry entry : params.entrySet()) { if (entry.getValue() != null && !"".equals(entry.getValue().toString().trim())) { if (firstParam) { urlBuilder.append("?"); firstParam = false; } else { urlBuilder.append("&"); } urlBuilder.append(URLEncoder.encode(entry.getKey(), "UTF-8")) .append("=") .append(URLEncoder.encode(entry.getValue().toString(), "UTF-8")); } } return urlBuilder.toString(); } catch (UnsupportedEncodingException e) { throw new RuntimeException("构建URL失败: " + e.getMessage()); } } /** * MD5加密工具方法 */ private String md5(String input) { try { MessageDigest md = MessageDigest.getInstance("MD5"); byte[] messageDigest = md.digest(input.getBytes(StandardCharsets.UTF_8)); StringBuilder hexString = new StringBuilder(); for (byte b : messageDigest) { String hex = Integer.toHexString(0xff & b); if (hex.length() == 1) { hexString.append('0'); } hexString.append(hex); } return hexString.toString(); } catch (NoSuchAlgorithmException e) { throw new RuntimeException("MD5加密失败", e); } } /** * 验证回调签名 */ public boolean verifySign(Map params) { try { String receivedSign = params.get("sign"); Map signParams = new HashMap<>(params); String calculatedSign = generateSign(signParams); return calculatedSign.equals(receivedSign); } catch (Exception e) { return false; } } /** * 生成签名 */ private String generateSign(Map params) throws Exception { // 移除sign参数 Map signParams = new HashMap<>(params); signParams.remove("sign"); // 过滤空值并排序 List keys = new ArrayList<>(); for (Map.Entry entry : signParams.entrySet()) { if (entry.getValue() != null && !entry.getValue().toString().trim().isEmpty()) { keys.add(entry.getKey()); } } Collections.sort(keys); // 拼接字符串 StringBuilder sb = new StringBuilder(); for (int i = 0; i < keys.size(); i++) { String key = keys.get(i); String value = signParams.get(key).toString(); if (i > 0) sb.append("&"); sb.append(key).append("=").append(value); } // MD5加密 String stringSignTemp = sb.toString() + "&key=" + key; MessageDigest md = MessageDigest.getInstance("MD5"); byte[] array = md.digest(stringSignTemp.getBytes("UTF-8")); StringBuilder result = new StringBuilder(); for (byte item : array) { result.append(String.format("%02x", item)); } return result.toString().toUpperCase(); } /** * 发送POST请求 */ private String sendPost(String urlStr, Map params) throws Exception { // 构建参数字符串 StringBuilder postData = new StringBuilder(); for (Map.Entry param : params.entrySet()) { if (postData.length() != 0) postData.append('&'); postData.append(URLEncoder.encode(param.getKey(), "UTF-8")); postData.append('='); postData.append(URLEncoder.encode(String.valueOf(param.getValue()), "UTF-8")); } byte[] postDataBytes = postData.toString().getBytes("UTF-8"); URL url = new URL(urlStr); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setRequestMethod("POST"); conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); conn.setRequestProperty("Content-Length", String.valueOf(postDataBytes.length)); conn.setDoOutput(true); conn.getOutputStream().write(postDataBytes); // 读取响应 Scanner scanner = new Scanner(conn.getInputStream(), "UTF-8"); String response = scanner.useDelimiter("\\A").next(); scanner.close(); return response; } }