|
@@ -0,0 +1,229 @@
|
|
|
+package org.jeecg.modules.pay.unionPay;
|
|
|
+
|
|
|
+import cn.hutool.core.date.DateUtil;
|
|
|
+import cn.hutool.core.util.RandomUtil;
|
|
|
+import com.fasterxml.jackson.core.JsonProcessingException;
|
|
|
+import com.fasterxml.jackson.databind.JsonNode;
|
|
|
+import com.fasterxml.jackson.databind.ObjectMapper;
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
+import org.apache.commons.lang.StringUtils;
|
|
|
+import org.jeecg.common.constant.CommonConstant;
|
|
|
+
|
|
|
+import java.io.UnsupportedEncodingException;
|
|
|
+import java.net.URLEncoder;
|
|
|
+import java.nio.charset.StandardCharsets;
|
|
|
+
|
|
|
+import java.text.SimpleDateFormat;
|
|
|
+import java.util.*;
|
|
|
+
|
|
|
+/**
|
|
|
+ * @author wzq
|
|
|
+ */
|
|
|
+@Slf4j
|
|
|
+public class UnionPayUtils {
|
|
|
+
|
|
|
+ public static void main(String[] args) throws UnsupportedEncodingException {
|
|
|
+ String jsonString = "{\n" +
|
|
|
+ " \"id\": \"\",\n" +
|
|
|
+ " \"name\": \"test\",\n" +
|
|
|
+ " \"age\": 25,\n" +
|
|
|
+ " \"timestamp\": 1633046400,\n" +
|
|
|
+ " \"sign\": \"0ddf80902bbd92cf2686f229d7d05f64da7f2a298e2433072701cfb39fa69a77\",\n" +
|
|
|
+ " \"xxx\": \"\"\n" +
|
|
|
+ "}";
|
|
|
+ String secretKey = "udik876ehjde32dU61edsxsf";
|
|
|
+ boolean isValid = verifySignature(jsonString, secretKey);
|
|
|
+ log.info("签名验证结果: {}", isValid ? "通过" : "不通过");
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 银联支付接口地址
|
|
|
+ */
|
|
|
+ public static final String UNION_PAY_URL = "https://dhjt-uat.chinaums.com/queryService/UmsWebPayPlugins";
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 验证JSON字符串的签名
|
|
|
+ * @param jsonString 待验证的JSON字符串
|
|
|
+ * @param secretKey 签名密钥
|
|
|
+ * @return 验证结果
|
|
|
+ */
|
|
|
+ public static boolean verifySignature(String jsonString, String secretKey) {
|
|
|
+ try {
|
|
|
+ // 1. 解析JSON字符串获取所有参数(除sign外)
|
|
|
+ Map<String, String> paramsMap = extractParamsFromJson(jsonString);
|
|
|
+ log.info("参数列表: {}", paramsMap);
|
|
|
+
|
|
|
+ // 2. 获取sign参数值
|
|
|
+ String signValue = getSignValueFromJson(jsonString);
|
|
|
+ if (signValue == null || signValue.isEmpty()) {
|
|
|
+ throw new IllegalArgumentException("JSON字符串中缺少sign参数");
|
|
|
+ }
|
|
|
+ log.info("sign参数值: {}", signValue);
|
|
|
+
|
|
|
+ // 3. 排序参数并构建待签名字符串
|
|
|
+ String stringA = buildStringToSign(paramsMap, secretKey);
|
|
|
+ log.info("待签名字符串: {}", stringA);
|
|
|
+
|
|
|
+ // 4. 生成签名
|
|
|
+ String localSign = generateSignature(stringA);
|
|
|
+ log.info("本地签名: {}", localSign);
|
|
|
+
|
|
|
+ // 5. 比较签名(忽略大小写)
|
|
|
+ return localSign.equalsIgnoreCase(signValue);
|
|
|
+
|
|
|
+ } catch (Exception e) {
|
|
|
+ throw new RuntimeException("签名验证失败: " + e.getMessage(), e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * @return 订单号
|
|
|
+ * @Author wzq
|
|
|
+ * @Description 订单编号生成逻辑
|
|
|
+ * @params 类型:0-(D:订单) 1-(T:退单) 2-(B:保单)
|
|
|
+ **/
|
|
|
+ private String genOrderNum(String msgId, int type) {
|
|
|
+ String format = DateUtil.format(new Date(), "yyyyMMddHHmmssSSS");
|
|
|
+ String nextInt = RandomUtil.randomNumbers(CommonConstant.NUMBER_7);
|
|
|
+ if (type == 0) {
|
|
|
+ return msgId + "D" + format + nextInt;
|
|
|
+ } else {
|
|
|
+ return msgId + "T" + format + nextInt;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 从JSON中获取sign参数的值
|
|
|
+ */
|
|
|
+ private static String getSignValueFromJson(String jsonString)
|
|
|
+ throws JsonProcessingException {
|
|
|
+ ObjectMapper objectMapper = new ObjectMapper();
|
|
|
+ JsonNode rootNode = objectMapper.readTree(jsonString);
|
|
|
+ JsonNode signNode = rootNode.get("sign");
|
|
|
+ return (signNode != null && !signNode.isNull()) ? signNode.asText() : null;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 从JSON字符串中提取所有参数(除了sign)
|
|
|
+ */
|
|
|
+ public static Map<String, String> extractParamsFromJson(String jsonString) throws Exception {
|
|
|
+ ObjectMapper objectMapper = new ObjectMapper();
|
|
|
+ JsonNode rootNode = objectMapper.readTree(jsonString);
|
|
|
+
|
|
|
+ Map<String, String> paramsMap = new HashMap<>();
|
|
|
+ traverseJsonNode(rootNode, "", paramsMap);
|
|
|
+ paramsMap.remove("sign");
|
|
|
+ return paramsMap;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static void traverseJsonNode(JsonNode node, String currentPath, Map<String, String> paramsMap) {
|
|
|
+ if (node.isObject()) {
|
|
|
+ node.fields().forEachRemaining(entry -> {
|
|
|
+ String newPath = currentPath.isEmpty() ? entry.getKey() : currentPath + "." + entry.getKey();
|
|
|
+ traverseJsonNode(entry.getValue(), newPath, paramsMap);
|
|
|
+ });
|
|
|
+ } else if (node.isArray()) {
|
|
|
+ for (int i = 0; i < node.size(); i++) {
|
|
|
+ traverseJsonNode(node.get(i), currentPath + "[" + i + "]", paramsMap);
|
|
|
+ }
|
|
|
+ } else if (node.isValueNode()) {
|
|
|
+ // 只处理值节点
|
|
|
+ if (!node.isNull()) {
|
|
|
+ // 排除JsonNode类型的NullNode
|
|
|
+ String value = node.asText();
|
|
|
+ // 对于非Null节点,获取其文本表示
|
|
|
+ if (value != null && !value.isEmpty()) {
|
|
|
+ // 进一步排除空字符串
|
|
|
+ paramsMap.put(currentPath, value);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 构建待签名字符串
|
|
|
+ */
|
|
|
+ private static String buildStringToSign(Map<String, String> paramsMap, String secretKey) {
|
|
|
+ String params = "";
|
|
|
+ try {
|
|
|
+ // 按key字典序排序
|
|
|
+ List<String> sortedKeys = new ArrayList<>(paramsMap.keySet());
|
|
|
+ Collections.sort(sortedKeys);
|
|
|
+
|
|
|
+ StringBuilder sb = new StringBuilder();
|
|
|
+
|
|
|
+ // 拼接参数键值对
|
|
|
+ for (String key : sortedKeys) {
|
|
|
+ String value = URLEncoder.encode(paramsMap.get(key), StandardCharsets.UTF_8);
|
|
|
+ // 跳过空值参数
|
|
|
+ if (value != null && !value.trim().isEmpty()) {
|
|
|
+ if (sb.length() > 0) {
|
|
|
+ sb.append("&");
|
|
|
+ }
|
|
|
+ sb.append(key).append("=").append(value);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // 删除末尾多余的&号和空格
|
|
|
+ params = sb.toString();
|
|
|
+ if (!params.isEmpty()) {
|
|
|
+ params = params.substring(0, params.length() - 1);
|
|
|
+ }
|
|
|
+ params+= params+ secretKey;
|
|
|
+ } catch (Exception e) {
|
|
|
+ return "";
|
|
|
+ }
|
|
|
+ return params;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 生成签名(使用SHA-256算法)
|
|
|
+ */
|
|
|
+ private static String generateSignature(String stringToSign) {
|
|
|
+ return SHA256Util.encrypt(stringToSign);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ *
|
|
|
+ * 遍历集合M,取出全部的参数key,将参数key字典序排列,根据排序后的参数key获取对应的value,将非空的参数key和参数value用"="拼接,多个参数之间用"&"拼接
|
|
|
+ *
|
|
|
+ * @param param 参数
|
|
|
+ * @param encode 编码
|
|
|
+ * @param isLower 是否小写
|
|
|
+ * @return
|
|
|
+ */
|
|
|
+ public static String formatUrlParam(Map<String, String> param, String encode, boolean isLower) {
|
|
|
+ String params = "";
|
|
|
+
|
|
|
+ try {
|
|
|
+ List<Map.Entry<String, String>> items = new ArrayList<>(param.entrySet());
|
|
|
+
|
|
|
+ //对所有传入的参数按照字段名从小到大排序
|
|
|
+ items.sort(Map.Entry.comparingByKey());
|
|
|
+
|
|
|
+ //构造URL 键值对的形式
|
|
|
+ StringBuffer sb = new StringBuffer();
|
|
|
+ for (Map.Entry<String, String> item : items) {
|
|
|
+ if (StringUtils.isNotBlank(item.getKey())) {
|
|
|
+ String key = item.getKey();
|
|
|
+ String val = item.getValue();
|
|
|
+ val = URLEncoder.encode(val, encode);
|
|
|
+ if (isLower) {
|
|
|
+ sb.append(key.toLowerCase()).append("=").append(val);
|
|
|
+ } else {
|
|
|
+ sb.append(key).append("=").append(val);
|
|
|
+ }
|
|
|
+ sb.append("&");
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // 删除末尾多余的&号和空格
|
|
|
+ params = sb.toString();
|
|
|
+ if (!params.isEmpty()) {
|
|
|
+ params = params.substring(0, params.length() - 1);
|
|
|
+ }
|
|
|
+ } catch (Exception e) {
|
|
|
+ return "";
|
|
|
+ }
|
|
|
+ return params;
|
|
|
+ }
|
|
|
+
|
|
|
+}
|