From 9a72fe01512e094430c36b14f0d540ad2533a7c7 Mon Sep 17 00:00:00 2001 From: 777 <123@qwe.com> Date: Tue, 8 Jul 2025 02:08:12 +0800 Subject: [PATCH] V13 --- .../java/com/ruoyi/cai/trdpay/PayMd5Util.java | 22 +++ .../com/ruoyi/cai/trdpay/PaySignUtil.java | 69 ++++++++ .../com/ruoyi/cai/trdpay/TrdPayTypeEnum.java | 6 +- .../cai/trdpay/handle/PayTrdV13Service.java | 158 ++++++++++++++++++ .../java/com/ruoyi/cai/util/MapUtils.java | 15 ++ .../com/ruoyi/cai/util/RestTemplateUtil.java | 14 ++ 6 files changed, 283 insertions(+), 1 deletion(-) create mode 100644 ruoyi-cai/src/main/java/com/ruoyi/cai/trdpay/PaySignUtil.java create mode 100644 ruoyi-cai/src/main/java/com/ruoyi/cai/trdpay/handle/PayTrdV13Service.java create mode 100644 ruoyi-cai/src/main/java/com/ruoyi/cai/util/MapUtils.java diff --git a/ruoyi-cai/src/main/java/com/ruoyi/cai/trdpay/PayMd5Util.java b/ruoyi-cai/src/main/java/com/ruoyi/cai/trdpay/PayMd5Util.java index 4de8d26f..ccfc7fe6 100644 --- a/ruoyi-cai/src/main/java/com/ruoyi/cai/trdpay/PayMd5Util.java +++ b/ruoyi-cai/src/main/java/com/ruoyi/cai/trdpay/PayMd5Util.java @@ -1,5 +1,9 @@ package com.ruoyi.cai.trdpay; +import cn.hutool.core.codec.Base64; +import cn.hutool.crypto.SecureUtil; +import cn.hutool.crypto.asymmetric.Sign; +import cn.hutool.crypto.asymmetric.SignAlgorithm; import cn.hutool.crypto.digest.DigestUtil; import org.apache.commons.lang3.StringUtils; import org.springframework.util.LinkedMultiValueMap; @@ -41,6 +45,24 @@ public class PayMd5Util { return resp; } + public static MultiValueMap createParamsOfMapMD5(Map params, String key) { + return createParamsOfMapMD5(params,key,"","sign"); + } + + public static MultiValueMap createParamsOfMapMD5(Map params, String key, String keyPre, String signKey) { + MultiValueMap resp = new LinkedMultiValueMap<>(); + List url = new ArrayList<>(); + for (Map.Entry entry : params.entrySet()) { + url.add(entry.getKey() + "=" + entry.getValue()); + resp.add(entry.getKey(), entry.getValue()); + } + url = url.stream().sorted().collect(Collectors.toList()); + String stringSignTemp = StringUtils.join(url, "&") + keyPre + key; + String sign = DigestUtil.md5Hex(stringSignTemp).toUpperCase(); + resp.add(signKey,sign); + return resp; + } + public static String createParams(Map params, String key) { List url = new ArrayList<>(); for (Map.Entry entry : params.entrySet()) { diff --git a/ruoyi-cai/src/main/java/com/ruoyi/cai/trdpay/PaySignUtil.java b/ruoyi-cai/src/main/java/com/ruoyi/cai/trdpay/PaySignUtil.java new file mode 100644 index 00000000..daae278b --- /dev/null +++ b/ruoyi-cai/src/main/java/com/ruoyi/cai/trdpay/PaySignUtil.java @@ -0,0 +1,69 @@ +package com.ruoyi.cai.trdpay; + +import cn.hutool.core.codec.Base64; +import cn.hutool.crypto.SecureUtil; +import cn.hutool.crypto.asymmetric.Sign; +import cn.hutool.crypto.asymmetric.SignAlgorithm; +import cn.hutool.crypto.digest.DigestUtil; +import org.apache.commons.lang3.StringUtils; + +import java.util.*; +import java.util.stream.Collectors; + +public class PaySignUtil { + + public static String createMD5ToUpperCase(Map params,String subKey) { + return createMD5(params,subKey).toUpperCase(); + } + + public static String createMD5ToLowerCase(Map params,String subKey) { + return createMD5(params,subKey).toLowerCase(); + } + + public static String createMD5(Map params,String subKey) { + List url = new ArrayList<>(); + for (Map.Entry entry : params.entrySet()) { + url.add(entry.getKey() + "=" + entry.getValue()); + } + url = url.stream().sorted().collect(Collectors.toList()); + String stringSignTemp = StringUtils.join(url, "&") + subKey; + return DigestUtil.md5Hex(stringSignTemp); + } + + public static boolean checkRSA256(Map params, String privateKey, String publicKey,String sign,String ... ignoreKeys) { + if(sign == null){ + return false; + } + Set ignoreKeySet = ignoreKeys == null ? new HashSet<>() : new HashSet<>(Arrays.asList(ignoreKeys)); + List url = new ArrayList<>(); + for (Map.Entry entry : params.entrySet()) { + if(ignoreKeySet.contains(entry.getKey())){ + continue; + } + url.add(entry.getKey() + "=" + entry.getValue()); + } + url = url.stream().sorted().collect(Collectors.toList()); + String stringSignTemp = StringUtils.join(url, "&"); + String signLLL = rsa256(stringSignTemp, privateKey, publicKey); + return signLLL.equals(sign); + } + + public static String createRSA256(Map params, String privateKey, String publicKey) { + List url = new ArrayList<>(); + for (Map.Entry entry : params.entrySet()) { + url.add(entry.getKey() + "=" + entry.getValue()); + } + url = url.stream().sorted().collect(Collectors.toList()); + String stringSignTemp = StringUtils.join(url, "&"); + return rsa256(stringSignTemp,privateKey,publicKey); + } + + public static String rsa256(String privateKey,String publicKey,String data) { + String privateKeyBase = Base64.encode(privateKey); + String publicKeyBase = Base64.encode(publicKey); + Sign sign = SecureUtil.sign(SignAlgorithm.SHA256withRSA, privateKeyBase, publicKeyBase); + String s = sign.signHex(data); + return Base64.encode(s); + } + +} diff --git a/ruoyi-cai/src/main/java/com/ruoyi/cai/trdpay/TrdPayTypeEnum.java b/ruoyi-cai/src/main/java/com/ruoyi/cai/trdpay/TrdPayTypeEnum.java index 02dda45e..42048fa2 100644 --- a/ruoyi-cai/src/main/java/com/ruoyi/cai/trdpay/TrdPayTypeEnum.java +++ b/ruoyi-cai/src/main/java/com/ruoyi/cai/trdpay/TrdPayTypeEnum.java @@ -128,7 +128,11 @@ public enum TrdPayTypeEnum { * 支付宝 8 微信 4 */ V11("http://bgxa.peiqi.zhifusg.com","/Pay_Index.html","/Pay_Trade_query.html","/api/pay/trd/notify/v11","OK"), - V12("https://cashapi.sandpay.com.cn","/gateway/trade","/Pay_Trade_query.html","/api/pay/trd/notify/v11","OK"), + V12("https://cashapi.sandpay.com.cn","/gateway/trade","/Pay_Trade_query.html","/api/pay/trd/notify/v12","OK"), + /** + * https://pp123.bghyvwk.cn/doc/pay_create.html + */ + V13("https://pp123.bghyvwk.cn","/api/pay/create","/api/pay/query","/api/pay/trd/notify/v12","success"), ; private final String gatewayUrl; diff --git a/ruoyi-cai/src/main/java/com/ruoyi/cai/trdpay/handle/PayTrdV13Service.java b/ruoyi-cai/src/main/java/com/ruoyi/cai/trdpay/handle/PayTrdV13Service.java new file mode 100644 index 00000000..b5cd38e1 --- /dev/null +++ b/ruoyi-cai/src/main/java/com/ruoyi/cai/trdpay/handle/PayTrdV13Service.java @@ -0,0 +1,158 @@ +package com.ruoyi.cai.trdpay.handle; + +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONObject; +import com.ruoyi.cai.domain.PayTrdConfig; +import com.ruoyi.cai.pay.PayManager; +import com.ruoyi.cai.pay.PayOrderInfoDTO; +import com.ruoyi.cai.pay.PayReturnResp; +import com.ruoyi.cai.service.OrderLogsService; +import com.ruoyi.cai.service.PayTrdConfigService; +import com.ruoyi.cai.trdpay.PayMd5Util; +import com.ruoyi.cai.trdpay.PaySignUtil; +import com.ruoyi.cai.trdpay.PayTrdService; +import com.ruoyi.cai.trdpay.TrdPayTypeEnum; +import com.ruoyi.cai.trdpay.dto.NotifyResp; +import com.ruoyi.cai.util.MapUtils; +import com.ruoyi.cai.util.RestTemplateUtil; +import com.ruoyi.common.exception.ServiceException; +import com.ruoyi.common.utils.ServletUtils; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.util.MultiValueMap; + +import java.util.HashMap; +import java.util.Map; + +@Service +@Slf4j +public class PayTrdV13Service implements PayTrdService { + + @Autowired + private OrderLogsService orderLogsService; + @Autowired + private PayManager payManager; + @Autowired + private PayTrdConfigService payTrdConfigService; + + @Override + public TrdPayTypeEnum getType() { + return TrdPayTypeEnum.V13; + } + + public static final String PUBLIC_KEY = ""; + + private String v1(PayOrderInfoDTO payOrderInfoDTO, PayTrdConfig payTrdConfig, boolean wx){ + TrdPayTypeEnum type = getType(); + Map params = new HashMap<>(); + params.put("pid", payTrdConfig.getMchId()); + params.put("device", "mobile"); + if(wx){ + params.put("type", "wxpay"); + }else{ + params.put("type", "alipay"); + } + params.put("out_trade_no", payOrderInfoDTO.getOrderNo()); + String notifyUrl = type.getNotifyUrl(payTrdConfig.getNotifyUrl(), wx); + params.put("notify_url", notifyUrl); +// params.put("return_url", notifyUrl); + params.put("name", payOrderInfoDTO.getSubject()); + params.put("money", payOrderInfoDTO.getPriceYuanStr()); + params.put("clientip", ServletUtils.getClientIP()); + String sign = PaySignUtil.createMD5ToLowerCase(params, ""); + MultiValueMap multiValueMap = MapUtils.mapToMultiValueMap(params); + multiValueMap.add("sign", sign); + multiValueMap.add("sign_type", "MD5"); + String gatewayUrl = getGatewayUrl(payTrdConfig); + String createOrderUrl = gatewayUrl + type.getCreateOrderUrl(); + return RestTemplateUtil.postFormData(createOrderUrl, multiValueMap); + } + + @Override + public PayReturnResp createOrderAli(PayOrderInfoDTO payOrderInfoDTO, PayTrdConfig payTrdConfig, boolean wx) { + TrdPayTypeEnum type = getType(); + Map params = new HashMap<>(); + params.put("param", payTrdConfig.getId()+""); + params.put("pid", payTrdConfig.getMchId()); + params.put("method", "jump"); + params.put("device", "mobile"); + if(wx){ + params.put("type", "wxpay"); + }else{ + params.put("type", "alipay"); + } + params.put("out_trade_no", payOrderInfoDTO.getOrderNo()); + String notifyUrl = type.getNotifyUrl(payTrdConfig.getNotifyUrl(), wx); + params.put("notify_url", notifyUrl); +// params.put("return_url", notifyUrl); + params.put("name", payOrderInfoDTO.getSubject()); + params.put("money", payOrderInfoDTO.getPriceYuanStr()); + params.put("clientip", ServletUtils.getClientIP()); + params.put("timestamp", System.currentTimeMillis()/1000+""); + String rsa = PaySignUtil.createRSA256(params, payTrdConfig.getSign(), PUBLIC_KEY); + MultiValueMap multiValueMap = MapUtils.mapToMultiValueMap(params); + MultiValueMap map = PayMd5Util.createParamsOfMapMD5(params, payTrdConfig.getSign()); + multiValueMap.add("sign", rsa); + multiValueMap.add("sign_type", "RSA"); + String gatewayUrl = getGatewayUrl(payTrdConfig); + String createOrderUrl = gatewayUrl + type.getCreateOrderUrl(); + String body = RestTemplateUtil.postFormData(createOrderUrl, multiValueMap); + JSONObject jsonObject = JSON.parseObject(body); + boolean success = checkSuccess(jsonObject); + orderLogsService.createAliPayLogs(payOrderInfoDTO.getOrderNo(), createOrderUrl+JSON.toJSONString(map), jsonObject, success, type, getStepName(wx)); + if(!success){ + log.info("第三方支付失败 V13 统一支付失败失败 url={} params={} body={}, payTrdConfig={}", createOrderUrl,JSON.toJSONString(map), body, JSON.toJSONString(payTrdConfig)); + throw new ServiceException("调用支付失败"); + } + String payUrl = jsonObject.getString("pay_info"); + return PayReturnResp.createH5(payUrl); + } + + private boolean checkSuccess(JSONObject jsonObject) { + if(jsonObject == null){ + return false; + } + if(!"0".equals(jsonObject.getString("code"))){ + return false; + } + return true; + } + + @Override + public NotifyResp getNotifyResp(Map sourceData) { + String sign = sourceData.get("sign"); + String mchOrderNo = sourceData.get("trade_no"); + String payOrderId = sourceData.get("out_trade_no"); + String status = sourceData.get("trade_status"); + String params = sourceData.get("params"); + NotifyResp resp = new NotifyResp(); + resp.setOrderNo(mchOrderNo); + resp.setTrdOrderNo(payOrderId); + resp.setPayTypeEnum(getType()); + resp.setSourceData(sourceData); + PayTrdConfig payTrdConfig = payTrdConfigService.getById(params); + if(payTrdConfig == null){ + resp.setSuccess(false); + return resp; + } + boolean checked = PaySignUtil.checkRSA256(sourceData, PUBLIC_KEY, payTrdConfig.getSign(), sign, "sign", "sign_type"); + if(!checked){ + resp.setSuccess(false); + return resp; + } + resp.setSuccess("TRADE_SUCCESS".equals(status)); + return resp; + } + + @Override + public JSONObject queryOrder(String orderNo, PayTrdConfig payTrdConfig) { + throw new ServiceException("V13不支持订单查询"); + } + + @Override + public JSONObject resetOrder(String orderNo, PayTrdConfig payTrdConfig, boolean updateData) { + throw new ServiceException("V13不支持订单查询"); + } + +} diff --git a/ruoyi-cai/src/main/java/com/ruoyi/cai/util/MapUtils.java b/ruoyi-cai/src/main/java/com/ruoyi/cai/util/MapUtils.java new file mode 100644 index 00000000..0568bc43 --- /dev/null +++ b/ruoyi-cai/src/main/java/com/ruoyi/cai/util/MapUtils.java @@ -0,0 +1,15 @@ +package com.ruoyi.cai.util; + +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; + +import java.util.Map; + +public class MapUtils { + + public static MultiValueMap mapToMultiValueMap(Map map) { + MultiValueMap multiValueMap = new LinkedMultiValueMap<>(); + map.forEach(multiValueMap::add); + return multiValueMap; + } +} diff --git a/ruoyi-cai/src/main/java/com/ruoyi/cai/util/RestTemplateUtil.java b/ruoyi-cai/src/main/java/com/ruoyi/cai/util/RestTemplateUtil.java index 58be5f92..4badac59 100644 --- a/ruoyi-cai/src/main/java/com/ruoyi/cai/util/RestTemplateUtil.java +++ b/ruoyi-cai/src/main/java/com/ruoyi/cai/util/RestTemplateUtil.java @@ -71,4 +71,18 @@ public class RestTemplateUtil { HttpEntity> request = new HttpEntity<>(map, headers); return restTemplate.postForObject(url, request, String.class); } + + public static String postFormData(String url, MultiValueMap map) { + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); + HttpEntity> request = new HttpEntity<>(map, headers); + return restTemplate.postForObject(url, request, String.class); + } + + public static String postFormDataNoSSL(String url, MultiValueMap map) { + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); + HttpEntity> request = new HttpEntity<>(map, headers); + return NO_SSL_REST_TEMPLATE.postForObject(url, request, String.class); + } }