1. 支付

This commit is contained in:
邱贞招 2024-09-13 09:20:41 +08:00
parent fb772973e0
commit 9dcaa811f6
15 changed files with 1245 additions and 16 deletions

View File

@ -243,3 +243,29 @@ et:
appcode: 32b6c6445b1a42ed862dd4202392c47d
repairAdmin: wx
operateAdmin: root
# 收银宝
syb:
# 集团ID
orgId: ""
# 商户ID
cusId: 56340307399QTL2
# 应用ID
appId: "00322693"
# MD5密钥
md5AppKey: allinpay888
# Api地址
apiUrl: https://vsp.allinpay.com/apiweb/unitorder
# 签名方式
signType: RSA
# 商户RSA私钥,用于向通联发起请求前进行签名
rsaCusPriKey: MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCkngbbMX1kgcp8ClMDqO3r2lHP8MIdG+lrwDXoMtxLXv/sohyvmZIGv1QKPDZhU3b+tMfjfyvpCQ53Ji885YVocThz7TP8nNnshhNzhFVIQ+LcfakuitHtmsLL0wohU6NiEOi3k9iZkSuTEHgXkJSoVv3yHicjdDfbcq/rsMhx2RLa9yffVgwJY6it9EO73phc6xHP/VrxYjwjF1EXM22HyF7mNvzSdMpl7k8IBm8r8uvuB108J+sL4yPoJqHSXkJYESA9r0vLPH6hmJ534UG/umleBxnJ3l6NplcgM7NOOVNIF8a0Cu2+l+hfW+E/oAEdNImA2H7NVQy3b0vsi0S9AgMBAAECggEAGTIJtRtcSJmX8wfGgHEm5zotWvEL2NY4khIjfhRoxM13yNFgKB1ObSN0GrPwE9HpTN9BF+gTQbHt6Z0pzj2H/34SyKWF1zRvo2S7DetzfSHM03gVQKclLeOJzJYSvxqUz3HsmIpztL90Jqh25Q5vPlvANwJX9bR4RymE1QNkDo6OjfSaXvPFK5xuQ+mHmWft2Gbh/OmVONcJ6bIguQvIgyXD3wlmH16Bh1D8trqEHfw6QzpWPF3+N9WHbuFjn/u9oO+HwmVu3s6qmYlrEfvBWzkrcyzDGmUxz9oIEx1Ruh+PS+fn5mfFvmARSe5vVd6ipFw+f4aumJd2mCQbf3J34QKBgQDixyqlMCs2xg+Rv+PmIgO7BTSNzcbmpO0o74OVnSIAJMF99M1TaQYOWoXqXKN2sS8lAUd9Kf48xuXX1lEzDsNZ2kKnLqDd0I28bapZ1hawdrcKMnot2j3gt3LIe2Mq7qjAu6qEjBgA5BC2WjpKuNT++MDE9XEnQdsu8DsT2vDTYQKBgQC51FXo93qcNnGj6JAZvax1x9X/z/jHMczBDfHOhjJwGaogael+uGLwmRCI3cKd0Hau1wp5wZ+ySyWGZsLYs2cB3jqqmRQGV8wHHLZKUCO+agft5zeyu17Rw1hRAxF57Fcrk5LzyMZXeO6ny6apxLPcLEB/phAMgs24CrqRMEMK3QKBgGxXlqognySl7x5EvM5fhcS9sePlYZcjSCJ8ezLpRSsoZECWydmFnV0SJlRXOckk0U8uk0ba3xmONJSvUU/BR9cPjTRt9HDEiFJzWiVdQK3BqaT3hKOPVmybd/0L5c7HAlxBzC9S9szGKK3V4W99alT95qqyUALfVaXRI4hS2ydBAoGAO528RfYHNtBcmsoEWM254ztOEziERltapeC0X5471izVoHp59zZuP2PLoy0Jv5leSe8hJDiS5B8plbmn7t93P2ktBwf/HZ3TDcEPVj3pia/4hUE1ZDiqTb9OuQaz95pzyL4aWe1ifDOG1PwHptU9Inhr5MfAxFK+brm6atPB3l0CgYBMnyT/eaMC2PZS9jYAOBDHGTBKMxX5ddRMcI/dZzspzDNfQ6cjWH5RiXFAU4PIU2o9LbnIJNuwjdD3NspQblwbB2/c28qW4iY08jGna7MRJ46mTQle3varn7hWcQ3LMgP7XavgPDKS/qRRv0xFA/cjbnYGFn4iQp5BxcbGCy5Gdg==
# 通联平台RSA公钥用于请求返回或者通联通知的验签
rsaTlPubKey: MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCm9OV6zH5DYH/ZnAVYHscEELdCNfNTHGuBv1nYYEY9FrOzE0/4kLl9f7Y9dkWHlc2ocDwbrFSm0Vqz0q2rJPxXUYBCQl5yW3jzuKSXif7q1yOwkFVtJXvuhf5WRy+1X5FOFoMvS7538No0RpnLzmNi3ktmiqmhpcY/1pmt20FHQQIDAQAB
# 商户sm2私钥,用于向通联发起请求前进行签名
sm2CusPriKey: MIGTAgEAMBMGByqGSM49AgEGCCqBHM9VAYItBHkwdwIBAQQgaPcLc/a1wqUa9wpDetd0PtjbPv+ldPq8vPc7UeDmlWegCgYIKoEcz1UBgi2hRANCAASo8eWV80+/3elREuKbSukOwkP+tekaq1bsk9zrreR14RAzQrDJWrq8PBso8Ctpvew51w6aVhFubw+oDFFNLE/w
# 通联平台sm2公钥用于请求返回或者通联通知的验签
sm2TlPubKey: MFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAEBQicgWm0KAMqhO3bdqMUEDrKQvYg8cCXHhdGwq7CGE6oJDzJ1P/94HpuVdBf1KidmPxr7HOH+0DAnpeCcx9TcQ==
# 支付通知地址
notifyUrl: http://124.221.246.124:2290/app/pay/notify/tl

View File

@ -0,0 +1,49 @@
package com.ruoyi.common.pay.syb.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* @author wjh
* 2024/9/9
*/
@Component
@ConfigurationProperties(prefix = "syb")
@Data
public class SybConfig {
// 集团ID
private String orgId;
// 商户ID
private String cusId;
// 应用ID
private String appId;
// MD5密钥
private String md5AppKey;
// Api地址
private String apiUrl;
// 签名方式
private String signType;
// 商户RSA私钥,用于向通联发起请求前进行签名
private String rsaCusPriKey;
// 通联平台RSA公钥用于请求返回或者通联通知的验签
private String rsaTlPubKey;
// 商户sm2私钥,用于向通联发起请求前进行签名
private String sm2CusPriKey;
// 通联平台sm2公钥用于请求返回或者通联通知的验签
private String sm2TlPubKey;
// 通知地址
private String notifyUrl;
}

View File

@ -0,0 +1,26 @@
package com.ruoyi.common.pay.syb.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 收银宝交易类型
* @author wjh
* 2024/9/10
*/
@Getter
@AllArgsConstructor
public enum SybTrxCode {
WX_PAY("VSP501", "微信支付"),
WX_REFUND("VSP503", "微信支付退款");
private final String code;
private final String msg;
public boolean equalsCode(String code) {
return this.getCode().equals(code);
}
}

View File

@ -0,0 +1,41 @@
package com.ruoyi.common.pay.syb.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 交易结果
* @author wjh
* 2024/9/10
*/
@Getter
@AllArgsConstructor
public enum SybTrxStatus {
SUCCESS("0000", "交易成功"),
PROCESSING("2000", "交易处理中"),
NOT_EXIST("1001", "交易不存在"),
REPEAT("3888", "流水号重复"),
CHANNEL_MERCHANT_ERROR("3099", "渠道商户错误"),
TRANSACTION_AMOUNT_LESS_THAN_SERVICE_CHARGE("3014", "交易金额小于应收手续费"),
CHECK_REAL_NAME_INFO_FAILED("3031", "校验实名信息失败"),
TRANSACTION_NOT_PAID("3088", "交易未支付"),
TRANSACTION_REVERSED("3089", "撤销异常"),
TRANSACTION_REVOKED("3050", "交易已被撤销");
public static boolean isSuccess(String code) {
return SUCCESS.getCode().equals(code);
}
public static SybTrxStatus getByCode(String code) {
for (SybTrxStatus value : SybTrxStatus.values()) {
if (value.getCode().equals(code)) {
return value;
}
}
return null;
}
private final String code;
private final String msg;
}

View File

@ -0,0 +1,53 @@
package com.ruoyi.common.pay.syb.service;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
@RestController
@RequestMapping("/syb")
public class ApiTestV2 extends BaseController {
@Autowired
private SybPayClient service;
@GetMapping("/scanPay")
public AjaxResult testScanPay() throws Exception {
// TODO Auto-generated method stub
String reqsn = String.valueOf(System.currentTimeMillis());
Map<String, String> map = service.scanPay(1, reqsn, "标题", "备注", "134775931316089668", "", "", "", "");
return success(map);
}
@GetMapping("/query")
public AjaxResult testQuery(@RequestParam String trxid) throws Exception {
Map<String, String> map = service.query("", trxid);
return success(map);
}
@PostMapping("/refund")
public AjaxResult testRefund() throws Exception {
String reqsn = String.valueOf(System.currentTimeMillis());
Map<String, String> map = service.refund(1, reqsn, "", "20160712167578.2547");
return success(map);
}
@PostMapping("/cancel")
public AjaxResult testCancel() throws Exception {
String reqsn = String.valueOf(System.currentTimeMillis());
Map<String, String> map = service.cancel(1, reqsn, "112094120001088316", "");
return success(map);
}
@PostMapping("/pay")
public AjaxResult testPay() throws Exception {
String reqsn = String.valueOf(System.currentTimeMillis());
Map<String, String> map = service.pay(1L, reqsn, "W06", "标题", "备注", "ol5kD7eeXNGeYE5z7uIhk12K-rBA", "123", "https://test.allinpaygd.com/JWeb/NotifyServlet", "", "", "", "", "", "", "", "", "", "", "", "");
return success(map);
}
}

View File

@ -0,0 +1,272 @@
package com.ruoyi.common.pay.syb.service;
import com.alibaba.fastjson2.JSON;
import com.ruoyi.common.pay.syb.config.SybConfig;
import com.ruoyi.common.pay.syb.util.HttpConnectionUtil;
import com.ruoyi.common.pay.syb.util.SybUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Map;
import java.util.TreeMap;
@Component
public class SybPayClient {
@Autowired
private SybConfig sybConfig;
/**
*
* @param trxamt
* @param reqsn
* @param paytype
* @param body
* @param remark
* @param acct
* @param validtime
* @param notify_url
* @param limit_pay
* @param idno
* @param truename
* @param asinfo
* @param sub_appid
* @param goods_tag 单品优惠信息
* @param chnlstoreid
* @param subbranch
* @param cusip 限云闪付JS支付业务
* @param fqnum 限支付宝分期业务
* @return
* @throws Exception
*/
public Map<String,String> pay(long trxamt, String reqsn, String paytype, String body, String remark, String acct, String validtime, String notify_url, String limit_pay,
String idno, String truename, String asinfo, String sub_appid, String goods_tag, String benefitdetail, String chnlstoreid, String subbranch, String extendparams, String cusip, String fqnum) throws Exception{
HttpConnectionUtil http = new HttpConnectionUtil(sybConfig.getApiUrl()+"/pay");
http.init();
TreeMap<String,String> params = new TreeMap<String,String>();
if(!SybUtil.isEmpty(sybConfig.getOrgId())) {
params.put("orgid", sybConfig.getOrgId());
}
params.put("cusid", sybConfig.getCusId());
params.put("appid", sybConfig.getAppId());
params.put("version", "11");
params.put("trxamt", String.valueOf(trxamt));
params.put("reqsn", reqsn);
params.put("paytype", paytype);
params.put("randomstr", SybUtil.getValidatecode(8));
params.put("body", body);
params.put("remark", remark);
params.put("validtime", validtime);
params.put("acct", acct);
params.put("notify_url", notify_url);
params.put("limit_pay", limit_pay);
params.put("sub_appid", sub_appid);
params.put("goods_tag", goods_tag);
params.put("benefitdetail", benefitdetail);
params.put("chnlstoreid", chnlstoreid);
params.put("subbranch", subbranch);
params.put("extendparams", extendparams);
params.put("cusip", cusip);
params.put("fqnum", fqnum);
params.put("idno", idno);
params.put("truename", truename);
params.put("asinfo", asinfo);
params.put("signtype", sybConfig.getSignType());
String appkey = "";
if(sybConfig.getSignType().equals("RSA")) {
appkey = sybConfig.getRsaCusPriKey();
} else if(sybConfig.getSignType().equals("SM2")) {
appkey = sybConfig.getSm2CusPriKey();
} else {
appkey = sybConfig.getMd5AppKey();
}
params.put("sign", SybUtil.unionSign(params,appkey,sybConfig.getSignType()));
byte[] bys = http.postParams(params, true);
String result = new String(bys,"UTF-8");
Map<String,String> map = handleResult(result);
return map;
}
public Map<String,String> cancel(long trxamt,String reqsn,String oldtrxid,String oldreqsn) throws Exception{
HttpConnectionUtil http = new HttpConnectionUtil(sybConfig.getApiUrl()+"/cancel");
http.init();
TreeMap<String,String> params = new TreeMap<String,String>();
if(!SybUtil.isEmpty(sybConfig.getOrgId()))
params.put("orgid", sybConfig.getOrgId());
params.put("cusid", sybConfig.getCusId());
params.put("appid", sybConfig.getAppId());
params.put("version", "11");
params.put("trxamt", String.valueOf(trxamt));
params.put("reqsn", reqsn);
params.put("oldtrxid", oldtrxid);
params.put("oldreqsn", oldreqsn);
params.put("randomstr", SybUtil.getValidatecode(8));
params.put("signtype", sybConfig.getSignType());
String appkey = "";
if(sybConfig.getSignType().equals("RSA"))
appkey = sybConfig.getRsaCusPriKey();
else if(sybConfig.getSignType().equals("SM2"))
appkey = sybConfig.getSm2CusPriKey();
else
appkey = sybConfig.getMd5AppKey();
params.put("sign", SybUtil.unionSign(params,appkey,sybConfig.getSignType()));
byte[] bys = http.postParams(params, true);
String result = new String(bys,"UTF-8");
Map<String,String> map = handleResult(result);
return map;
}
/**
* 关闭支付订单
*/
public Map<String,String> close(String oldtrxid,String oldreqsn) throws Exception{
HttpConnectionUtil http = new HttpConnectionUtil(sybConfig.getApiUrl()+"/close");
http.init();
TreeMap<String,String> params = new TreeMap<String,String>();
if(!SybUtil.isEmpty(sybConfig.getOrgId()))
params.put("orgid", sybConfig.getOrgId());
params.put("cusid", sybConfig.getCusId());
params.put("appid", sybConfig.getAppId());
params.put("version", "11");
params.put("oldtrxid", oldtrxid);
params.put("oldreqsn", oldreqsn);
params.put("randomstr", SybUtil.getValidatecode(8));
params.put("signtype", sybConfig.getSignType());
String appkey = "";
if(sybConfig.getSignType().equals("RSA"))
appkey = sybConfig.getRsaCusPriKey();
else if(sybConfig.getSignType().equals("SM2"))
appkey = sybConfig.getSm2CusPriKey();
else
appkey = sybConfig.getMd5AppKey();
params.put("sign", SybUtil.unionSign(params,appkey,sybConfig.getSignType()));
byte[] bys = http.postParams(params, true);
String result = new String(bys,"UTF-8");
Map<String,String> map = handleResult(result);
return map;
}
public Map<String,String> refund(long trxamt,String reqsn,String oldtrxid,String oldreqsn) throws Exception{
HttpConnectionUtil http = new HttpConnectionUtil(sybConfig.getApiUrl()+"/refund");
http.init();
TreeMap<String,String> params = new TreeMap<String,String>();
if(!SybUtil.isEmpty(sybConfig.getOrgId()))
params.put("orgid", sybConfig.getOrgId());
params.put("cusid", sybConfig.getCusId());
params.put("appid", sybConfig.getAppId());
params.put("version", "11");
params.put("trxamt", String.valueOf(trxamt));
params.put("reqsn", reqsn);
params.put("oldreqsn", oldreqsn);
params.put("oldtrxid", oldtrxid);
params.put("randomstr", SybUtil.getValidatecode(8));
params.put("signtype", sybConfig.getSignType());
String appkey = "";
if(sybConfig.getSignType().equals("RSA"))
appkey = sybConfig.getRsaCusPriKey();
else if(sybConfig.getSignType().equals("SM2"))
appkey = sybConfig.getSm2CusPriKey();
else
appkey = sybConfig.getMd5AppKey();
params.put("sign", SybUtil.unionSign(params,appkey,sybConfig.getSignType()));
byte[] bys = http.postParams(params, true);
String result = new String(bys,"UTF-8");
Map<String,String> map = handleResult(result);
return map;
}
public Map<String,String> query(String reqsn,String trxid) throws Exception{
HttpConnectionUtil http = new HttpConnectionUtil(sybConfig.getApiUrl()+"/query");
http.init();
TreeMap<String,String> params = new TreeMap<String,String>();
if(!SybUtil.isEmpty(sybConfig.getOrgId()))
params.put("orgid", sybConfig.getOrgId());
params.put("cusid", sybConfig.getCusId());
params.put("appid", sybConfig.getAppId());
params.put("version", "11");
params.put("reqsn", reqsn);
params.put("trxid", trxid);
params.put("randomstr", SybUtil.getValidatecode(8));
params.put("signtype", sybConfig.getSignType());
String appkey = "";
if(sybConfig.getSignType().equals("RSA"))
appkey = sybConfig.getRsaCusPriKey();
else if(sybConfig.getSignType().equals("SM2"))
appkey = sybConfig.getSm2CusPriKey();
else
appkey = sybConfig.getMd5AppKey();
params.put("sign", SybUtil.unionSign(params,appkey,sybConfig.getSignType()));
byte[] bys = http.postParams(params, true);
String result = new String(bys,"UTF-8");
Map<String,String> map = handleResult(result);
return map;
}
@SuppressWarnings({ "rawtypes", "unchecked" })
public Map<String,String> handleResult(String result) throws Exception{
System.out.println("ret:"+result);
Map map = JSON.parseObject(result, Map.class);
if(map == null){
throw new Exception("返回数据错误");
}
if("SUCCESS".equals(map.get("retcode"))){
TreeMap tmap = new TreeMap();
tmap.putAll(map);
String appkey = "";
if(sybConfig.getSignType().equals("RSA"))
appkey = sybConfig.getRsaTlPubKey();
else if(sybConfig.getSignType().equals("SM2"))
appkey = sybConfig.getSm2TlPubKey();
else
appkey = sybConfig.getMd5AppKey();
if(SybUtil.validSign(tmap, appkey, sybConfig.getSignType())){
System.out.println("签名成功");
return map;
}else{
throw new Exception("验证签名失败");
}
}else{
throw new Exception(map.get("retmsg").toString());
}
}
public Map<String, String> scanPay(long trxamt,String reqsn,String body,String remark,String authcode,String limit_pay,String idno,String truename,String asinfo) throws Exception{
// TODO Auto-generated method stub
HttpConnectionUtil http = new HttpConnectionUtil(sybConfig.getApiUrl()+"/scanqrpay");
http.init();
TreeMap<String,String> params = new TreeMap<String,String>();
if(!SybUtil.isEmpty(sybConfig.getOrgId()))
params.put("orgid", sybConfig.getOrgId());
params.put("cusid", sybConfig.getCusId());
params.put("appid", sybConfig.getAppId());
params.put("version", "11");
params.put("trxamt", String.valueOf(trxamt));
params.put("reqsn", reqsn);
params.put("randomstr", SybUtil.getValidatecode(8));
params.put("body", body);
params.put("remark", remark);
params.put("authcode", authcode);
params.put("limit_pay", limit_pay);
params.put("asinfo", asinfo);
params.put("signtype", sybConfig.getSignType());
String appkey = "";
if(sybConfig.getSignType().equals("RSA"))
appkey = sybConfig.getRsaCusPriKey();
else if(sybConfig.getSignType().equals("SM2"))
appkey = sybConfig.getSm2CusPriKey();
else
appkey = sybConfig.getMd5AppKey();
params.put("sign", SybUtil.unionSign(params,appkey,sybConfig.getSignType()));
byte[] bys = http.postParams(params, true);
String result = new String(bys,"UTF-8");
Map<String,String> map = handleResult(result);
return map;
}
}

View File

@ -0,0 +1,125 @@
package com.ruoyi.common.pay.syb.service;
import com.alibaba.fastjson2.JSON;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.pay.syb.config.SybConfig;
import com.ruoyi.common.pay.syb.util.SybUtil;
import com.ruoyi.common.pay.wx.Payable;
import com.ruoyi.common.pay.wx.RefundAble;
import com.ruoyi.common.utils.StringUtils;
import com.wechat.pay.java.service.payments.jsapi.model.PrepayWithRequestPaymentResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.Map;
import java.util.TreeMap;
/**
* @author wjh
* 2024/9/9
*/
@Service
public class SybPayService {
@Autowired
private SybPayClient sybPayClient;
@Autowired
private SybConfig sybConfig;
/**
* 微信小程序预下单
*/
public PrepayWithRequestPaymentResponse prepayWxApp(Payable payable) {
try {
Map<String, String> result = sybPayClient.pay(
payable.getAmount(),
payable.getOutTradeNo(),
"W06",
payable.getDescription(),
payable.getAttach(),
payable.getOpenid(),
"",
sybConfig.getNotifyUrl(),
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
""
);
if (result != null) {
String payInfo = result.get("payinfo");
if (StringUtils.hasText(payInfo)) {
return JSON.parseObject(payInfo, PrepayWithRequestPaymentResponse.class);
}
}
throw new ServiceException("支付数据为空");
} catch(Exception e) {
throw new ServiceException("调起支付失败:" + e.getMessage());
}
}
public boolean validSign(TreeMap<String, String> params) {
try {
String appkey = "";
if("RSA".equals(params.get("signtype"))) {
appkey = sybConfig.getRsaTlPubKey();
} else if("SM2".equals(params.get("signtype"))) {
appkey = sybConfig.getSm2TlPubKey();
} else {
appkey = sybConfig.getMd5AppKey();
}
boolean isSign = SybUtil.validSign(params, appkey, params.get("signtype"));// 接受到推送通知,首先验签
System.out.println("验签结果:"+isSign);
return isSign;
} catch (Exception e) {
throw new ServiceException("验签异常:" + e.getMessage());
}
}
/**
* 使用商户自定义订单编号查询支付订单
*/
public Map<String, String> queryOrderByOutTradeNo(String outTradeNo) {
try {
return sybPayClient.query(outTradeNo, "");
} catch (Exception e) {
throw new ServiceException("查询支付订单异常:" + e.getMessage());
}
}
/**
* 微信退款
*/
public Map<String, String> refundWx(RefundAble refundAble) {
try {
return sybPayClient.refund(
refundAble.refundAmount().getRefund().longValue(),
refundAble.refundOutRefundNo(),
"",
refundAble.refundOutTradeNo()
);
} catch (Exception e) {
throw new ServiceException("发起退款异常:" + e.getMessage());
}
}
/**
* 关闭微信支付订单
*/
public void closeOrderWx(String outTradeNo) {
try {
sybPayClient.close("", outTradeNo);
} catch (Exception e) {
throw new ServiceException("关闭支付订单异常:" + e.getMessage());
}
}
}

View File

@ -0,0 +1,122 @@
package com.ruoyi.common.pay.syb.util;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLSession;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLEncoder;
import java.util.Map;
public class HttpConnectionUtil {
private HttpURLConnection conn;
private String connectUrl;
public HttpConnectionUtil(String connectUrl){
this.connectUrl = connectUrl;
}
public void init() throws Exception{
URL url = new URL(connectUrl);
System.setProperty("java.protocol.handler.pkgs", "javax.net.ssl");
HostnameVerifier hv = new HostnameVerifier() {
public boolean verify(String urlHostName, SSLSession session) {
return urlHostName.equals(session.getPeerHost());
}
};
HttpsURLConnection.setDefaultHostnameVerifier(hv);
URLConnection conn = url.openConnection();
conn.setDoInput(true);
conn.setDoOutput(true);
conn.setReadTimeout(60000);
conn.setConnectTimeout(30000);
if (conn instanceof HttpsURLConnection){
HttpsURLConnection httpsConn = (HttpsURLConnection)conn;
httpsConn.setSSLSocketFactory(SSLUtil.getInstance().getSSLSocketFactory());
} else if (conn instanceof HttpURLConnection){
HttpURLConnection httpConn = (HttpURLConnection)conn;
} else {
throw new Exception("不是http/https协议的url");
}
this.conn = (HttpURLConnection)conn;
initDefaultPost();
}
public void destory(){
try{
if(this.conn!=null){
this.conn.disconnect();
}
}catch(Exception e){
}
}
private void initDefaultPost() throws Exception{
conn.setDoOutput(true);
conn.setDoInput(true);
conn.setRequestMethod("POST");
conn.setUseCaches(false);
conn.setInstanceFollowRedirects(true);
conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
}
public byte[] postParams(Map<String, String> params,boolean readreturn) throws IOException {
StringBuilder outBuf = new StringBuilder();
boolean isNotFirst = false;
for (Map.Entry<String, String> entry: params.entrySet()){
if (isNotFirst)
outBuf.append('&');
isNotFirst = true;
outBuf
.append(entry.getKey())
.append('=')
.append(URLEncoder.encode(entry.getValue(), "UTF-8"));
}
System.out.println("参数:"+outBuf.toString());
return postParams(outBuf.toString(),readreturn);
}
public byte[] postParams(String message,boolean readreturn) throws IOException {
DataOutputStream out = new DataOutputStream(conn.getOutputStream());
out.write(message.getBytes("UTF-8"));
out.close();
if(readreturn){
return readBytesFromStream(conn.getInputStream());
}else{
return null;
}
}
public byte[] postParams(byte[] message,boolean readreturn) throws IOException {
DataOutputStream out = new DataOutputStream(conn.getOutputStream());
out.write(message);
out.close();
if(readreturn){
return readBytesFromStream(conn.getInputStream());
}else{
return null;
}
}
private byte[] readBytesFromStream(InputStream is) throws IOException{
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int readLen;
byte[] tmpBuf = new byte[4096];
while ((readLen = is.read(tmpBuf)) > 0)
baos.write(tmpBuf, 0, readLen);
is.close();
return baos.toByteArray();
}
public HttpURLConnection getConn() {
return conn;
}
}

View File

@ -0,0 +1,53 @@
package com.ruoyi.common.pay.syb.util;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
/**
* SSL管理助手类
* @author Administrator
*
*/
public class SSLUtil implements X509TrustManager {
private SSLSocketFactory sslFactory = null;
private SSLUtil(){
}
public void checkClientTrusted(X509Certificate[] arg0, String arg1)
throws CertificateException {
}
public void checkServerTrusted(X509Certificate[] arg0, String arg1)
throws CertificateException {
}
public X509Certificate[] getAcceptedIssuers() {
return null;
}
/** 获取SSL Socket工厂 */
public SSLSocketFactory getSSLSocketFactory(){
return sslFactory;
}
private static SSLUtil _instance = null;
/** 获取SSL管理助手类实例 */
synchronized public static SSLUtil getInstance() throws NoSuchAlgorithmException, KeyManagementException {
if (_instance == null){
_instance = new SSLUtil();
SSLContext sc = SSLContext.getInstance("TLSv1.2");
sc.init(null, new TrustManager[]{new SSLUtil()}, null);
_instance.sslFactory = sc.getSocketFactory();
}
return _instance;
}
}

View File

@ -0,0 +1,133 @@
package com.ruoyi.common.pay.syb.util;
import org.apache.commons.codec.binary.Base64;
import org.bouncycastle.asn1.ASN1EncodableVector;
import org.bouncycastle.asn1.ASN1Integer;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.DERSequence;
import org.bouncycastle.jcajce.spec.SM2ParameterSpec;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.util.encoders.Hex;
import java.io.IOException;
import java.math.BigInteger;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Arrays;
public class SmUtil {
static{
Security.addProvider(new BouncyCastleProvider());
}
/**算法常量:SM3withSM2*/
public static final String ALGORITHM_SM3SM2_BCPROV = "SM3withSM2";
private final static int SM3withSM2_RS_LEN=32;
public static void main(String[] args) throws Exception {
/**商户平台分配的appid,也是签名的certid**/
String appid = "00000156";
/**商户sm2私钥,用于向通联发起请求前进行签名**/
String cusPrivateKey = "MIGTAgEAMBMGByqGSM49AgEGCCqBHM9VAYItBHkwdwIBAQQgjj4Rk+b0YjwO+UwXofnHf4bK+kaaY5Btkd8nMP2VimmgCgYIKoEcz1UBgi2hRANCAAQqlALW4qGC3bP1x3wo5QsKxaCMEZJ2ODTTwOQ+d8UGU7GoK/y/WMBQWf5upMnFU06p5FxGooXYYoBtldgm03hq";
/**商户sm2公钥需要配置到通联商户平台**/
String cusPubKey = "MFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAEKpQC1uKhgt2z9cd8KOULCsWgjBGSdjg008DkPnfFBlOxqCv8v1jAUFn+bqTJxVNOqeRcRqKF2GKAbZXYJtN4ag==";
/**通联平台sm2公钥用于请求返回或者通联通知的验签**/
String tlPubKey = "MFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAE/BnA8BawehBtH0ksPyayo4pmzL/u1FQ2sZcqwOp6bjVqQX4tjo930QAvHZPJ2eez8sCz/RYghcqv4LvMq+kloQ==";
String blankStr = "请求待签名数据";
PrivateKey privkey = privKeySM2FromBase64Str(cusPrivateKey);
String sign = signSM3SM2RetBase64(privkey, appid, blankStr.getBytes("UTF-8"));//签名
System.out.println(sign);
String rspBlankStr = "返回待验签数据";//通联返回的明文
String rspSign = "AovBKQGUe0xuJ0ox7FgIIX+yB3DzbudgUsnNvJmDV0IdHZtU2Y8vdeUY1pd2vmPUf08hNgdkoz+4WP/D/ktOcA==";//通联返回的签名
PublicKey publicKey = pubKeySM2FromBase64Str(tlPubKey);
boolean isOk = verifySM3SM2(publicKey, "Allinpay", Base64.decodeBase64(rspSign), rspBlankStr.getBytes("UTF-8"));
System.out.println("验签结果:"+isOk);
}
/**签名并BASE64编码-SM3WithSM2 */
public static String signSM3SM2RetBase64(final PrivateKey privateKey,String certid,final byte[] data) throws Exception{
return Base64.encodeBase64String(signSM3SM2(privateKey, certid, data));
}
/**签名-SM3WithSM2 */
public static byte[] signSM3SM2(final PrivateKey privateKey,String certid,final byte[] data) throws Exception{
SM2ParameterSpec parameterSpec = new SM2ParameterSpec(certid.getBytes());
Signature signer = Signature.getInstance(ALGORITHM_SM3SM2_BCPROV, "BC");
signer.setParameter(parameterSpec);
signer.initSign(privateKey, new SecureRandom());
signer.update(data);
return byteAsn12BytePlain(signer.sign());
}
/** 验证签名-SM3WithSM2*/
public static boolean verifySM3SM2(final PublicKey publicKey,String certid,final byte[] signData, final byte[] srcData) throws Exception {
SM2ParameterSpec parameterSpec = new SM2ParameterSpec(certid.getBytes());
Signature verifier = Signature.getInstance(ALGORITHM_SM3SM2_BCPROV, "BC");
verifier.setParameter(parameterSpec);
verifier.initVerify(publicKey);
verifier.update(srcData);
return verifier.verify(bytePlain2ByteAsn1(signData));
}
/**从字符串读取私钥-目前支持PKCS8(keystr为BASE64格式)*/
public static PrivateKey privKeySM2FromBase64Str(String keystr) throws Exception {
KeyFactory keyFactory = KeyFactory.getInstance("EC");
return keyFactory.generatePrivate(new PKCS8EncodedKeySpec(Base64.decodeBase64(keystr)));
}
/**从字符串读取RSA公钥(keystr为BASE64格式)*/
public static PublicKey pubKeySM2FromBase64Str(String keystr) throws Exception {
KeyFactory keyFactory = KeyFactory.getInstance("EC");
return keyFactory.generatePublic(new X509EncodedKeySpec(Base64.decodeBase64(keystr)));
}
/**
* 将普通字节数组转换为ASN1字节数组 适用于SM3withSM2验签时验签明文转换
*/
private static byte[] bytePlain2ByteAsn1(byte[] data) {
if (data.length != SM3withSM2_RS_LEN * 2) throw new RuntimeException("err data. ");
BigInteger r = new BigInteger(1, Arrays.copyOfRange(data, 0, SM3withSM2_RS_LEN));
BigInteger s = new BigInteger(1, Arrays.copyOfRange(data, SM3withSM2_RS_LEN, SM3withSM2_RS_LEN * 2));
ASN1EncodableVector v = new ASN1EncodableVector();
v.add(new ASN1Integer(r));
v.add(new ASN1Integer(s));
try {
return new DERSequence(v).getEncoded("DER");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
* 将ASN1字节数组转换为普通字节数组 适用于SM3withSM2签名时签名结果转换
*/
private static byte[] byteAsn12BytePlain(byte[] dataAsn1) {
ASN1Sequence seq = ASN1Sequence.getInstance(dataAsn1);
byte[] r = bigIntToFixexLengthBytes(ASN1Integer.getInstance(seq.getObjectAt(0)).getValue());
byte[] s = bigIntToFixexLengthBytes(ASN1Integer.getInstance(seq.getObjectAt(1)).getValue());
byte[] result = new byte[SM3withSM2_RS_LEN * 2];
System.arraycopy(r, 0, result, 0, r.length);
System.arraycopy(s, 0, result, SM3withSM2_RS_LEN, s.length);
return result;
}
private static byte[] bigIntToFixexLengthBytes(BigInteger rOrS) {
byte[] rs = rOrS.toByteArray();
if (rs.length == SM3withSM2_RS_LEN) return rs;
else if (rs.length == SM3withSM2_RS_LEN + 1 && rs[0] == 0)
return Arrays.copyOfRange(rs, 1, SM3withSM2_RS_LEN + 1);
else if (rs.length < SM3withSM2_RS_LEN) {
byte[] result = new byte[SM3withSM2_RS_LEN];
Arrays.fill(result, (byte) 0);
System.arraycopy(rs, 0, result, SM3withSM2_RS_LEN - rs.length, rs.length);
return result;
} else {
throw new RuntimeException("err rs: " + Hex.toHexString(rs));
}
}
}

View File

@ -0,0 +1,230 @@
package com.ruoyi.common.pay.syb.util;
import org.apache.commons.codec.binary.Base64;
import javax.servlet.http.HttpServletRequest;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Map;
import java.util.Random;
import java.util.TreeMap;
public class SybUtil {
/**
* md5
*
* @param b
* @return
*/
public static String md5(byte[] b) {
try {
MessageDigest md = MessageDigest.getInstance("MD5");
md.reset();
md.update(b);
byte[] hash = md.digest();
StringBuffer outStrBuf = new StringBuffer(32);
for (int i = 0; i < hash.length; i++) {
int v = hash[i] & 0xFF;
if (v < 16) {
outStrBuf.append('0');
}
outStrBuf.append(Integer.toString(v, 16).toLowerCase());
}
return outStrBuf.toString();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
return new String(b);
}
}
/**
* 判断字符串是否为空
*
* @param s
* @return
*/
public static boolean isEmpty(String s) {
if (s == null || "".equals(s.trim()))
return true;
return false;
}
/**
* 生成随机码
*
* @param n
* @return
*/
public static String getValidatecode(int n) {
Random random = new Random();
String sRand = "";
n = n == 0 ? 4 : n;// default 4
for (int i = 0; i < n; i++) {
String rand = String.valueOf(random.nextInt(10));
sRand += rand;
}
return sRand;
}
public static boolean validSign(TreeMap<String, String> param,
String appkey, String signType) throws Exception {
if (param != null && !param.isEmpty()) {
if (!param.containsKey("sign"))
return false;
String sign = param.remove("sign");
if ("MD5".equals(signType)) {// 如果是md5则需要把md5的key加入到排序
param.put("key", appkey);
}
StringBuilder sb = new StringBuilder();
for (Map.Entry<String, String> entry : param.entrySet()) {
if (entry.getValue() != null && entry.getValue().length() > 0) {
sb.append(entry.getKey()).append("=")
.append(entry.getValue()).append("&");
}
}
if (sb.length() > 0) {
sb.deleteCharAt(sb.length() - 1);
}
if ("MD5".equals(signType)) {
return sign.toLowerCase().equals(
md5(sb.toString().getBytes("UTF-8")).toLowerCase());
} else if("SM2".equals(signType)){
PublicKey publicKey = SmUtil.pubKeySM2FromBase64Str(appkey);
return SmUtil.verifySM3SM2(publicKey, "Allinpay", Base64.decodeBase64(sign), sb.toString().getBytes("UTF-8"));
}else {
return rsaVerifyPublickey(sb.toString(), sign, appkey, "UTF-8");
}
}
return false;
}
public static boolean rsaVerifyPublickey(String content, String sign,
String publicKey, String charset) throws Exception {
try {
PublicKey pubKey = getPublicKeyFromX509("RSA",
Base64.decodeBase64(publicKey.getBytes()));
return rsaVerifyPublickey(content, sign, pubKey, charset);
} catch (Exception e) {
e.printStackTrace();
throw new Exception("RSAcontent = " + content + ",sign=" + sign
+ ",charset = " + charset, e);
}
}
public static boolean rsaVerifyPublickey(String content, String sign,
PublicKey pubKey, String charset) throws Exception {
try {
Signature signature = Signature
.getInstance("SHA1WithRSA");
signature.initVerify(pubKey);
if (charset == null || "".equals(charset)) {
signature.update(content.getBytes());
} else {
signature.update(content.getBytes(charset));
}
return signature.verify(Base64.decodeBase64(sign.getBytes()));
} catch (Exception e) {
throw e;
}
}
public static String unionSign(TreeMap<String, String> params,String appkey,
String signType) throws Exception {
// TODO Auto-generated method stub
params.remove("sign");
if ("MD5".equals(signType)) {// 如果是md5则需要把md5的key加入到排序
params.put("key", appkey);
}
StringBuilder sb = new StringBuilder();
for (Map.Entry<String, String> entry : params.entrySet()) {
if (entry.getValue() != null && entry.getValue().length() > 0) {
sb.append(entry.getKey()).append("=").append(entry.getValue())
.append("&");
}
}
if (sb.length() > 0) {
sb.deleteCharAt(sb.length() - 1);
}
String sign = "";
if ("MD5".equals(signType)) {
System.out.println(sb.toString());
sign = md5(sb.toString().getBytes("UTF-8"));// 记得是md5编码的加签
params.remove("key");
}else if("SM2".equals(signType)){
System.out.println(sb.toString());
PrivateKey privkey = SmUtil.privKeySM2FromBase64Str(appkey);
sign = SmUtil.signSM3SM2RetBase64(privkey, params.get("appid"), sb.toString().getBytes("UTF-8"));//签名
} else {
System.out.println(sb.toString());
sign = rsaSign(sb.toString(), appkey, "UTF-8");
}
return sign;
}
public static String rsaSign(String content, String privateKey,
String charset) throws Exception {
PrivateKey priKey = getPrivateKeyFromPKCS8("RSA",
Base64.decodeBase64(privateKey.getBytes()));
return rsaSign(content, priKey, charset);
}
public static String rsaSign(String content, byte[] privateKey,
String charset) throws Exception {
PrivateKey priKey = getPrivateKeyFromPKCS8("RSA", privateKey);
return rsaSign(content, priKey, charset);
}
public static String rsaSign(String content, PrivateKey priKey,
String charset) throws Exception {
Signature signature = Signature
.getInstance("SHA1WithRSA");
signature.initSign(priKey);
if (charset == null || "".equals(charset)) {
signature.update(content.getBytes());
} else {
signature.update(content.getBytes(charset));
}
byte[] signed = signature.sign();
return new String(Base64.encodeBase64(signed));
}
public static PrivateKey getPrivateKeyFromPKCS8(String algorithm,
byte[] encodedKey) throws Exception {
KeyFactory keyFactory = KeyFactory.getInstance(algorithm);
return keyFactory.generatePrivate(new PKCS8EncodedKeySpec(encodedKey));
}
public static PublicKey getPublicKeyFromX509(String algorithm,
byte[] encodedKey) throws Exception {
KeyFactory keyFactory = KeyFactory.getInstance(algorithm);
return keyFactory.generatePublic(new X509EncodedKeySpec(encodedKey));
}
/**
* 动态遍历获取所有收到的参数,此步非常关键,因为收银宝以后可能会加字段,动态获取可以兼容由于收银宝加字段而引起的签名异常
* @param request
* @return
*/
public static TreeMap<String, String> getParams(HttpServletRequest request){
TreeMap<String, String> map = new TreeMap<String, String>();
Map reqMap = request.getParameterMap();
for(Object key:reqMap.keySet()){
String value = ((String[])reqMap.get(key))[0];
// System.out.println(key+";"+value);
map.put(key.toString(),value);
}
return map;
}
}

View File

@ -0,0 +1,37 @@
package com.ruoyi.common.pay.wx;
import lombok.Data;
/**
* @author qzz
* 2024/9/12
*/
@Data
public class Payable {
/**
* 金额
*/
private long amount;
/**
* 描述
*/
private String description;
/**
* 附加信息
*/
private String attach;
/**
* 外部订单号
*/
private String outTradeNo;
/**
* 微信openId
*/
private String openid;
}

View File

@ -0,0 +1,23 @@
package com.ruoyi.common.pay.wx;
import com.wechat.pay.java.service.refund.model.AmountReq;
/**
* @author wjh
* 2024/7/10
*/
public interface RefundAble {
// 原商户订单号
String refundOutTradeNo();
// 退款单号
String refundOutRefundNo();
// 退款原因
String refundReason();
// 退款金额
AmountReq refundAmount();
}

View File

@ -0,0 +1,48 @@
//package com.ruoyi.common.pay.wx.service;
//
//import com.ruoyi.common.pay.wx.domain.RefundAble;
//import com.wechat.pay.java.service.refund.model.AmountReq;
//import io.swagger.annotations.ApiModelProperty;
//import lombok.Data;
//
//import java.math.BigDecimal;
//
///**
// * @author wjh
// * 2024/7/9
// */
//@Data
//public class RefundVO extends Refund implements RefundAble {
//
// @ApiModelProperty("原订单编号")
// private String payNo;
//
// @ApiModelProperty("原订单交易总额")
// private BigDecimal payAmount;
//
//
// @Override
// public String refundOutTradeNo() {
// return getPayNo();
// }
//
// @Override
// public String refundOutRefundNo() {
// return this.getRefundNo();
// }
//
// @Override
// public String refundReason() {
// return this.getReason();
// }
//
// @Override
// public AmountReq refundAmount() {
// BigDecimal decimal100 = new BigDecimal(100);
// AmountReq amount = new AmountReq();
// amount.setRefund(this.getAmount().multiply(decimal100).longValue());
// amount.setTotal(getPayAmount().multiply(decimal100).longValue());
// amount.setCurrency("CNY");
// return amount;
// }
//}

View File

@ -4,14 +4,13 @@ import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson2.JSON;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.ruoyi.common.constant.CacheConstants;
import com.ruoyi.common.constant.ServiceConstants;
import com.ruoyi.common.core.domain.entity.AsUser;
import com.ruoyi.common.core.domain.entity.SysDept;
import com.ruoyi.common.core.redis.RedisLock;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.pay.wx.Payable;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.uuid.IdUtils;
import com.ruoyi.system.domain.EtAreaDept;
import com.ruoyi.system.domain.EtOrder;
@ -98,9 +97,9 @@ public class WxPayService implements IWxPayService {
// String isProfitSharing = sysDept.getIsProfitSharing();
// 获取JSAPI所需参数
PrepayRequest request = new PrepayRequest();
request.setAmount(getAmount(order.getPayFee()));
JsapiServiceExtension jsapiServiceExtension = getJsapiServiceExtension(sysDept);
// PrepayRequest request = new PrepayRequest();
Payable payable = new Payable();
payable.setAmount(order.getPayFee().longValue());
if(StrUtil.isNotBlank(order.getOutTradeNo())){
String tradeNo = order.getOutTradeNo();
// 关闭订单
@ -111,27 +110,19 @@ public class WxPayService implements IWxPayService {
}
String outTradeNo = IdUtils.getOrderNo("wx");
order.setOutTradeNo(outTradeNo);
// order.setLocking("1");
int updateEtOrder = etOrderService.updateEtOrder(order);
if(updateEtOrder == 0){
throw new ServiceException("更新订单outTradeNo失败");
}
request.setOutTradeNo(outTradeNo);
request.setAppid(sysDept.getAppid());
request.setMchid(sysDept.getMerchantId());
payable.setOutTradeNo(outTradeNo);
// request.setOutTradeNo(outTradeNo);
// request.setMchid(sysDept.getMerchantId());
String type = order.getType();
String description = type.equals(ServiceConstants.ORDER_TYPE_RIDING) ? "骑行订单-"+billNo : "押金充值-"+billNo;
// LoginUser loginUser = SecurityUtils.getLoginUser();
// log.info("【预下单】获取登录用户信息:"+JSON.toJSONString(loginUser));
request.setAttach(JSON.toJSONString(new AttachVo(payType,user.getUserId(), "")));
request.setDescription(description);
request.setNotifyUrl(sysDept.getNotifyUrl());
request.setPayer(getPayer(user.getWxopenid()));
SettleInfo settleInfo = new SettleInfo();
settleInfo.setProfitSharing(false);//暂时关闭分账
// settleInfo.setProfitSharing( "true".equalsIgnoreCase(isProfitSharing));
request.setSettleInfo(settleInfo);
// JsapiServiceExtension jsapiServiceExtension = getJsapiServiceExtension(sysDept);
PrepayWithRequestPaymentResponse res = jsapiServiceExtension.prepayWithRequestPayment(request);
return res;
}else{