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