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;
|
|
@Value("${pay.call-back-url}")
|
private String callBackUrl;
|
|
/**
|
* 创建支付订单 - 适配新支付系统(修正版)
|
*/
|
public String createOrder(BigDecimal amount, String orderNo,String payProductId) {
|
try {
|
Map<String, Object> params = new HashMap<>();
|
|
// 必需参数
|
params.put("userCode", mchId); // 商户号
|
params.put("channelCode", payProductId); // 通道码
|
params.put("orderId", orderNo); // 订单号
|
params.put("orderMoney", amount); // 金额(格式化金额)
|
params.put("callbackUrl", callBackUrl); // 回调地址
|
|
// 可选参数(根据业务需要添加)
|
// 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<String, Object> params) {
|
try {
|
// 1. 参数名ASCII码从小到大排序(字典序)
|
List<String> 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<String, Object> params) {
|
try {
|
StringBuilder urlBuilder = new StringBuilder(baseUrl);
|
boolean firstParam = true;
|
|
for (Map.Entry<String, Object> 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<String, String> params) {
|
try {
|
String receivedSign = params.get("sign");
|
Map<String, Object> signParams = new HashMap<>(params);
|
String calculatedSign = generateSign(signParams);
|
return calculatedSign.equals(receivedSign);
|
} catch (Exception e) {
|
return false;
|
}
|
}
|
|
/**
|
* 生成签名
|
*/
|
private String generateSign(Map<String, Object> params) throws Exception {
|
// 移除sign参数
|
Map<String, Object> signParams = new HashMap<>(params);
|
signParams.remove("sign");
|
|
// 过滤空值并排序
|
List<String> keys = new ArrayList<>();
|
for (Map.Entry<String, Object> 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<String, Object> params) throws Exception {
|
// 构建参数字符串
|
StringBuilder postData = new StringBuilder();
|
for (Map.Entry<String, Object> 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;
|
}
|
}
|