IJPay微信企业付款到零钱协议不正确 No appropriate protocol
问题发现
项目中使用了微信企业付款到零钱的功能,因为自己去封装过于麻烦,就使用了LJpay,原本使用的是jdk11的环境去运行一切正常,但是因为云托管没有固定的IP所以转移到了公司服务器,公司服务器的环境是jdk8,所以我把环境换到了jdk8之后报了如下错误
Servlet.service() for servlet [dispatcherServlet] in context with path [/server] threw exception [Request processing failed; nested exception is java.lang.RuntimeException: cn.hutool.core.io.IORuntimeException: SSLHandshakeException: No appropriate protocol (protocol is disabled or cipher suites are inappropriate)] with root cause
javax.net.ssl.SSLHandshakeException: No appropriate protocol (protocol is disabled or cipher suites are inappropriate)
问题研究
经过在各搜索引擎搜索,网上也有出现相同错误的,原因很直接,jdk的版本原因,这也是和我的猜想一样,因为只有环境变了代码没有做出改变。很多答案都是让我去把useSSL给禁用了了,更有甚者 让我去\jdk安装包目录下的\jre\lib\security中的java.security文件,将对应的SSLv3删除了,还有它后缀的两个一样的算法一起删除保存再重启服务,很明显这不现实,因为服务器里面跑着另一个服务。
解决方案
然后我再去查找LJpay相关的这个问题的话题只找到了微信退款协议不正确。随后我去看LJpay的源码。发现 transfers 在AbstractHttpDelegate里面的post是写死的 SSLSocketFactoryBuilder.TLSv1 的协议,想到两个解决方法,一个删除环境安装包的配置(否决了),还有一个是禁用了useSSL,发现作者写的LJpay的WxpayApi里面提供了自定义协议的方法transfersByProtocol。
/**
* 企业付款到零钱
*
* @param params 请求参数
* @param certFile 证书文件的 InputStream
* @param certPass 证书密码
* @return {@link String} 请求返回的结果
*/
public static String transfers(Map<String, String> params, InputStream certFile, String certPass) {
return execution(getReqUrl(WxApiType.TRANSFER, null, false), params, certFile, certPass);
}
/**
* 企业付款到零钱
*
* @param params 请求参数
* @param certFile 证书文件的 InputStream
* @param certPass 证书密码
* @param protocol 协议
* @return {@link String} 请求返回的结果
*/
public static String transfersByProtocol(Map<String, String> params, InputStream certFile, String certPass, String protocol) {
return executionByProtocol(getReqUrl(WxApiType.TRANSFER, null, false), params, certFile, certPass, protocol);
}
适配优化
但是 我发现我这里就使用到了企业付款到零钱这个功能,而且我个人觉得LJpay封装了太多,为了减少打包的重量,我决定重新实现LJpay企业付款到零钱这个功能。
// 自己的业务代码
String refundStr = post(WxPayApi.getReqUrl(WxApiType.TRANSFER
, null, false), WxPayKit.toXml(params), stream, wxPayV3Bean.getMchId());
// 实际调用AbstractHttpDelegate类中的方法
public String post(String url, String data, InputStream certFile, String certPass) {
try {
return HttpRequest
.post(url)
.setSSLSocketFactory(SSLSocketFactoryBuilder.create()
.setProtocol("")
.setKeyManagers(this.getKeyManager(certPass, null, certFile))
.setSecureRandom(new SecureRandom())
.build())
.body(data)
.execute()
.body();
} catch (Exception var7) {
throw new RuntimeException(var7);
}
}
private KeyManager[] getKeyManager(String certPass, String certPath, InputStream certFile) throws Exception {
KeyStore clientStore = KeyStore.getInstance("PKCS12");
if (certFile != null) {
clientStore.load(certFile, certPass.toCharArray());
} else {
clientStore.load(new FileInputStream(certPath), certPass.toCharArray());
}
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(clientStore, certPass.toCharArray());
return kmf.getKeyManagers();
}
// 重写生成模板类,因为很多方法都抽象的继承了,为了方便直接调用。
/**
* @Description:
* 重写的这个类主要有两个功能
* 1. 通过Builder创建Map。
* 2. 排序并参与字符拼接的参数组,来实现签名算法
* 3. 构造签名Map加入参数sign
* @author: zwy
* @date: 2022年02月22日 15:23
*/
@Builder
@AllArgsConstructor
@Getter
@Setter
public class TransferModel {
private String mch_appid;
private String mchid;
private String device_info;
private String nonce_str;
private String sign;
private String partner_trade_no;
private String openid;
private String check_name;
private String re_user_name;
private String amount;
private String desc;
private String spbill_create_ip;
//************************继承BaseModel实现类****************************************
/**
* 将建构的 builder 转为 Map
*
* @return 转化后的 Map
*/
public Map<String, String> toMap() {
String[] fieldNames = getFiledNames(this);
HashMap<String, String> map = new HashMap<String, String>(fieldNames.length);
for (String name : fieldNames) {
String value = (String) getFieldValueByName(name, this);
if (StrUtil.isNotEmpty(value)) {
map.put(name, value);
}
}
return map;
}
/**
* 根据属性名获取属性值
*
* @param fieldName 属性名称
* @param obj 对象
* @return 返回对应属性的值
*/
public Object getFieldValueByName(String fieldName, Object obj) {
try {
String firstLetter = fieldName.substring(0, 1).toUpperCase();
String getter = new StringBuffer().append("get")
.append(firstLetter)
.append(fieldName.substring(1))
.toString();
Method method = obj.getClass().getMethod(getter);
return method.invoke(obj);
} catch (Exception e) {
return null;
}
}
/**
* 获取属性名数组
*
* @param obj 对象
* @return 返回对象属性名数组
*/
public String[] getFiledNames(Object obj) {
Field[] fields = obj.getClass().getDeclaredFields();
String[] fieldNames = new String[fields.length];
for (int i = 0; i < fields.length; i++) {
fieldNames[i] = fields[i].getName();
}
return fieldNames;
}
//************************ 排序并参与字符拼接的参数组 ****************************************
/*形参:
params – 需要排序并参与字符拼接的参数组
connStr – 连接符号
encode – 是否进行URLEncoder
返回值:
拼接后字符串*/
public static String createLinkString(Map<String, String> params, String connStr, boolean encode, boolean quotes) {
List<String> keys = new ArrayList<>(params.keySet());
Collections.sort(keys);
StringBuilder content = new StringBuilder();
for (int i = 0; i < keys.size(); i++) {
String key = keys.get(i);
String value = params.get(key);
// 拼接时,不包括最后一个&字符
if (i == keys.size() - 1) {
if (quotes) {
content.append(key).append("=").append('"').append(encode ? urlEncode(value) : value).append('"');
} else {
content.append(key).append("=").append(encode ? urlEncode(value) : value);
}
} else {
if (quotes) {
content.append(key).append("=").append('"').append(encode ? urlEncode(value) : value).append('"').append(connStr);
} else {
content.append(key).append("=").append(encode ? urlEncode(value) : value).append(connStr);
}
}
}
return content.toString();
}
/**
* URL 编码
*
* @param src 需要编码的字符串
* @return 编码后的字符串
*/
public static String urlEncode(String src) {
try {
return URLEncoder.encode(src, CharsetUtil.UTF_8).replace("+", "%20");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
return null;
}
}
//************************ 构造签名Map ****************************************
/**
* 构建签名 Map
*
* @param partnerKey API KEY
* @return 构建签名后的 Map
*/
public Map<String, String> createSign(String partnerKey) {
return buildSign(toMap(), partnerKey);
}
/**
* 构建签名
*
* @param params 需要签名的参数
* @param partnerKey 密钥
* @return 签名后的 Map
*/
public static Map<String, String> buildSign(Map<String, String> params, String partnerKey) {
String sign = createSign(params, partnerKey);
params.put("sign", sign);
return params;
}
/**
* 生成签名
*
* @param params 需要签名的参数
* @param partnerKey 密钥
* @return 签名后的数据
*/
public static String createSign(Map<String, String> params, String partnerKey) {
// 生成签名前先去除sign
params.remove("sign");
String tempStr = createLinkString(params, "&", false, false);
String stringSignTemp = tempStr + "&key=" + partnerKey;
return SecureUtil.md5(stringSignTemp).toUpperCase();
}
}