oying-common/src/main/java/com/oying/utils/enums/PayReturnStateEnum.java
New file @@ -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(); } } oying-common/src/main/java/com/oying/utils/enums/PayStateEnum.java
New file @@ -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(); } } oying-common/src/main/java/com/oying/utils/enums/PayTypeEnum.java
New file @@ -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; } } oying-system/src/main/java/com/oying/modules/hwc/domain/HwcResponse.java
New file @@ -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; } oying-system/src/main/java/com/oying/modules/hwc/rest/SwiftPassController.java
New file @@ -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); } } oying-system/src/main/java/com/oying/modules/hwc/service/SwiftPassService.java
New file @@ -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); } oying-system/src/main/java/com/oying/modules/hwc/service/impl/SwiftPassServiceImpl.java
New file @@ -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()); } } } oying-system/src/main/java/com/oying/modules/hwc/utils/MD5.java
New file @@ -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); } } } oying-system/src/main/java/com/oying/modules/hwc/utils/RSAUtil.java
New file @@ -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; } } oying-system/src/main/java/com/oying/modules/hwc/utils/SignUtil.java
New file @@ -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); } } oying-system/src/main/java/com/oying/modules/hwc/utils/SignUtils.java
New file @@ -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(); } } oying-system/src/main/java/com/oying/modules/hwc/utils/XmlUtils.java
New file @@ -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(); } } 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>