From e9ad3ca0104dc5caac3db45374c8ae97168ee53b Mon Sep 17 00:00:00 2001
From: xin <1099200748@qq.com>
Date: Thu, 12 Jun 2025 21:31:29 +0800
Subject: [PATCH] 支付
---
oying-system/src/main/java/com/oying/modules/hwc/utils/SignUtils.java | 110 +++++
oying-system/src/main/java/com/oying/modules/hwc/rest/SwiftPassController.java | 38 +
oying-system/src/main/java/com/oying/modules/hwc/utils/XmlUtils.java | 138 ++++++
oying-system/src/main/java/com/oying/modules/hwc/domain/HwcResponse.java | 30 +
oying-common/src/main/java/com/oying/utils/enums/PayTypeEnum.java | 38 +
pom.xml | 7
oying-system/src/main/java/com/oying/modules/hwc/service/impl/SwiftPassServiceImpl.java | 395 +++++++++++++++++++
oying-common/src/main/java/com/oying/utils/enums/PayReturnStateEnum.java | 44 ++
oying-system/src/main/java/com/oying/modules/hwc/service/SwiftPassService.java | 51 ++
oying-common/src/main/java/com/oying/utils/enums/PayStateEnum.java | 52 ++
oying-system/src/main/java/com/oying/modules/hwc/utils/MD5.java | 56 ++
oying-system/src/main/java/com/oying/modules/hwc/utils/SignUtil.java | 90 ++++
oying-system/src/main/java/com/oying/modules/hwc/utils/RSAUtil.java | 104 +++++
13 files changed, 1,153 insertions(+), 0 deletions(-)
diff --git a/oying-common/src/main/java/com/oying/utils/enums/PayReturnStateEnum.java b/oying-common/src/main/java/com/oying/utils/enums/PayReturnStateEnum.java
new file mode 100644
index 0000000..7983114
--- /dev/null
+++ b/oying-common/src/main/java/com/oying/utils/enums/PayReturnStateEnum.java
@@ -0,0 +1,44 @@
+package com.oying.utils.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * @author xin
+ * @description 退款状态
+ * @date 2024/10/25 下午4:35
+ */
+@Getter
+@AllArgsConstructor
+public enum PayReturnStateEnum {
+
+ SUCCESS("SUCCESS","退款成功"),
+
+ CLOSED("CLOSED","退款关闭"),
+
+ ABNORMAL("ABNORMAL","退款异常"),
+
+ UNKNOWN("UNKNOWN", "未知枚举");
+
+ private final String key;
+
+ private final String value;
+
+ public static PayReturnStateEnum find(String val) {
+ for (PayReturnStateEnum value : PayReturnStateEnum.values()) {
+ if (val.equals(value.getKey())) {
+ return value;
+ }
+ }
+ return UNKNOWN;
+ }
+
+ public static String getValue(String val) {
+ for (PayReturnStateEnum value : PayReturnStateEnum.values()) {
+ if (val.equals(value.getKey())) {
+ return value.getValue();
+ }
+ }
+ return UNKNOWN.getValue();
+ }
+}
diff --git a/oying-common/src/main/java/com/oying/utils/enums/PayStateEnum.java b/oying-common/src/main/java/com/oying/utils/enums/PayStateEnum.java
new file mode 100644
index 0000000..1f46015
--- /dev/null
+++ b/oying-common/src/main/java/com/oying/utils/enums/PayStateEnum.java
@@ -0,0 +1,52 @@
+package com.oying.utils.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * @author xin
+ * @description 支付状态
+ * @date 2024/10/25 下午4:09
+ */
+@Getter
+@AllArgsConstructor
+public enum PayStateEnum {
+
+ SUCCESS("SUCCESS", "支付成功"),
+
+ REFUND("REFUND", "转入退款"),
+
+ NOTPAY("NOTPAY", "未支付"),
+
+ CLOSED("CLOSED", "已关闭"),
+
+ REVOKED("REVOKED", "已撤销"),
+
+ USERPAYING("USERPAYING", "用户支付中"),
+
+ PAYERROR("PAYERROR", "支付失败"),
+
+ UNKNOWN("UNKNOWN", "未知枚举");
+
+ private final String key;
+
+ private final String value;
+
+ public static PayStateEnum find(String val) {
+ for (PayStateEnum value : PayStateEnum.values()) {
+ if (val.equals(value.getKey())) {
+ return value;
+ }
+ }
+ return UNKNOWN;
+ }
+
+ public static String getValue(String val) {
+ for (PayStateEnum value : PayStateEnum.values()) {
+ if (val.equals(value.getKey())) {
+ return value.getValue();
+ }
+ }
+ return UNKNOWN.getValue();
+ }
+}
diff --git a/oying-common/src/main/java/com/oying/utils/enums/PayTypeEnum.java b/oying-common/src/main/java/com/oying/utils/enums/PayTypeEnum.java
new file mode 100644
index 0000000..640c57a
--- /dev/null
+++ b/oying-common/src/main/java/com/oying/utils/enums/PayTypeEnum.java
@@ -0,0 +1,38 @@
+package com.oying.utils.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * @author xin
+ * @description 支付类型
+ * @date 2024/10/25 下午4:09
+ */
+@Getter
+@AllArgsConstructor
+public enum PayTypeEnum {
+
+ /* 通过邮箱重置密码 */
+ HWC("HWC", "汇旺财");
+
+ private final String key;
+ private final String value;
+
+ public static PayTypeEnum find(String val) {
+ for (PayTypeEnum value : PayTypeEnum.values()) {
+ if (val.equals(value.getKey())) {
+ return value;
+ }
+ }
+ return null;
+ }
+
+ public static String getValue(String val) {
+ for (PayTypeEnum value : PayTypeEnum.values()) {
+ if (val.equals(value.getKey())) {
+ return value.getValue();
+ }
+ }
+ return null;
+ }
+}
diff --git a/oying-system/src/main/java/com/oying/modules/hwc/domain/HwcResponse.java b/oying-system/src/main/java/com/oying/modules/hwc/domain/HwcResponse.java
new file mode 100644
index 0000000..83b1942
--- /dev/null
+++ b/oying-system/src/main/java/com/oying/modules/hwc/domain/HwcResponse.java
@@ -0,0 +1,30 @@
+package com.oying.modules.hwc.domain;
+
+import com.alibaba.fastjson2.annotation.JSONField;
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * @author xin
+ * @description
+ * @date 2025/1/23 下午9:56
+ */
+@Getter
+@Setter
+public class HwcResponse {
+
+ @JSONField(name = "appId")
+ private String appId;
+ @JSONField(name = "timeStamp")
+ private String timeStamp;
+ @JSONField(name = "nonceStr")
+ private String nonceStr;
+ @JSONField(name = "package")
+ private String packageVal;
+ @JSONField(name = "signType")
+ private String signType;
+ @JSONField(name = "paySign")
+ private String paySign;
+ @JSONField(name = "status")
+ private String status;
+}
diff --git a/oying-system/src/main/java/com/oying/modules/hwc/rest/SwiftPassController.java b/oying-system/src/main/java/com/oying/modules/hwc/rest/SwiftPassController.java
new file mode 100644
index 0000000..f544bdb
--- /dev/null
+++ b/oying-system/src/main/java/com/oying/modules/hwc/rest/SwiftPassController.java
@@ -0,0 +1,38 @@
+package com.oying.modules.hwc.rest;
+
+import com.oying.annotation.rest.AnonymousPostMapping;
+import com.oying.modules.hwc.service.SwiftPassService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * @author xin
+ * @description
+ * @date 2025/1/24 下午1:01
+ */
+@RestController
+@RequiredArgsConstructor
+@Api(tags = "HWS:汇旺财回调")
+@RequestMapping("/api/swiftPass")
+public class SwiftPassController {
+
+ private final SwiftPassService swiftPassService;
+
+ @ApiOperation("HWS支付回调")
+ @AnonymousPostMapping(value = "/alipayCallback")
+ public void alipayCallback(HttpServletRequest request, HttpServletResponse response) {
+ swiftPassService.alipayCallback(request, response);
+ }
+
+ @ApiOperation("HWS退款回调")
+ @AnonymousPostMapping(value = "/returnNotify")
+ public void returnNotify(HttpServletRequest request, HttpServletResponse response) {
+ swiftPassService.returnNotify(request, response);
+ }
+}
diff --git a/oying-system/src/main/java/com/oying/modules/hwc/service/SwiftPassService.java b/oying-system/src/main/java/com/oying/modules/hwc/service/SwiftPassService.java
new file mode 100644
index 0000000..9083daa
--- /dev/null
+++ b/oying-system/src/main/java/com/oying/modules/hwc/service/SwiftPassService.java
@@ -0,0 +1,51 @@
+package com.oying.modules.hwc.service;
+
+import com.alibaba.fastjson2.JSONObject;
+import com.oying.modules.hwc.domain.HwcResponse;
+import com.oying.utils.enums.PayTypeEnum;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.validation.constraints.NotNull;
+import java.io.IOException;
+import java.util.Map;
+
+/**
+ * @author xin
+ * @description
+ * @date 2025/1/23 下午3:55
+ */
+public interface SwiftPassService {
+
+ /**
+ * <一句话功能简述>
+ * <功能详细描述>支付请求
+ *
+ * @see [类、类#方法、类#成员]
+ */
+ HwcResponse pay(String ip, Integer total, String timeExpire, String description, String openId,
+ String orderNum, PayTypeEnum status) throws IOException;
+
+ /**
+ * <一句话功能简述>
+ * <功能详细描述>订单查询
+ *
+ * @see [类、类#方法、类#成员]
+ */
+ JSONObject query(String orderNum, PayTypeEnum status) throws IOException;
+
+ void closeOrder(String outTradeNo, PayTypeEnum status) throws IOException;
+
+ /**
+ * <一句话功能简述>
+ * <功能详细描述>退款
+ *
+ * @see [类、类#方法、类#成员]
+ */
+ Map<String, String> refund(String returnNum, String orderNum, @NotNull(message = "备注不能为空") String reason,
+ long refund, long total, PayTypeEnum payType) throws IOException;
+
+ void alipayCallback(HttpServletRequest request, HttpServletResponse response);
+
+ void returnNotify(HttpServletRequest request, HttpServletResponse response);
+}
diff --git a/oying-system/src/main/java/com/oying/modules/hwc/service/impl/SwiftPassServiceImpl.java b/oying-system/src/main/java/com/oying/modules/hwc/service/impl/SwiftPassServiceImpl.java
new file mode 100644
index 0000000..bc39419
--- /dev/null
+++ b/oying-system/src/main/java/com/oying/modules/hwc/service/impl/SwiftPassServiceImpl.java
@@ -0,0 +1,395 @@
+package com.oying.modules.hwc.service.impl;
+
+import com.alibaba.fastjson2.JSONObject;
+import com.oying.exception.BadRequestException;
+import com.oying.modules.hwc.domain.HwcResponse;
+import com.oying.modules.hwc.service.SwiftPassService;
+import com.oying.modules.hwc.utils.SignUtil;
+import com.oying.modules.hwc.utils.SignUtils;
+import com.oying.modules.hwc.utils.XmlUtils;
+import com.oying.modules.security.config.SwiftPassProperties;
+import com.oying.modules.sh.service.OrderReturnService;
+import com.oying.modules.sh.service.OrderService;
+import com.oying.utils.enums.PayTypeEnum;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClients;
+import org.apache.http.util.EntityUtils;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import javax.annotation.Resource;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.*;
+
+/**
+ * @author xin
+ * @description
+ * @date 2025/1/23 下午4:01
+ */
+@SuppressWarnings({"unchecked", "all"})
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class SwiftPassServiceImpl implements SwiftPassService {
+
+ private final static String version = "2.0";
+ private final static String charset = "UTF-8";
+ private final static String service_pay = "pay.weixin.jspay";
+ private final static String service_query = "unified.trade.query";
+ private final static String service_refund = "unified.trade.refund";
+ private final static String service_close = "unified.trade.close";
+
+ private final SwiftPassProperties properties;
+ private final OrderService orderService;
+ private final OrderReturnService returnService;
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public HwcResponse pay(String ip, Integer total, String timeExpire, String description, String openId,
+ String orderNum, PayTypeEnum status) throws IOException {
+ SortedMap<String, String> map = new TreeMap<>();
+ map.put("service", service_pay);
+ map.put("version", version);
+ map.put("charset", charset);
+ map.put("sign_type", properties.getSignType());
+ map.put("is_raw", properties.getIsRaw());
+ map.put("is_minipg", properties.getIsMinipg());
+ map.put("out_trade_no", orderNum);
+ map.put("body", description);
+ map.put("sub_openid", openId);
+ map.put("sub_appid", properties.getAppId());
+ map.put("total_fee", String.valueOf(total));
+ map.put("mch_create_ip", ip);
+ map.put("notify_url", properties.getNotifyUrl());
+ map.put("time_expire", timeExpire);
+ map.put("nonce_str", String.valueOf(new Date().getTime()));
+ switch (status) {
+ case HWC:
+ map.put("mch_id", properties.getMchId());
+ break;
+ default:
+ throw new BadRequestException("汇旺财类型错误");
+ }
+ Map<String, String> params = SignUtils.paraFilter(map);
+ StringBuilder buf = new StringBuilder((params.size() + 1) * 10);
+ SignUtils.buildPayParams(buf, params, false);
+ String preStr = buf.toString();
+ String sign_type = map.get("sign_type");
+
+ map.put("sign", SignUtil.getSign(sign_type, preStr, properties, status));
+
+ String reqUrl = properties.getReqUrl();
+ CloseableHttpResponse response = null;
+ CloseableHttpClient client = null;
+ Map<String, String> resultMap = new HashMap<>();
+ try {
+ HttpPost httpPost = new HttpPost(reqUrl);
+ StringEntity entityParams = new StringEntity(XmlUtils.parseXML(map), "utf-8");
+ httpPost.setEntity(entityParams);
+ httpPost.setHeader("Content-Type", "text/xml;utf-8");
+ client = HttpClients.createDefault();
+ response = client.execute(httpPost);
+ if (response != null && response.getEntity() != null) {
+ resultMap = XmlUtils.toMap(EntityUtils.toByteArray(response.getEntity()), "utf-8");
+ String reSign = resultMap.get("sign");
+ sign_type = resultMap.get("sign_type");
+ if (resultMap.containsKey("sign") && SignUtil.verifySign(reSign, sign_type, resultMap, properties, status)) {
+ throw new BadRequestException("验证签名错误");
+ } else {
+ if ("0".equals(resultMap.get("status")) && "0".equals(resultMap.get("result_code"))) {
+// Gson gson = new Gson();
+// return gson.fromJson(resultMap.get("pay_info"), HwcResponse.class);
+ return null;
+ } else {
+ throw new BadRequestException(resultMap.get("err_code") + " : " + resultMap.get("err_msg") + "\n" +
+ resultMap.get("status") + " : " + resultMap.get("message"));
+ }
+ }
+ } else {
+ throw new BadRequestException("操作失败");
+ }
+ } catch (Exception e) {
+ log.error("请求参数:{}", params);
+ log.error("返回参数:{}", resultMap);
+ throw new BadRequestException("系统异常:" + e.getMessage());
+ } finally {
+ if (response != null) {
+ response.close();
+ }
+ if (client != null) {
+ client.close();
+ }
+ }
+ }
+
+ @Override
+ public JSONObject query(String orderNum, PayTypeEnum status) throws IOException {
+ // 参数
+ SortedMap<String, String> map = new TreeMap<>();
+ map.put("service", service_query);
+ map.put("version", version);
+ map.put("charset", charset);
+ map.put("out_trade_no", orderNum);
+ map.put("sign_type", properties.getSignType());
+ switch (status) {
+ case HWC:
+ map.put("mch_id", properties.getMchId());
+ break;
+ default:
+ throw new BadRequestException("汇旺财类型错误");
+ }
+ map.put("nonce_str", String.valueOf(new Date().getTime()));
+ // 签名
+ Map<String, String> params = SignUtils.paraFilter(map);
+ StringBuilder buf = new StringBuilder((params.size() + 1) * 10);
+ SignUtils.buildPayParams(buf, params, false);
+ String preStr = buf.toString();
+ String sign_type = map.get("sign_type");
+ map.put("sign", SignUtil.getSign(sign_type, preStr, properties, status));
+ // 请求
+ CloseableHttpResponse response = null;
+ CloseableHttpClient client = null;
+ Map<String, String> resultMap = new HashMap<>();
+ try {
+ HttpPost httpPost = new HttpPost(properties.getReqUrl());
+ StringEntity entityParams = new StringEntity(XmlUtils.parseXML(map), "utf-8");
+ httpPost.setEntity(entityParams);
+ httpPost.setHeader("Content-Type", "text/xml;utf-8");
+ client = HttpClients.createDefault();
+ response = client.execute(httpPost);
+ if (response != null && response.getEntity() != null) {
+ resultMap = XmlUtils.toMap(EntityUtils.toByteArray(response.getEntity()), "utf-8");
+ String reSign = resultMap.get("sign");
+ sign_type = resultMap.get("sign_type");
+ if (resultMap.containsKey("sign") && SignUtil.verifySign(reSign, sign_type, resultMap, properties, status)) {
+ throw new BadRequestException("验证签名错误");
+ } else {
+ if ("0".equals(resultMap.get("status"))) {
+ return JSONObject.parseObject(JSONObject.toJSONString(resultMap));
+ } else {
+
+ throw new BadRequestException(resultMap.get("err_code") + " : " + resultMap.get("err_msg") + "\n" +
+ resultMap.get("status") + " : " + resultMap.get("message"));
+ }
+ }
+ } else {
+ throw new BadRequestException("操作失败!");
+ }
+ } catch (Exception e) {
+ log.error("请求参数:{}", params);
+ log.error("返回参数:{}", resultMap);
+ throw new BadRequestException("操作失败,原因:" + e.getMessage());
+ } finally {
+ if (response != null) {
+ response.close();
+ }
+ if (client != null) {
+ client.close();
+ }
+ }
+ }
+
+ @Override
+ public void closeOrder(String outTradeNo, PayTypeEnum status) throws IOException {
+ // 参数
+ SortedMap<String, String> map = new TreeMap<>();
+ map.put("service", service_close);
+ map.put("version", version);
+ map.put("charset", charset);
+ map.put("out_trade_no", outTradeNo);
+ map.put("sign_type", properties.getSignType());
+ switch (status) {
+ case HWC:
+ map.put("mch_id", properties.getMchId());
+ break;
+ default:
+ throw new BadRequestException("汇旺财类型错误");
+ }
+ map.put("nonce_str", String.valueOf(new Date().getTime()));
+ // 签名
+ Map<String, String> params = SignUtils.paraFilter(map);
+ StringBuilder buf = new StringBuilder((params.size() + 1) * 10);
+ SignUtils.buildPayParams(buf, params, false);
+ String preStr = buf.toString();
+ String sign_type = map.get("sign_type");
+ map.put("sign", SignUtil.getSign(sign_type, preStr, properties, status));
+ // 请求
+ CloseableHttpResponse response = null;
+ CloseableHttpClient client = null;
+ Map<String, String> resultMap = new HashMap<>();
+ try {
+ HttpPost httpPost = new HttpPost(properties.getReqUrl());
+ StringEntity entityParams = new StringEntity(XmlUtils.parseXML(map), "utf-8");
+ httpPost.setEntity(entityParams);
+ httpPost.setHeader("Content-Type", "text/xml;utf-8");
+ client = HttpClients.createDefault();
+ response = client.execute(httpPost);
+ if (response != null && response.getEntity() != null) {
+ resultMap = XmlUtils.toMap(EntityUtils.toByteArray(response.getEntity()), "utf-8");
+ String reSign = resultMap.get("sign");
+ sign_type = resultMap.get("sign_type");
+ if (resultMap.containsKey("sign") && SignUtil.verifySign(reSign, sign_type, resultMap, properties, status)) {
+ throw new BadRequestException("验证签名错误");
+ } else {
+ if (!("0".equals(resultMap.get("status")))) {
+ throw new BadRequestException(resultMap.get("err_code") + " : " + resultMap.get("err_msg") + "\n" +
+ resultMap.get("status") + " : " + resultMap.get("message"));
+ }
+ }
+ } else {
+ throw new BadRequestException("操作失败!");
+ }
+ } catch (Exception e) {
+ log.error("请求参数:{}", params);
+ log.error("返回参数:{}", resultMap);
+ throw new BadRequestException("操作失败,原因:" + e.getMessage());
+ } finally {
+ if (response != null) {
+ response.close();
+ }
+ if (client != null) {
+ client.close();
+ }
+ }
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public Map<String, String> refund(String returnNum, String orderNum, String reason, long refund,
+ long total, PayTypeEnum payType) throws IOException {
+ // 参数
+ SortedMap<String, String> map = new TreeMap<>();
+ map.put("service", service_refund);
+ map.put("version", version);
+ map.put("charset", charset);
+ map.put("sign_type", properties.getSignType());
+ map.put("out_trade_no", orderNum);
+ map.put("out_refund_no", returnNum);
+ map.put("total_fee", String.valueOf(total));
+ map.put("refund_fee", String.valueOf(refund));
+ map.put("nonce_str", String.valueOf(new Date().getTime()));
+ switch (payType) {
+ case HWC:
+ map.put("mch_id", properties.getMchId());
+ map.put("op_user_id", properties.getMchId());
+ break;
+ default:
+ throw new BadRequestException("汇旺财类型错误");
+ }
+ // 签名
+ Map<String, String> params = SignUtils.paraFilter(map);
+ StringBuilder buf = new StringBuilder((params.size() + 1) * 10);
+ SignUtils.buildPayParams(buf, params, false);
+ String preStr = buf.toString();
+ String sign_type = map.get("sign_type");
+ map.put("sign", SignUtil.getSign(sign_type, preStr, properties, payType));
+ // 参数
+ CloseableHttpResponse response = null;
+ CloseableHttpClient client = null;
+ Map<String, String> resultMap = new HashMap<>();
+ try {
+ HttpPost httpPost = new HttpPost(properties.getReqUrl());
+ StringEntity entityParams = new StringEntity(XmlUtils.parseXML(map), "utf-8");
+ httpPost.setEntity(entityParams);
+ httpPost.setHeader("Content-Type", "text/xml;utf-8");
+ client = HttpClients.createDefault();
+ response = client.execute(httpPost);
+ if (response != null && response.getEntity() != null) {
+ resultMap = XmlUtils.toMap(EntityUtils.toByteArray(response.getEntity()), "utf-8");
+ if (resultMap.containsKey("sign") && SignUtil.verifySign(resultMap.get("sign"), resultMap.get("sign_type"), resultMap, properties, payType)) {
+ throw new BadRequestException("验证签名错误");
+ } else {
+ if ("0".equals(resultMap.get("status"))) {
+ return resultMap;
+ } else {
+ throw new BadRequestException(resultMap.get("err_code") + " : " + resultMap.get("err_msg") + "\n" +
+ resultMap.get("status") + " : " + resultMap.get("message"));
+ }
+ }
+ } else {
+ throw new BadRequestException("操作失败!");
+ }
+ } catch (Exception e) {
+ log.error("请求参数:{}", params);
+ log.error("返回参数:{}", resultMap);
+ throw new BadRequestException("操作失败,原因:" + e);
+ } finally {
+ if (response != null) {
+ response.close();
+ }
+ if (client != null) {
+ client.close();
+ }
+ }
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public void alipayCallback(HttpServletRequest request, HttpServletResponse response) {
+ try {
+ String resString = XmlUtils.parseRequest(request);
+ String respString = "error";
+ if (!resString.isEmpty()) {
+ Map<String, String> map = XmlUtils.toMap(resString.getBytes(), "utf-8");
+ String sign_type = map.get("sign_type");
+ String reSign = map.get("sign");
+ if (map.containsKey("sign")) {
+// OrderDto order = orderService.findByOrderNum(map.get("out_trade_no"));
+ PayTypeEnum status = PayTypeEnum.find("order.getPayType()");
+ if (SignUtil.verifySign(reSign, sign_type, map, properties, status)) {
+ System.out.println("验证签名错误!:" + map);
+ } else {
+ if ("0".equals(map.get("status"))) {
+ if ("0".equals(map.get("result_code"))) {
+ //业务处理
+ respString = "success";
+ response.getWriter().write(respString);
+ }
+ }
+ }
+ }
+ }
+ response.getWriter().write(respString);
+ } catch (Exception e) {
+ throw new BadRequestException("操作失败,原因:" + e.getMessage());
+ }
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public void returnNotify(HttpServletRequest request, HttpServletResponse response) {
+ try {
+ String resString = XmlUtils.parseRequest(request);
+ String respString = "error";
+ if (!resString.isEmpty()) {
+ Map<String, String> map = XmlUtils.toMap(resString.getBytes(), "utf-8");
+ String sign_type = map.get("sign_type");
+ String reSign = map.get("sign");
+ if (map.containsKey("sign")) {
+// ReturnOrder returnOrder = returnOrderRepository.findByReturnNum(map.get("out_refund_no"));
+ PayTypeEnum status = PayTypeEnum.find("order.getPayType()");
+ if (SignUtil.verifySign(reSign, sign_type, map, properties, status)) {
+ System.out.println("验证签名错误!:" + map);
+ } else {
+ if ("0".equals(map.get("status"))) {
+ if ("0".equals(map.get("result_code"))) {
+ // 业务处理
+ respString = "success";
+ }
+ }
+ }
+ }
+ }
+ response.getWriter().write(respString);
+ } catch (Exception e) {
+ System.out.println("操作失败,原因:" + e.getMessage());
+ }
+ }
+}
diff --git a/oying-system/src/main/java/com/oying/modules/hwc/utils/MD5.java b/oying-system/src/main/java/com/oying/modules/hwc/utils/MD5.java
new file mode 100644
index 0000000..4a7e899
--- /dev/null
+++ b/oying-system/src/main/java/com/oying/modules/hwc/utils/MD5.java
@@ -0,0 +1,56 @@
+package com.oying.modules.hwc.utils;
+
+import org.apache.commons.codec.digest.DigestUtils;
+
+import java.io.UnsupportedEncodingException;
+
+/**
+* 功能:MD5签名
+* 版本:3.3
+* 修改日期:2012-08-17
+* */
+public class MD5 {
+
+ /**
+ * 签名字符串
+ * @param text 需要签名的字符串
+ * @param key 密钥
+ * @param input_charset 编码格式
+ * @return 签名结果
+ */
+ public static String sign(String text, String key, String input_charset) {
+ text = text + key;
+ return DigestUtils.md5Hex(getContentBytes(text, input_charset)).toUpperCase();
+ }
+
+ /**
+ * 签名字符串
+ * @param text 需要签名的字符串
+ * @param sign 签名结果
+ * @param key 密钥
+ * @param input_charset 编码格式
+ * @return 签名结果
+ */
+ public static boolean verify(String text, String sign, String key, String input_charset) {
+ text = text + key;
+ String md5Hex = DigestUtils.md5Hex(getContentBytes(text, input_charset));
+ return md5Hex.equals(sign);
+ }
+
+ /**
+ * @param content 需要签名的字符串
+ * @param charset 编码格式
+ * @return 签名结果
+ */
+ private static byte[] getContentBytes(String content, String charset) {
+ if (charset == null || charset.isEmpty()) {
+ return content.getBytes();
+ }
+ try {
+ return content.getBytes(charset);
+ } catch (UnsupportedEncodingException e) {
+ throw new RuntimeException("MD5签名过程中出现错误,指定的编码集不对,您目前指定的编码集是:" + charset);
+ }
+ }
+
+}
diff --git a/oying-system/src/main/java/com/oying/modules/hwc/utils/RSAUtil.java b/oying-system/src/main/java/com/oying/modules/hwc/utils/RSAUtil.java
new file mode 100644
index 0000000..11b2b45
--- /dev/null
+++ b/oying-system/src/main/java/com/oying/modules/hwc/utils/RSAUtil.java
@@ -0,0 +1,104 @@
+package com.oying.modules.hwc.utils;
+
+import org.apache.commons.codec.binary.Base64;
+
+import java.security.*;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.security.spec.X509EncodedKeySpec;
+
+
+/**
+ * @author zeming.fan@swiftpass.cn
+ *
+ */
+public class RSAUtil {
+
+ public enum SignatureSuite {
+ //SHA1("SHA1WithRSA"), MD5("MD5WithRSA");
+ SHA1("SHA1WithRSA"), SHA256("SHA256WithRSA");
+ private final String suite;
+
+ SignatureSuite(String suite) {
+ this.suite = suite;
+ }
+
+ public String val() {
+ return suite;
+ }
+ }
+
+
+ private static KeyFactory getKeyFactory() {
+ try {
+ return KeyFactory.getInstance("RSA");
+ } catch (NoSuchAlgorithmException e) {
+ // 应该不会出现
+ throw new RuntimeException("初始化RSA KeyFactory失败");
+ }
+ }
+
+ public static byte[] sign(SignatureSuite suite, byte[] msgBuf, String privateKeyStr) {
+ Signature signature = null;
+ try {
+ signature = Signature.getInstance(suite.val());
+ } catch (Exception e) {
+ // 上线运行时套件一定存在
+ // 异常不往外抛
+ }
+
+ try {
+ PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(Base64.decodeBase64(privateKeyStr));
+ PrivateKey privateKey = getKeyFactory().generatePrivate(keySpec);
+ if (signature != null) {
+ signature.initSign(privateKey);
+ }
+ } catch(Exception e) {
+ //logger.warn("解析私钥失败:{}", e.getMessage());
+ throw new RuntimeException("INVALID_PRIKEY");
+ }
+ try {
+ if (signature != null) {
+ signature.update(msgBuf);
+ }
+ if (signature != null) {
+ return signature.sign();
+ }
+ } catch (SignatureException e) {
+ // 一般不会出现
+ throw new RuntimeException(e.getMessage());
+ }
+ return msgBuf;
+ }
+
+ public static boolean verifySign(SignatureSuite suite, byte[] msgBuf, byte[] sign, String publicKeyStr) {
+ Signature signature = null;
+ try {
+ signature = Signature.getInstance(suite.val());
+ } catch (Exception e) {
+ // 上线运行时套件一定存在
+ // 异常不往外抛
+ }
+
+ try {
+ X509EncodedKeySpec keySpec = new X509EncodedKeySpec(Base64.decodeBase64(publicKeyStr));
+ PublicKey publicKey = getKeyFactory().generatePublic(keySpec);
+ if (signature != null) {
+ signature.initVerify(publicKey);
+ }
+ } catch(Exception e) {
+ throw new RuntimeException("INVALID_PUBKEY");
+ }
+ try {
+ if (signature != null) {
+ signature.update(msgBuf);
+ }
+ if (signature != null) {
+ return signature.verify(sign);
+ }
+ } catch (SignatureException e) {
+ // 一般不会出现
+ throw new RuntimeException("签名格式不合法");
+ }
+ return false;
+ }
+}
diff --git a/oying-system/src/main/java/com/oying/modules/hwc/utils/SignUtil.java b/oying-system/src/main/java/com/oying/modules/hwc/utils/SignUtil.java
new file mode 100644
index 0000000..b2a80cb
--- /dev/null
+++ b/oying-system/src/main/java/com/oying/modules/hwc/utils/SignUtil.java
@@ -0,0 +1,90 @@
+package com.oying.modules.hwc.utils;
+
+import com.oying.exception.BadRequestException;
+import com.oying.modules.security.config.SwiftPassProperties;
+import com.oying.utils.enums.PayTypeEnum;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.codec.binary.Base64;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Map;
+
+/**
+ * @author zeming.fan@swiftpass.cn
+ */
+@Slf4j
+public class SignUtil {
+
+ /**
+ * 请求时根据不同签名方式去生成不同的sign
+ */
+ public static String getSign(String signType, String preStr, SwiftPassProperties properties, PayTypeEnum status) {
+ if ("RSA_1_256".equals(signType)) {
+ try {
+ return SignUtil.sign(preStr, "RSA_1_256", properties.getMchPrivateKey());
+ } catch (Exception e1) {
+ log.error(e1.getMessage(), e1);
+ throw new BadRequestException(e1.getMessage());
+ }
+ } else {
+ if (status.equals(PayTypeEnum.HWC)) {
+ return MD5.sign(preStr, "&key=" + properties.getKey(), "utf-8");
+ }
+ throw new BadRequestException("汇旺财类型错误");
+ }
+ }
+
+ /**
+ * 对返回参数的验证签名
+ */
+ public static boolean verifySign(String sign, String signType, Map<String, String> resultMap, SwiftPassProperties properties, PayTypeEnum status) throws Exception {
+ if ("RSA_1_256".equals(signType)) {
+ Map<String, String> params = SignUtils.paraFilter(resultMap);
+ StringBuilder builder = new StringBuilder((params.size() + 1) * 10);
+ SignUtils.buildPayParams(builder, params, false);
+ String preStr = builder.toString();
+ return !SignUtil.verifySign(preStr, sign, "RSA_1_256", properties.getPlatPublicKey());
+ } else if ("MD5".equals(signType)) {
+ if (status.equals(PayTypeEnum.HWC)) {
+ return !SignUtils.checkParam(resultMap, properties.getKey());
+ }
+ throw new BadRequestException("汇旺财类型错误");
+ }
+ return true;
+ }
+
+ /**
+ * RSA_1_256 验证签名
+ */
+ public static boolean verifySign(String preStr, String sign, String signType, String platPublicKey) throws Exception {
+ // 调用这个函数前需要先判断是MD5还是RSA
+ // 商户的验签函数要同时支持MD5和RSA
+ RSAUtil.SignatureSuite suite;
+ if ("RSA_1_1".equals(signType)) {
+ suite = RSAUtil.SignatureSuite.SHA1;
+ } else if ("RSA_1_256".equals(signType)) {
+ suite = RSAUtil.SignatureSuite.SHA256;
+ } else {
+ throw new Exception("不支持的签名方式");
+ }
+ return RSAUtil.verifySign(suite, preStr.getBytes(StandardCharsets.UTF_8), Base64.decodeBase64(sign.getBytes(StandardCharsets.UTF_8)),
+ platPublicKey);
+ }
+
+ /**
+ * RSA_1_256生成不同的sign
+ */
+ public static String sign(String preStr, String signType, String mchPrivateKey) throws Exception {
+ RSAUtil.SignatureSuite suite;
+ if ("RSA_1_1".equals(signType)) {
+ suite = RSAUtil.SignatureSuite.SHA1;
+ } else if ("RSA_1_256".equals(signType)) {
+ suite = RSAUtil.SignatureSuite.SHA256;
+ } else {
+ throw new Exception("不支持的签名方式");
+ }
+ byte[] signBuf = RSAUtil.sign(suite, preStr.getBytes(StandardCharsets.UTF_8),
+ mchPrivateKey);
+ return new String(Base64.encodeBase64(signBuf), StandardCharsets.UTF_8);
+ }
+}
diff --git a/oying-system/src/main/java/com/oying/modules/hwc/utils/SignUtils.java b/oying-system/src/main/java/com/oying/modules/hwc/utils/SignUtils.java
new file mode 100644
index 0000000..50daf80
--- /dev/null
+++ b/oying-system/src/main/java/com/oying/modules/hwc/utils/SignUtils.java
@@ -0,0 +1,110 @@
+package com.oying.modules.hwc.utils;
+
+import org.dom4j.Document;
+import org.dom4j.DocumentException;
+import org.dom4j.Element;
+import org.dom4j.io.SAXReader;
+import org.xml.sax.InputSource;
+
+import java.io.StringReader;
+import java.net.URLEncoder;
+import java.util.*;
+
+
+/**
+ * ClassName:SignUtils
+ * Function: 签名用的工具箱
+ * Date: 2014-6-27 下午3:22:33
+ */
+public class SignUtils {
+
+ /**
+ * <一句话功能简述>
+ * <功能详细描述>验证返回参数
+ *
+ * @see [类、类#方法、类#成员]
+ */
+ public static boolean checkParam(Map<String, String> params, String key) {
+ boolean result = false;
+ if (params.containsKey("sign")) {
+ String sign = params.get("sign");
+ params.remove("sign");
+ StringBuilder buf = new StringBuilder((params.size() + 1) * 10);
+ SignUtils.buildPayParams(buf, params, false);
+ String preStr = buf.toString();
+ String signMD5 = MD5.sign(preStr, "&key=" + key, "utf-8");
+ result = sign.equalsIgnoreCase(signMD5);
+ }
+ return result;
+ }
+
+ /**
+ * 过滤参数
+ */
+ public static Map<String, String> paraFilter(Map<String, String> sArray) {
+ Map<String, String> result = new HashMap<>(sArray.size());
+ if (sArray.isEmpty()) {
+ return result;
+ }
+ for (String key : sArray.keySet()) {
+ String value = sArray.get(key);
+ if (value == null || value.isEmpty() || key.equalsIgnoreCase("sign")) {
+ continue;
+ }
+ result.put(key, value);
+ }
+ return result;
+ }
+
+ /**
+ * <一句话功能简述>
+ * <功能详细描述>将map转成String
+ *
+ * @see [类、类#方法、类#成员]
+ */
+ public static String payParamsToString(Map<String, String> payParams) {
+ return payParamsToString(payParams, false);
+ }
+
+ public static String payParamsToString(Map<String, String> payParams, boolean encoding) {
+ return payParamsToString(new StringBuilder(), payParams, encoding);
+ }
+
+ public static String payParamsToString(StringBuilder sb, Map<String, String> payParams, boolean encoding) {
+ buildPayParams(sb, payParams, encoding);
+ return sb.toString();
+ }
+
+ public static void buildPayParams(StringBuilder sb, Map<String, String> payParams, boolean encoding) {
+ List<String> keys = new ArrayList<>(payParams.keySet());
+ Collections.sort(keys);
+ for (String key : keys) {
+ sb.append(key).append("=");
+ if (encoding) {
+ sb.append(urlEncode(payParams.get(key)));
+ } else {
+ sb.append(payParams.get(key));
+ }
+ sb.append("&");
+ }
+ sb.setLength(sb.length() - 1);
+ }
+
+ public static String urlEncode(String str) {
+ try {
+ return URLEncoder.encode(str, "UTF-8");
+ } catch (Throwable e) {
+ return str;
+ }
+ }
+
+ public static Element readerXml(String body, String encode) throws DocumentException {
+ SAXReader reader = new SAXReader(false);
+ InputSource source = new InputSource(new StringReader(body));
+ source.setEncoding(encode);
+ Document doc = reader.read(source);
+ return doc.getRootElement();
+ }
+
+}
+
diff --git a/oying-system/src/main/java/com/oying/modules/hwc/utils/XmlUtils.java b/oying-system/src/main/java/com/oying/modules/hwc/utils/XmlUtils.java
new file mode 100644
index 0000000..783c326
--- /dev/null
+++ b/oying-system/src/main/java/com/oying/modules/hwc/utils/XmlUtils.java
@@ -0,0 +1,138 @@
+package com.oying.modules.hwc.utils;
+
+import com.oying.exception.BadRequestException;
+import org.dom4j.Document;
+import org.dom4j.Element;
+import org.dom4j.io.SAXReader;
+import org.xml.sax.InputSource;
+
+import javax.servlet.ServletInputStream;
+import javax.servlet.http.HttpServletRequest;
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.InputStreamReader;
+import java.util.*;
+
+/**
+ * ClassName:Xml
+ * Function: XML的工具方法
+ * Date: 2014-8-10 下午10:48:21
+ */
+@SuppressWarnings({"rawtypes", "unchecked"})
+public class XmlUtils {
+
+ /**
+ * <一句话功能简述>
+ * <功能详细描述>request转字符串
+ *
+ * @see [类、类#方法、类#成员]
+ */
+ public static String parseRequest(HttpServletRequest request) {
+ StringBuilder body = new StringBuilder();
+ try {
+ ServletInputStream inputStream = request.getInputStream();
+ BufferedReader br = new BufferedReader(new InputStreamReader(inputStream));
+ while (true) {
+ String info = br.readLine();
+ if (info == null) {
+ break;
+ }
+ if (body.length() == 0) {
+ body = new StringBuilder(info);
+ } else {
+ body.append(info);
+ }
+ }
+ } catch (Exception e) {
+ throw new BadRequestException("读取数据流异常:" + e.getMessage());
+ }
+ return body.toString();
+ }
+
+ public static String parseXML(SortedMap<String, String> parameters) {
+ StringBuilder sb = new StringBuilder();
+ sb.append("<xml>");
+ Set es = parameters.entrySet();
+ for (Object e : es) {
+ Map.Entry entry = (Map.Entry) e;
+ String k = (String) entry.getKey();
+ String v = (String) entry.getValue();
+ if (null != v && !v.isEmpty() && !"appkey".equals(k)) {
+ sb.append("<").append(k).append(">").append(parameters.get(k)).append("</").append(k).append(">\n");
+ }
+ }
+ sb.append("</xml>");
+ return sb.toString();
+ }
+
+ /**
+ * 从request中获得参数Map,并返回可读的Map
+ */
+ public static SortedMap getParameterMap(HttpServletRequest request) {
+ // 参数Map
+ Map properties = request.getParameterMap();
+ // 返回值Map
+ SortedMap returnMap = new TreeMap();
+ Iterator entries = properties.entrySet().iterator();
+ Map.Entry entry;
+ String name;
+ String value = "";
+ while (entries.hasNext()) {
+ entry = (Map.Entry) entries.next();
+ name = (String) entry.getKey();
+ Object valueObj = entry.getValue();
+ if (null == valueObj) {
+ value = "";
+ } else if (valueObj instanceof String[]) {
+ String[] values = (String[]) valueObj;
+ for (String s : values) {
+ value = s + ",";
+ }
+ value = value.substring(0, value.length() - 1);
+ } else {
+ value = valueObj.toString();
+ }
+ returnMap.put(name, value.trim());
+ }
+ returnMap.remove("method");
+ return returnMap;
+ }
+
+ /**
+ * 转XML map
+ */
+ public static Map<String, String> toMap(byte[] xmlBytes, String charset) throws Exception {
+ SAXReader reader = new SAXReader(false);
+ InputSource source = new InputSource(new ByteArrayInputStream(xmlBytes));
+ source.setEncoding(charset);
+ Document doc = reader.read(source);
+ return XmlUtils.toMap(doc.getRootElement());
+ }
+
+ /**
+ * 转MAP
+ */
+ public static Map<String, String> toMap(Element element) {
+ Map<String, String> rest = new HashMap<>();
+ List<Element> els = element.elements();
+ for (Element el : els) {
+ rest.put(el.getName().toLowerCase(), el.getTextTrim());
+ }
+ return rest;
+ }
+
+ public static String toXml(Map<String, String> params) {
+ StringBuilder buf = new StringBuilder();
+ List<String> keys = new ArrayList<>(params.keySet());
+ Collections.sort(keys);
+ buf.append("<xml>");
+ for (String key : keys) {
+ buf.append("<").append(key).append(">");
+ buf.append("<![CDATA[").append(params.get(key)).append("]]>");
+ buf.append("</").append(key).append(">\n");
+ }
+ buf.append("</xml>");
+ return buf.toString();
+ }
+}
+
diff --git a/pom.xml b/pom.xml
index 1264ba2..6005c46 100644
--- a/pom.xml
+++ b/pom.xml
@@ -197,6 +197,13 @@
<version>2.12.2</version>
</dependency>
+ <!-- dom4j 使用安全的 2.x 版本 -->
+ <dependency>
+ <groupId>org.dom4j</groupId>
+ <artifactId>dom4j</artifactId>
+ <version>2.1.4</version>
+ </dependency>
+
<!-- fastjson2 -->
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
--
Gitblit v1.9.3