
11 changed files with 2502 additions and 8 deletions
@ -0,0 +1,504 @@ |
|||
package com.yxt.pay.utils.wx; |
|||
|
|||
import cn.hutool.core.io.FileUtil; |
|||
import cn.hutool.http.HttpRequest; |
|||
import cn.hutool.http.HttpResponse; |
|||
import cn.hutool.http.HttpUtil; |
|||
import cn.hutool.http.ssl.SSLSocketFactoryBuilder; |
|||
|
|||
import javax.net.ssl.KeyManager; |
|||
import javax.net.ssl.KeyManagerFactory; |
|||
import java.io.File; |
|||
import java.io.FileInputStream; |
|||
import java.io.InputStream; |
|||
import java.security.KeyStore; |
|||
import java.security.SecureRandom; |
|||
import java.util.Map; |
|||
|
|||
/** |
|||
* <p>IJPay 让支付触手可及,封装了微信支付、支付宝支付、银联支付等常用的支付方式以及各种常用的接口。</p> |
|||
* |
|||
* <p>不依赖任何第三方 mvc 框架,仅仅作为工具使用简单快速完成支付模块的开发,可轻松嵌入到任何系统里。 </p> |
|||
* |
|||
* <p>IJPay 交流群: 723992875</p> |
|||
* |
|||
* <p>Node.js 版: https://gitee.com/javen205/TNWX</p>
|
|||
* |
|||
* <p>Http 代理类</p> |
|||
* |
|||
* @author Javen |
|||
*/ |
|||
public abstract class AbstractHttpDelegate { |
|||
|
|||
/** |
|||
* get 请求 |
|||
* |
|||
* @param url 请求url |
|||
* @return {@link String} 请求返回的结果 |
|||
*/ |
|||
public String get(String url) { |
|||
return HttpUtil.get(url); |
|||
} |
|||
|
|||
/** |
|||
* get 请求 |
|||
* |
|||
* @param url 请求url |
|||
* @param paramMap 请求参数 |
|||
* @return {@link String} 请求返回的结果 |
|||
*/ |
|||
public String get(String url, Map<String, Object> paramMap) { |
|||
return HttpUtil.get(url, paramMap); |
|||
} |
|||
|
|||
/** |
|||
* get 请求 |
|||
* |
|||
* @param url 请求url |
|||
* @param paramMap 请求参数 |
|||
* @param headers 请求头 |
|||
* @return {@link IJPayHttpResponse} 请求返回的结果 |
|||
*/ |
|||
public IJPayHttpResponse get(String url, Map<String, Object> paramMap, Map<String, String> headers) { |
|||
IJPayHttpResponse response = new IJPayHttpResponse(); |
|||
HttpResponse httpResponse = getToResponse(url, paramMap, headers); |
|||
response.setBody(httpResponse.body()); |
|||
response.setStatus(httpResponse.getStatus()); |
|||
response.setHeaders(httpResponse.headers()); |
|||
return response; |
|||
} |
|||
|
|||
/** |
|||
* post 请求 |
|||
* |
|||
* @param url 请求url |
|||
* @param data 请求参数 |
|||
* @return {@link String} 请求返回的结果 |
|||
*/ |
|||
public String post(String url, String data) { |
|||
return HttpUtil.post(url, data); |
|||
} |
|||
|
|||
/** |
|||
* post 请求 |
|||
* |
|||
* @param url 请求url |
|||
* @param paramMap 请求参数 |
|||
* @return {@link String} 请求返回的结果 |
|||
*/ |
|||
public String post(String url, Map<String, Object> paramMap) { |
|||
return HttpUtil.post(url, paramMap); |
|||
} |
|||
|
|||
/** |
|||
* post 请求 |
|||
* |
|||
* @param url 请求url |
|||
* @param paramMap 请求参数 |
|||
* @param headers 请求头 |
|||
* @return {@link IJPayHttpResponse} 请求返回的结果 |
|||
*/ |
|||
public IJPayHttpResponse post(String url, Map<String, Object> paramMap, Map<String, String> headers) { |
|||
IJPayHttpResponse response = new IJPayHttpResponse(); |
|||
HttpResponse httpResponse = postToResponse(url, headers, paramMap); |
|||
response.setBody(httpResponse.body()); |
|||
response.setStatus(httpResponse.getStatus()); |
|||
response.setHeaders(httpResponse.headers()); |
|||
return response; |
|||
} |
|||
|
|||
/** |
|||
* post 请求 |
|||
* |
|||
* @param url 请求url |
|||
* @param data 请求参数 |
|||
* @param headers 请求头 |
|||
* @return {@link IJPayHttpResponse} 请求返回的结果 |
|||
*/ |
|||
public IJPayHttpResponse post(String url, String data, Map<String, String> headers) { |
|||
IJPayHttpResponse response = new IJPayHttpResponse(); |
|||
HttpResponse httpResponse = postToResponse(url, headers, data); |
|||
response.setBody(httpResponse.body()); |
|||
response.setStatus(httpResponse.getStatus()); |
|||
response.setHeaders(httpResponse.headers()); |
|||
return response; |
|||
} |
|||
|
|||
/** |
|||
* patch 请求 |
|||
* |
|||
* @param url 请求url |
|||
* @param paramMap 请求参数 |
|||
* @param headers 请求头 |
|||
* @return {@link IJPayHttpResponse} 请求返回的结果 |
|||
*/ |
|||
public IJPayHttpResponse patch(String url, Map<String, Object> paramMap, Map<String, String> headers) { |
|||
IJPayHttpResponse response = new IJPayHttpResponse(); |
|||
HttpResponse httpResponse = patchToResponse(url, headers, paramMap); |
|||
response.setBody(httpResponse.body()); |
|||
response.setStatus(httpResponse.getStatus()); |
|||
response.setHeaders(httpResponse.headers()); |
|||
return response; |
|||
} |
|||
|
|||
/** |
|||
* patch 请求 |
|||
* |
|||
* @param url 请求url |
|||
* @param data 请求参数 |
|||
* @param headers 请求头 |
|||
* @return {@link IJPayHttpResponse} 请求返回的结果 |
|||
*/ |
|||
public IJPayHttpResponse patch(String url, String data, Map<String, String> headers) { |
|||
IJPayHttpResponse response = new IJPayHttpResponse(); |
|||
HttpResponse httpResponse = patchToResponse(url, headers, data); |
|||
response.setBody(httpResponse.body()); |
|||
response.setStatus(httpResponse.getStatus()); |
|||
response.setHeaders(httpResponse.headers()); |
|||
return response; |
|||
} |
|||
|
|||
/** |
|||
* delete 请求 |
|||
* |
|||
* @param url 请求url |
|||
* @param paramMap 请求参数 |
|||
* @param headers 请求头 |
|||
* @return {@link IJPayHttpResponse} 请求返回的结果 |
|||
*/ |
|||
public IJPayHttpResponse delete(String url, Map<String, Object> paramMap, Map<String, String> headers) { |
|||
IJPayHttpResponse response = new IJPayHttpResponse(); |
|||
HttpResponse httpResponse = deleteToResponse(url, headers, paramMap); |
|||
response.setBody(httpResponse.body()); |
|||
response.setStatus(httpResponse.getStatus()); |
|||
response.setHeaders(httpResponse.headers()); |
|||
return response; |
|||
} |
|||
|
|||
/** |
|||
* delete 请求 |
|||
* |
|||
* @param url 请求url |
|||
* @param data 请求参数 |
|||
* @param headers 请求头 |
|||
* @return {@link IJPayHttpResponse} 请求返回的结果 |
|||
*/ |
|||
public IJPayHttpResponse delete(String url, String data, Map<String, String> headers) { |
|||
IJPayHttpResponse response = new IJPayHttpResponse(); |
|||
HttpResponse httpResponse = deleteToResponse(url, headers, data); |
|||
response.setBody(httpResponse.body()); |
|||
response.setStatus(httpResponse.getStatus()); |
|||
response.setHeaders(httpResponse.headers()); |
|||
return response; |
|||
} |
|||
|
|||
/** |
|||
* put 请求 |
|||
* |
|||
* @param url 请求url |
|||
* @param paramMap 请求参数 |
|||
* @param headers 请求头 |
|||
* @return {@link IJPayHttpResponse} 请求返回的结果 |
|||
*/ |
|||
public IJPayHttpResponse put(String url, Map<String, Object> paramMap, Map<String, String> headers) { |
|||
IJPayHttpResponse response = new IJPayHttpResponse(); |
|||
HttpResponse httpResponse = putToResponse(url, headers, paramMap); |
|||
response.setBody(httpResponse.body()); |
|||
response.setStatus(httpResponse.getStatus()); |
|||
response.setHeaders(httpResponse.headers()); |
|||
return response; |
|||
} |
|||
|
|||
/** |
|||
* put 请求 |
|||
* |
|||
* @param url 请求url |
|||
* @param data 请求参数 |
|||
* @param headers 请求头 |
|||
* @return {@link IJPayHttpResponse} 请求返回的结果 |
|||
*/ |
|||
public IJPayHttpResponse put(String url, String data, Map<String, String> headers) { |
|||
IJPayHttpResponse response = new IJPayHttpResponse(); |
|||
HttpResponse httpResponse = putToResponse(url, headers, data); |
|||
response.setBody(httpResponse.body()); |
|||
response.setStatus(httpResponse.getStatus()); |
|||
response.setHeaders(httpResponse.headers()); |
|||
return response; |
|||
} |
|||
|
|||
/** |
|||
* 上传文件 |
|||
* |
|||
* @param url 请求url |
|||
* @param data 请求参数 |
|||
* @param certPath 证书路径 |
|||
* @param certPass 证书密码 |
|||
* @param filePath 上传文件路径 |
|||
* @param protocol 协议 |
|||
* @return {@link String} 请求返回的结果 |
|||
*/ |
|||
public String upload(String url, String data, String certPath, String certPass, String filePath, String protocol) { |
|||
try { |
|||
File file = FileUtil.newFile(filePath); |
|||
return HttpRequest.post(url) |
|||
.setSSLSocketFactory(SSLSocketFactoryBuilder |
|||
.create() |
|||
.setProtocol(protocol) |
|||
.setKeyManagers(getKeyManager(certPass, certPath, null)) |
|||
.setSecureRandom(new SecureRandom()) |
|||
.build() |
|||
) |
|||
.header("Content-Type", "multipart/form-data;boundary=\"boundary\"") |
|||
.form("file", file) |
|||
.form("meta", data) |
|||
.execute() |
|||
.body(); |
|||
} catch (Exception e) { |
|||
throw new RuntimeException(e); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 上传文件 |
|||
* |
|||
* @param url 请求url |
|||
* @param data 请求参数 |
|||
* @param certPath 证书路径 |
|||
* @param certPass 证书密码 |
|||
* @param filePath 上传文件路径 |
|||
* @return {@link String} 请求返回的结果 |
|||
*/ |
|||
public String upload(String url, String data, String certPath, String certPass, String filePath) { |
|||
return upload(url, data, certPath, certPass, filePath, SSLSocketFactoryBuilder.TLSv1); |
|||
} |
|||
|
|||
/** |
|||
* post 请求 |
|||
* |
|||
* @param url 请求url |
|||
* @param data 请求参数 |
|||
* @param certPath 证书路径 |
|||
* @param certPass 证书密码 |
|||
* @param protocol 协议 |
|||
* @return {@link String} 请求返回的结果 |
|||
*/ |
|||
public String post(String url, String data, String certPath, String certPass, String protocol) { |
|||
try { |
|||
return HttpRequest.post(url) |
|||
.setSSLSocketFactory(SSLSocketFactoryBuilder |
|||
.create() |
|||
.setProtocol(protocol) |
|||
.setKeyManagers(getKeyManager(certPass, certPath, null)) |
|||
.setSecureRandom(new SecureRandom()) |
|||
.build() |
|||
) |
|||
.body(data) |
|||
.execute() |
|||
.body(); |
|||
} catch (Exception e) { |
|||
throw new RuntimeException(e); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* post 请求 |
|||
* |
|||
* @param url 请求url |
|||
* @param data 请求参数 |
|||
* @param certPath 证书路径 |
|||
* @param certPass 证书密码 |
|||
* @return {@link String} 请求返回的结果 |
|||
*/ |
|||
public String post(String url, String data, String certPath, String certPass) { |
|||
return post(url, data, certPath, certPass, SSLSocketFactoryBuilder.TLSv1); |
|||
} |
|||
|
|||
/** |
|||
* post 请求 |
|||
* |
|||
* @param url 请求url |
|||
* @param data 请求参数 |
|||
* @param certFile 证书文件输入流 |
|||
* @param certPass 证书密码 |
|||
* @param protocol 协议 |
|||
* @return {@link String} 请求返回的结果 |
|||
*/ |
|||
public String post(String url, String data, InputStream certFile, String certPass, String protocol) { |
|||
try { |
|||
return HttpRequest.post(url) |
|||
.setSSLSocketFactory(SSLSocketFactoryBuilder |
|||
.create() |
|||
.setProtocol(protocol) |
|||
.setKeyManagers(getKeyManager(certPass, null, certFile)) |
|||
.setSecureRandom(new SecureRandom()) |
|||
.build() |
|||
) |
|||
.body(data) |
|||
.execute() |
|||
.body(); |
|||
} catch (Exception e) { |
|||
throw new RuntimeException(e); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* post 请求 |
|||
* |
|||
* @param url 请求url |
|||
* @param data 请求参数 |
|||
* @param certFile 证书文件输入流 |
|||
* @param certPass 证书密码 |
|||
* @return {@link String} 请求返回的结果 |
|||
*/ |
|||
public String post(String url, String data, InputStream certFile, String certPass) { |
|||
return post(url, data, certFile, certPass, SSLSocketFactoryBuilder.TLSv1); |
|||
} |
|||
|
|||
/** |
|||
* get 请求 |
|||
* |
|||
* @param url 请求url |
|||
* @param paramMap 请求参数 |
|||
* @param headers 请求头 |
|||
* @return {@link HttpResponse} 请求返回的结果 |
|||
*/ |
|||
private HttpResponse getToResponse(String url, Map<String, Object> paramMap, Map<String, String> headers) { |
|||
return HttpRequest.get(url) |
|||
.addHeaders(headers) |
|||
.form(paramMap) |
|||
.execute(); |
|||
} |
|||
|
|||
/** |
|||
* post 请求 |
|||
* |
|||
* @param url 请求url |
|||
* @param headers 请求头 |
|||
* @param data 请求参数 |
|||
* @return {@link HttpResponse} 请求返回的结果 |
|||
*/ |
|||
private HttpResponse postToResponse(String url, Map<String, String> headers, String data) { |
|||
return HttpRequest.post(url) |
|||
.addHeaders(headers) |
|||
.body(data) |
|||
.execute(); |
|||
} |
|||
|
|||
/** |
|||
* post 请求 |
|||
* |
|||
* @param url 请求url |
|||
* @param headers 请求头 |
|||
* @param paramMap 请求参数 |
|||
* @return {@link HttpResponse} 请求返回的结果 |
|||
*/ |
|||
private HttpResponse postToResponse(String url, Map<String, String> headers, Map<String, Object> paramMap) { |
|||
return HttpRequest.post(url) |
|||
.addHeaders(headers) |
|||
.form(paramMap) |
|||
.execute(); |
|||
} |
|||
|
|||
/** |
|||
* patch 请求 |
|||
* |
|||
* @param url 请求url |
|||
* @param headers 请求头 |
|||
* @param paramMap 请求参数 |
|||
* @return {@link HttpResponse} 请求返回的结果 |
|||
*/ |
|||
private HttpResponse patchToResponse(String url, Map<String, String> headers, Map<String, Object> paramMap) { |
|||
return HttpRequest.patch(url) |
|||
.addHeaders(headers) |
|||
.form(paramMap) |
|||
.execute(); |
|||
} |
|||
|
|||
/** |
|||
* patch 请求 |
|||
* |
|||
* @param url 请求url |
|||
* @param headers 请求头 |
|||
* @param data 请求参数 |
|||
* @return {@link HttpResponse} 请求返回的结果 |
|||
*/ |
|||
private HttpResponse patchToResponse(String url, Map<String, String> headers, String data) { |
|||
return HttpRequest.patch(url) |
|||
.addHeaders(headers) |
|||
.body(data) |
|||
.execute(); |
|||
} |
|||
|
|||
/** |
|||
* delete 请求 |
|||
* |
|||
* @param url 请求url |
|||
* @param headers 请求头 |
|||
* @param data 请求参数 |
|||
* @return {@link HttpResponse} 请求返回的结果 |
|||
*/ |
|||
private HttpResponse deleteToResponse(String url, Map<String, String> headers, String data) { |
|||
return HttpRequest.delete(url) |
|||
.addHeaders(headers) |
|||
.body(data) |
|||
.execute(); |
|||
} |
|||
|
|||
/** |
|||
* delete 请求 |
|||
* |
|||
* @param url 请求url |
|||
* @param headers 请求头 |
|||
* @param paramMap 请求参数 |
|||
* @return {@link HttpResponse} 请求返回的结果 |
|||
*/ |
|||
private HttpResponse deleteToResponse(String url, Map<String, String> headers, Map<String, Object> paramMap) { |
|||
return HttpRequest.delete(url) |
|||
.addHeaders(headers) |
|||
.form(paramMap) |
|||
.execute(); |
|||
} |
|||
|
|||
/** |
|||
* put 请求 |
|||
* |
|||
* @param url 请求url |
|||
* @param headers 请求头 |
|||
* @param data 请求参数 |
|||
* @return {@link HttpResponse} 请求返回的结果 |
|||
*/ |
|||
private HttpResponse putToResponse(String url, Map<String, String> headers, String data) { |
|||
return HttpRequest.put(url) |
|||
.addHeaders(headers) |
|||
.body(data) |
|||
.execute(); |
|||
} |
|||
|
|||
/** |
|||
* put 请求 |
|||
* |
|||
* @param url 请求url |
|||
* @param headers 请求头 |
|||
* @param paramMap 请求参数 |
|||
* @return {@link HttpResponse} 请求返回的结果 |
|||
*/ |
|||
private HttpResponse putToResponse(String url, Map<String, String> headers, Map<String, Object> paramMap) { |
|||
return HttpRequest.put(url) |
|||
.addHeaders(headers) |
|||
.form(paramMap) |
|||
.execute(); |
|||
} |
|||
|
|||
|
|||
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(); |
|||
} |
|||
} |
@ -0,0 +1,70 @@ |
|||
package com.yxt.pay.utils.wx; |
|||
|
|||
import cn.hutool.core.codec.Base64; |
|||
|
|||
import javax.crypto.Cipher; |
|||
import javax.crypto.NoSuchPaddingException; |
|||
import javax.crypto.spec.GCMParameterSpec; |
|||
import javax.crypto.spec.SecretKeySpec; |
|||
import java.nio.charset.StandardCharsets; |
|||
import java.security.GeneralSecurityException; |
|||
import java.security.InvalidAlgorithmParameterException; |
|||
import java.security.InvalidKeyException; |
|||
import java.security.NoSuchAlgorithmException; |
|||
|
|||
/** |
|||
* <p>IJPay 让支付触手可及,封装了微信支付、支付宝支付、银联支付常用的支付方式以及各种常用的接口。</p> |
|||
* |
|||
* <p>不依赖任何第三方 mvc 框架,仅仅作为工具使用简单快速完成支付模块的开发,可轻松嵌入到任何系统里。 </p> |
|||
* |
|||
* <p>IJPay 交流群: 723992875</p> |
|||
* |
|||
* <p>Node.js 版: https://gitee.com/javen205/TNWX</p>
|
|||
* |
|||
* <p>工具类 AesUtil</p> |
|||
* |
|||
* @author 微信 |
|||
*/ |
|||
public class AesUtil { |
|||
|
|||
static final int KEY_LENGTH_BYTE = 32; |
|||
static final int TAG_LENGTH_BIT = 128; |
|||
private final byte[] aesKey; |
|||
|
|||
/** |
|||
* @param key APIv3 密钥 |
|||
*/ |
|||
public AesUtil(byte[] key) { |
|||
if (key.length != KEY_LENGTH_BYTE) { |
|||
throw new IllegalArgumentException("无效的ApiV3Key,长度必须为32个字节"); |
|||
} |
|||
this.aesKey = key; |
|||
} |
|||
|
|||
/** |
|||
* 证书和回调报文解密 |
|||
* |
|||
* @param associatedData associated_data |
|||
* @param nonce nonce |
|||
* @param cipherText ciphertext |
|||
* @return {String} 平台证书明文 |
|||
* @throws GeneralSecurityException 异常 |
|||
*/ |
|||
public String decryptToString(byte[] associatedData, byte[] nonce, String cipherText) throws GeneralSecurityException { |
|||
try { |
|||
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding"); |
|||
|
|||
SecretKeySpec key = new SecretKeySpec(aesKey, "AES"); |
|||
GCMParameterSpec spec = new GCMParameterSpec(TAG_LENGTH_BIT, nonce); |
|||
|
|||
cipher.init(Cipher.DECRYPT_MODE, key, spec); |
|||
cipher.updateAAD(associatedData); |
|||
|
|||
return new String(cipher.doFinal(Base64.decode(cipherText)), StandardCharsets.UTF_8); |
|||
} catch (NoSuchAlgorithmException | NoSuchPaddingException e) { |
|||
throw new IllegalStateException(e); |
|||
} catch (InvalidKeyException | InvalidAlgorithmParameterException e) { |
|||
throw new IllegalArgumentException(e); |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,87 @@ |
|||
package com.yxt.pay.utils.wx; |
|||
|
|||
|
|||
import javax.servlet.http.HttpServletRequest; |
|||
import java.io.BufferedReader; |
|||
import java.io.IOException; |
|||
import java.util.HashMap; |
|||
import java.util.Map; |
|||
|
|||
/** |
|||
* <p>IJPay 让支付触手可及,封装了微信支付、支付宝支付、银联支付常用的支付方式以及各种常用的接口。</p> |
|||
* |
|||
* <p>不依赖任何第三方 mvc 框架,仅仅作为工具使用简单快速完成支付模块的开发,可轻松嵌入到任何系统里。 </p> |
|||
* |
|||
* <p>IJPay 交流群: 723992875</p> |
|||
* |
|||
* <p>Node.js 版: https://gitee.com/javen205/TNWX</p>
|
|||
* |
|||
* <p>Http 工具类</p> |
|||
* |
|||
* @author Javen |
|||
*/ |
|||
public class HttpKit { |
|||
|
|||
private static AbstractHttpDelegate delegate = new DefaultHttpKit(); |
|||
|
|||
public static AbstractHttpDelegate getDelegate() { |
|||
return delegate; |
|||
} |
|||
|
|||
public static void setDelegate(AbstractHttpDelegate delegate) { |
|||
HttpKit.delegate = delegate; |
|||
} |
|||
|
|||
public static String readData(HttpServletRequest request) { |
|||
BufferedReader br = null; |
|||
try { |
|||
StringBuilder result = new StringBuilder(); |
|||
br = request.getReader(); |
|||
for (String line; (line = br.readLine()) != null; ) { |
|||
if (result.length() > 0) { |
|||
result.append("\n"); |
|||
} |
|||
result.append(line); |
|||
} |
|||
return result.toString(); |
|||
} catch (IOException e) { |
|||
throw new RuntimeException(e); |
|||
} finally { |
|||
if (br != null) { |
|||
try { |
|||
br.close(); |
|||
} catch (IOException e) { |
|||
e.printStackTrace(); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 将同步通知的参数转化为Map |
|||
* |
|||
* @param request {@link HttpServletRequest} |
|||
* @return 转化后的 Map |
|||
*/ |
|||
public static Map<String, String> toMap(HttpServletRequest request) { |
|||
Map<String, String> params = new HashMap<>(); |
|||
Map<String, String[]> requestParams = request.getParameterMap(); |
|||
for (String name : requestParams.keySet()) { |
|||
String[] values = requestParams.get(name); |
|||
String valueStr = ""; |
|||
for (int i = 0; i < values.length; i++) { |
|||
valueStr = (i == values.length - 1) ? valueStr + values[i] : valueStr + values[i] + ","; |
|||
} |
|||
params.put(name, valueStr); |
|||
} |
|||
return params; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 使用 huTool 实现的 Http 工具类 |
|||
* |
|||
* @author Javen |
|||
*/ |
|||
class DefaultHttpKit extends AbstractHttpDelegate { |
|||
} |
@ -0,0 +1,77 @@ |
|||
|
|||
package com.yxt.pay.utils.wx; |
|||
|
|||
import cn.hutool.core.collection.CollectionUtil; |
|||
import cn.hutool.core.map.CaseInsensitiveMap; |
|||
import cn.hutool.core.util.StrUtil; |
|||
|
|||
import java.io.Serializable; |
|||
import java.util.List; |
|||
import java.util.Map; |
|||
|
|||
/** |
|||
* <p>IJPay 让支付触手可及,封装了微信支付、支付宝支付、银联支付常用的支付方式以及各种常用的接口。</p> |
|||
* |
|||
* <p>不依赖任何第三方 mvc 框架,仅仅作为工具使用简单快速完成支付模块的开发,可轻松嵌入到任何系统里。 </p> |
|||
* |
|||
* <p>IJPay 交流群: 723992875</p> |
|||
* |
|||
* <p>Node.js 版: https://gitee.com/javen205/TNWX</p>
|
|||
* |
|||
* <p>IJPay Http Response</p> |
|||
* |
|||
* @author Javen |
|||
*/ |
|||
public class IJPayHttpResponse implements Serializable { |
|||
private static final long serialVersionUID = 6089103955998013402L; |
|||
private String body; |
|||
private int status; |
|||
private Map<String, List<String>> headers; |
|||
|
|||
public String getBody() { |
|||
return body; |
|||
} |
|||
|
|||
public void setBody(String body) { |
|||
this.body = body; |
|||
} |
|||
|
|||
public int getStatus() { |
|||
return status; |
|||
} |
|||
|
|||
public void setStatus(int status) { |
|||
this.status = status; |
|||
} |
|||
|
|||
public Map<String, List<String>> getHeaders() { |
|||
return headers; |
|||
} |
|||
|
|||
public void setHeaders(Map<String, List<String>> headers) { |
|||
this.headers = headers; |
|||
} |
|||
|
|||
public String getHeader(String name) { |
|||
List<String> values = this.headerList(name); |
|||
return CollectionUtil.isEmpty(values) ? null : values.get(0); |
|||
} |
|||
|
|||
private List<String> headerList(String name) { |
|||
if (StrUtil.isBlank(name)) { |
|||
return null; |
|||
} else { |
|||
CaseInsensitiveMap<String, List<String>> headersIgnoreCase = new CaseInsensitiveMap<>(getHeaders()); |
|||
return headersIgnoreCase.get(name.trim()); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public String toString() { |
|||
return "IJPayHttpResponse{" + |
|||
"body='" + body + '\'' + |
|||
", status=" + status + |
|||
", headers=" + headers + |
|||
'}'; |
|||
} |
|||
} |
@ -0,0 +1,521 @@ |
|||
package com.yxt.pay.utils.wx; |
|||
|
|||
import cn.hutool.core.codec.Base64; |
|||
import cn.hutool.core.io.FileUtil; |
|||
import cn.hutool.core.io.resource.ClassPathResource; |
|||
import cn.hutool.core.io.resource.Resource; |
|||
import cn.hutool.core.lang.Snowflake; |
|||
import cn.hutool.core.util.CharsetUtil; |
|||
import cn.hutool.core.util.IdUtil; |
|||
import cn.hutool.core.util.StrUtil; |
|||
import cn.hutool.crypto.SecureUtil; |
|||
import cn.hutool.crypto.digest.HmacAlgorithm; |
|||
import com.ijpay.core.XmlHelper; |
|||
import com.ijpay.core.enums.RequestMethod; |
|||
|
|||
import javax.crypto.BadPaddingException; |
|||
import javax.crypto.Cipher; |
|||
import javax.crypto.IllegalBlockSizeException; |
|||
import javax.crypto.NoSuchPaddingException; |
|||
import java.io.File; |
|||
import java.io.InputStream; |
|||
import java.io.UnsupportedEncodingException; |
|||
import java.net.URLEncoder; |
|||
import java.nio.charset.StandardCharsets; |
|||
import java.security.InvalidKeyException; |
|||
import java.security.NoSuchAlgorithmException; |
|||
import java.security.PrivateKey; |
|||
import java.security.cert.*; |
|||
import java.util.*; |
|||
|
|||
/** |
|||
* <p>IJPay 让支付触手可及,封装了微信支付、支付宝支付、银联支付常用的支付方式以及各种常用的接口。</p> |
|||
* |
|||
* <p>不依赖任何第三方 mvc 框架,仅仅作为工具使用简单快速完成支付模块的开发,可轻松嵌入到任何系统里。 </p> |
|||
* |
|||
* <p>IJPay 交流群: 723992875</p> |
|||
* |
|||
* <p>Node.js 版: https://gitee.com/javen205/TNWX</p>
|
|||
* |
|||
* <p>IJPay 工具类</p> |
|||
* |
|||
* @author Javen |
|||
*/ |
|||
public class PayKit { |
|||
|
|||
/** |
|||
* 生成16进制的 sha256 字符串 |
|||
* |
|||
* @param data 数据 |
|||
* @param key 密钥 |
|||
* @return sha256 字符串 |
|||
*/ |
|||
public static String hmacSha256(String data, String key) { |
|||
return SecureUtil.hmac(HmacAlgorithm.HmacSHA256, key).digestHex(data); |
|||
} |
|||
|
|||
/** |
|||
* SHA1加密文件,生成16进制SHA1字符串<br> |
|||
* |
|||
* @param dataFile 被加密文件 |
|||
* @return SHA1 字符串 |
|||
*/ |
|||
public static String sha1(File dataFile) { |
|||
return SecureUtil.sha1(dataFile); |
|||
} |
|||
|
|||
/** |
|||
* SHA1加密,生成16进制SHA1字符串<br> |
|||
* |
|||
* @param data 数据 |
|||
* @return SHA1 字符串 |
|||
*/ |
|||
public static String sha1(InputStream data) { |
|||
return SecureUtil.sha1(data); |
|||
} |
|||
|
|||
/** |
|||
* SHA1加密,生成16进制SHA1字符串<br> |
|||
* |
|||
* @param data 数据 |
|||
* @return SHA1 字符串 |
|||
*/ |
|||
public static String sha1(String data) { |
|||
return SecureUtil.sha1(data); |
|||
} |
|||
|
|||
/** |
|||
* 生成16进制 MD5 字符串 |
|||
* |
|||
* @param data 数据 |
|||
* @return MD5 字符串 |
|||
*/ |
|||
public static String md5(String data) { |
|||
return SecureUtil.md5(data); |
|||
} |
|||
|
|||
/** |
|||
* AES 解密 |
|||
* |
|||
* @param base64Data 需要解密的数据 |
|||
* @param key 密钥 |
|||
* @return 解密后的数据 |
|||
*/ |
|||
public static String decryptData(String base64Data, String key) { |
|||
return SecureUtil.aes(md5(key).toLowerCase().getBytes()).decryptStr(base64Data); |
|||
} |
|||
|
|||
/** |
|||
* AES 加密 |
|||
* |
|||
* @param data 需要加密的数据 |
|||
* @param key 密钥 |
|||
* @return 加密后的数据 |
|||
*/ |
|||
public static String encryptData(String data, String key) { |
|||
return SecureUtil.aes(md5(key).toLowerCase().getBytes()).encryptBase64(data.getBytes()); |
|||
} |
|||
|
|||
/** |
|||
* 简化的UUID,去掉了横线,使用性能更好的 ThreadLocalRandom 生成UUID |
|||
* |
|||
* @return 简化的 UUID,去掉了横线 |
|||
*/ |
|||
public static String generateStr() { |
|||
return IdUtil.fastSimpleUUID(); |
|||
} |
|||
|
|||
/** |
|||
* 雪花算法 |
|||
* |
|||
* @param workerId 终端ID |
|||
* @param dataCenterId 数据中心ID |
|||
* @return {@link Snowflake} |
|||
*/ |
|||
public static Snowflake getSnowflake(long workerId, long dataCenterId) { |
|||
return IdUtil.getSnowflake(workerId, dataCenterId); |
|||
} |
|||
|
|||
/** |
|||
* 把所有元素排序 |
|||
* |
|||
* @param params 需要排序并参与字符拼接的参数组 |
|||
* @return 拼接后字符串 |
|||
*/ |
|||
public static String createLinkString(Map<String, String> params) { |
|||
return createLinkString(params, false); |
|||
} |
|||
|
|||
/** |
|||
* @param params 需要排序并参与字符拼接的参数组 |
|||
* @param encode 是否进行URLEncoder |
|||
* @return 拼接后字符串 |
|||
*/ |
|||
public static String createLinkString(Map<String, String> params, boolean encode) { |
|||
return createLinkString(params, "&", encode); |
|||
} |
|||
|
|||
/** |
|||
* @param params 需要排序并参与字符拼接的参数组 |
|||
* @param connStr 连接符号 |
|||
* @param encode 是否进行URLEncoder |
|||
* @return 拼接后字符串 |
|||
*/ |
|||
public static String createLinkString(Map<String, String> params, String connStr, boolean encode) { |
|||
return createLinkString(params, connStr, encode, false); |
|||
} |
|||
|
|||
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 并构建 xml 数据 |
|||
* |
|||
* @param params 需要遍历的 Map |
|||
* @param prefix xml 前缀 |
|||
* @param suffix xml 后缀 |
|||
* @return xml 字符串 |
|||
*/ |
|||
public static StringBuffer forEachMap(Map<String, String> params, String prefix, String suffix) { |
|||
StringBuffer xml = new StringBuffer(); |
|||
if (StrUtil.isNotEmpty(prefix)) { |
|||
xml.append(prefix); |
|||
} |
|||
for (Map.Entry<String, String> entry : params.entrySet()) { |
|||
String key = entry.getKey(); |
|||
String value = entry.getValue(); |
|||
// 略过空值
|
|||
if (StrUtil.isEmpty(value)) { |
|||
continue; |
|||
} |
|||
xml.append("<").append(key).append(">"); |
|||
xml.append(entry.getValue()); |
|||
xml.append("</").append(key).append(">"); |
|||
} |
|||
if (StrUtil.isNotEmpty(suffix)) { |
|||
xml.append(suffix); |
|||
} |
|||
return xml; |
|||
} |
|||
|
|||
/** |
|||
* 微信下单 map to xml |
|||
* |
|||
* @param params Map 参数 |
|||
* @return xml 字符串 |
|||
*/ |
|||
public static String toXml(Map<String, String> params) { |
|||
StringBuffer xml = forEachMap(params, "<xml>", "</xml>"); |
|||
return xml.toString(); |
|||
} |
|||
|
|||
/** |
|||
* 针对支付的 xml,没有嵌套节点的简单处理 |
|||
* |
|||
* @param xmlStr xml 字符串 |
|||
* @return 转化后的 Map |
|||
*/ |
|||
public static Map<String, String> xmlToMap(String xmlStr) { |
|||
XmlHelper xmlHelper = XmlHelper.of(xmlStr); |
|||
return xmlHelper.toMap(); |
|||
} |
|||
|
|||
/** |
|||
* 构造签名串 |
|||
* |
|||
* @param method {@link RequestMethod} GET,POST,PUT等 |
|||
* @param url 请求接口 /v3/certificates |
|||
* @param timestamp 获取发起请求时的系统当前时间戳 |
|||
* @param nonceStr 随机字符串 |
|||
* @param body 请求报文主体 |
|||
* @return 待签名字符串 |
|||
*/ |
|||
public static String buildSignMessage(RequestMethod method, String url, long timestamp, String nonceStr, String body) { |
|||
ArrayList<String> arrayList = new ArrayList<>(); |
|||
arrayList.add(method.toString()); |
|||
arrayList.add(url); |
|||
arrayList.add(String.valueOf(timestamp)); |
|||
arrayList.add(nonceStr); |
|||
arrayList.add(body); |
|||
return buildSignMessage(arrayList); |
|||
} |
|||
|
|||
/** |
|||
* 构造签名串 |
|||
* |
|||
* @param timestamp 应答时间戳 |
|||
* @param nonceStr 应答随机串 |
|||
* @param body 应答报文主体 |
|||
* @return 应答待签名字符串 |
|||
*/ |
|||
public static String buildSignMessage(String timestamp, String nonceStr, String body) { |
|||
ArrayList<String> arrayList = new ArrayList<>(); |
|||
arrayList.add(timestamp); |
|||
arrayList.add(nonceStr); |
|||
arrayList.add(body); |
|||
return buildSignMessage(arrayList); |
|||
} |
|||
|
|||
/** |
|||
* 构造签名串 |
|||
* |
|||
* @param signMessage 待签名的参数 |
|||
* @return 构造后带待签名串 |
|||
*/ |
|||
public static String buildSignMessage(ArrayList<String> signMessage) { |
|||
if (signMessage == null || signMessage.size() <= 0) { |
|||
return null; |
|||
} |
|||
StringBuilder sbf = new StringBuilder(); |
|||
for (String str : signMessage) { |
|||
sbf.append(str).append("\n"); |
|||
} |
|||
return sbf.toString(); |
|||
} |
|||
|
|||
/** |
|||
* v3 接口创建签名 |
|||
* |
|||
* @param signMessage 待签名的参数 |
|||
* @param keyPath key.pem 证书路径 |
|||
* @return 生成 v3 签名 |
|||
* @throws Exception 异常信息 |
|||
*/ |
|||
public static String createSign(ArrayList<String> signMessage, String keyPath) throws Exception { |
|||
return createSign(buildSignMessage(signMessage), keyPath); |
|||
} |
|||
|
|||
/** |
|||
* v3 接口创建签名 |
|||
* |
|||
* @param signMessage 待签名的参数 |
|||
* @param privateKey 商户私钥 |
|||
* @return 生成 v3 签名 |
|||
* @throws Exception 异常信息 |
|||
*/ |
|||
public static String createSign(ArrayList<String> signMessage, PrivateKey privateKey) throws Exception { |
|||
return createSign(buildSignMessage(signMessage), privateKey); |
|||
} |
|||
|
|||
|
|||
/** |
|||
* v3 接口创建签名 |
|||
* |
|||
* @param signMessage 待签名的参数 |
|||
* @param keyPath key.pem 证书路径 |
|||
* @return 生成 v3 签名 |
|||
* @throws Exception 异常信息 |
|||
*/ |
|||
public static String createSign(String signMessage, String keyPath) throws Exception { |
|||
if (StrUtil.isEmpty(signMessage)) { |
|||
return null; |
|||
} |
|||
// 获取商户私钥
|
|||
PrivateKey privateKey = PayKit.getPrivateKey(keyPath); |
|||
// 生成签名
|
|||
return RsaKit.encryptByPrivateKey(signMessage, privateKey); |
|||
} |
|||
|
|||
/** |
|||
* v3 接口创建签名 |
|||
* |
|||
* @param signMessage 待签名的参数 |
|||
* @param privateKey 商户私钥 |
|||
* @return 生成 v3 签名 |
|||
* @throws Exception 异常信息 |
|||
*/ |
|||
public static String createSign(String signMessage, PrivateKey privateKey) throws Exception { |
|||
if (StrUtil.isEmpty(signMessage)) { |
|||
return null; |
|||
} |
|||
// 生成签名
|
|||
return RsaKit.encryptByPrivateKey(signMessage, privateKey); |
|||
} |
|||
|
|||
/** |
|||
* 获取授权认证信息 |
|||
* |
|||
* @param mchId 商户号 |
|||
* @param serialNo 商户API证书序列号 |
|||
* @param nonceStr 请求随机串 |
|||
* @param timestamp 时间戳 |
|||
* @param signature 签名值 |
|||
* @param authType 认证类型,目前为WECHATPAY2-SHA256-RSA2048 |
|||
* @return 请求头 Authorization |
|||
*/ |
|||
public static String getAuthorization(String mchId, String serialNo, String nonceStr, String timestamp, String signature, String authType) { |
|||
Map<String, String> params = new HashMap<>(5); |
|||
params.put("mchid", mchId); |
|||
params.put("serial_no", serialNo); |
|||
params.put("nonce_str", nonceStr); |
|||
params.put("timestamp", timestamp); |
|||
params.put("signature", signature); |
|||
return authType.concat(" ").concat(createLinkString(params, ",", false, true)); |
|||
} |
|||
|
|||
/** |
|||
* 获取商户私钥 |
|||
* |
|||
* @param keyPath 商户私钥证书路径 |
|||
* @return {@link String} 商户私钥 |
|||
* @throws Exception 异常信息 |
|||
*/ |
|||
public static String getPrivateKeyStr(String keyPath) throws Exception { |
|||
return RsaKit.getPrivateKeyStr(getPrivateKey(keyPath)); |
|||
} |
|||
|
|||
/** |
|||
* 获取商户私钥 |
|||
* |
|||
* @param keyPath 商户私钥证书路径 |
|||
* @return {@link PrivateKey} 商户私钥 |
|||
* @throws Exception 异常信息 |
|||
*/ |
|||
public static PrivateKey getPrivateKey(String keyPath) throws Exception { |
|||
String originalKey = FileUtil.readUtf8String(keyPath); |
|||
return getPrivateKeyByKeyContent(originalKey); |
|||
} |
|||
|
|||
/** |
|||
* 获取商户私钥 |
|||
* |
|||
* @param originalKey 私钥文本内容 |
|||
* @return {@link PrivateKey} 商户私钥 |
|||
* @throws Exception 异常信息 |
|||
*/ |
|||
public static PrivateKey getPrivateKeyByKeyContent(String originalKey) throws Exception { |
|||
String privateKey = originalKey |
|||
.replace("-----BEGIN PRIVATE KEY-----", "") |
|||
.replace("-----END PRIVATE KEY-----", "") |
|||
.replaceAll("\\s+", ""); |
|||
return RsaKit.loadPrivateKey(privateKey); |
|||
} |
|||
|
|||
/** |
|||
* 获取证书 |
|||
* |
|||
* @param inputStream 证书文件 |
|||
* @return {@link X509Certificate} 获取证书 |
|||
*/ |
|||
public static X509Certificate getCertificate(InputStream inputStream) { |
|||
try { |
|||
CertificateFactory cf = CertificateFactory.getInstance("X509"); |
|||
X509Certificate cert = (X509Certificate) cf.generateCertificate(inputStream); |
|||
cert.checkValidity(); |
|||
return cert; |
|||
} catch (CertificateExpiredException e) { |
|||
throw new RuntimeException("证书已过期", e); |
|||
} catch (CertificateNotYetValidException e) { |
|||
throw new RuntimeException("证书尚未生效", e); |
|||
} catch (CertificateException e) { |
|||
throw new RuntimeException("无效的证书", e); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 公钥加密 |
|||
* |
|||
* @param data 待加密数据 |
|||
* @param certificate 平台公钥证书 |
|||
* @return 加密后的数据 |
|||
* @throws Exception 异常信息 |
|||
*/ |
|||
public static String rsaEncryptOAEP(String data, X509Certificate certificate) throws Exception { |
|||
try { |
|||
Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-1AndMGF1Padding"); |
|||
cipher.init(Cipher.ENCRYPT_MODE, certificate.getPublicKey()); |
|||
|
|||
byte[] dataByte = data.getBytes(StandardCharsets.UTF_8); |
|||
byte[] cipherData = cipher.doFinal(dataByte); |
|||
return Base64.encode(cipherData); |
|||
} catch (NoSuchAlgorithmException | NoSuchPaddingException e) { |
|||
throw new RuntimeException("当前Java环境不支持RSA v1.5/OAEP", e); |
|||
} catch (InvalidKeyException e) { |
|||
throw new IllegalArgumentException("无效的证书", e); |
|||
} catch (IllegalBlockSizeException | BadPaddingException e) { |
|||
throw new IllegalBlockSizeException("加密原串的长度不能超过214字节"); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 私钥解密 |
|||
* |
|||
* @param cipherText 加密字符 |
|||
* @param privateKey 私钥 |
|||
* @return 解密后的数据 |
|||
* @throws Exception 异常信息 |
|||
*/ |
|||
public static String rsaDecryptOAEP(String cipherText, PrivateKey privateKey) throws Exception { |
|||
try { |
|||
Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-1AndMGF1Padding"); |
|||
cipher.init(Cipher.DECRYPT_MODE, privateKey); |
|||
byte[] data = Base64.decode(cipherText); |
|||
return new String(cipher.doFinal(data), StandardCharsets.UTF_8); |
|||
} catch (NoSuchPaddingException | NoSuchAlgorithmException e) { |
|||
throw new RuntimeException("当前Java环境不支持RSA v1.5/OAEP", e); |
|||
} catch (InvalidKeyException e) { |
|||
throw new IllegalArgumentException("无效的私钥", e); |
|||
} catch (BadPaddingException | IllegalBlockSizeException e) { |
|||
throw new BadPaddingException("解密失败"); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 传入 classPath 静态资源路径返回文件输入流 |
|||
* |
|||
* @param classPath 静态资源路径 |
|||
* @return InputStream |
|||
*/ |
|||
public static InputStream getFileToStream(String classPath) { |
|||
Resource resource = new ClassPathResource(classPath); |
|||
return resource.getStream(); |
|||
} |
|||
|
|||
/** |
|||
* 传入 classPath 静态资源路径返回绝对路径 |
|||
* |
|||
* @param classPath 静态资源路径 |
|||
* @return 绝对路径 |
|||
*/ |
|||
public static String getAbsolutePath(String classPath) { |
|||
return new ClassPathResource(classPath).getAbsolutePath(); |
|||
} |
|||
} |
@ -0,0 +1,64 @@ |
|||
package com.yxt.pay.utils.wx; |
|||
|
|||
/** |
|||
* <p>IJPay 让支付触手可及,封装了微信支付、支付宝支付、银联支付常用的支付方式以及各种常用的接口。</p> |
|||
* |
|||
* <p>不依赖任何第三方 mvc 框架,仅仅作为工具使用简单快速完成支付模块的开发,可轻松嵌入到任何系统里。 </p> |
|||
* |
|||
* <p>IJPay 交流群: 723992875</p> |
|||
* |
|||
* <p>Node.js 版: https://gitee.com/javen205/TNWX</p>
|
|||
* |
|||
* <p>HTTP 请求的方法</p> |
|||
* |
|||
* @author Javen |
|||
*/ |
|||
public enum RequestMethod { |
|||
/** |
|||
* 上传实质是 post 请求 |
|||
*/ |
|||
UPLOAD("POST"), |
|||
/** |
|||
* post 请求 |
|||
*/ |
|||
POST("POST"), |
|||
/** |
|||
* get 请求 |
|||
*/ |
|||
GET("GET"), |
|||
/** |
|||
* put 请求 |
|||
*/ |
|||
PUT("PUT"), |
|||
/** |
|||
* delete 请求 |
|||
*/ |
|||
DELETE("DELETE"), |
|||
/** |
|||
* options 请求 |
|||
*/ |
|||
OPTIONS("OPTIONS"), |
|||
/** |
|||
* head 请求 |
|||
*/ |
|||
HEAD("HEAD"), |
|||
/** |
|||
* trace 请求 |
|||
*/ |
|||
TRACE("TRACE"), |
|||
/** |
|||
* connect 请求 |
|||
*/ |
|||
CONNECT("CONNECT"); |
|||
|
|||
private final String method; |
|||
|
|||
RequestMethod(String method) { |
|||
this.method = method; |
|||
} |
|||
|
|||
@Override |
|||
public String toString() { |
|||
return this.method; |
|||
} |
|||
} |
@ -0,0 +1,387 @@ |
|||
package com.yxt.pay.utils.wx; |
|||
|
|||
import cn.hutool.core.codec.Base64; |
|||
import cn.hutool.core.util.StrUtil; |
|||
|
|||
import javax.crypto.Cipher; |
|||
import java.io.ByteArrayOutputStream; |
|||
import java.math.BigInteger; |
|||
import java.nio.charset.StandardCharsets; |
|||
import java.security.*; |
|||
import java.security.interfaces.RSAPrivateKey; |
|||
import java.security.interfaces.RSAPublicKey; |
|||
import java.security.spec.*; |
|||
import java.util.HashMap; |
|||
import java.util.Map; |
|||
|
|||
/** |
|||
* <p>IJPay 让支付触手可及,封装了微信支付、支付宝支付、银联支付常用的支付方式以及各种常用的接口。</p> |
|||
* |
|||
* <p>不依赖任何第三方 mvc 框架,仅仅作为工具使用简单快速完成支付模块的开发,可轻松嵌入到任何系统里。 </p> |
|||
* |
|||
* <p>IJPay 交流群: 723992875</p> |
|||
* |
|||
* <p>Node.js 版: https://gitee.com/javen205/TNWX</p>
|
|||
* |
|||
* <p>RSA 非对称加密工具类</p> |
|||
* |
|||
* @author Javen |
|||
*/ |
|||
public class RsaKit { |
|||
|
|||
/** |
|||
* RSA最大加密明文大小 |
|||
*/ |
|||
private static final int MAX_ENCRYPT_BLOCK = 117; |
|||
|
|||
/** |
|||
* RSA最大解密密文大小 |
|||
*/ |
|||
private static final int MAX_DECRYPT_BLOCK = 128; |
|||
|
|||
/** |
|||
* 加密算法RSA |
|||
*/ |
|||
private static final String KEY_ALGORITHM = "RSA"; |
|||
|
|||
/** |
|||
* 生成公钥和私钥 |
|||
* |
|||
* @throws Exception 异常信息 |
|||
*/ |
|||
public static Map<String, String> getKeys() throws Exception { |
|||
KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(KEY_ALGORITHM); |
|||
keyPairGen.initialize(1024); |
|||
KeyPair keyPair = keyPairGen.generateKeyPair(); |
|||
RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic(); |
|||
RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate(); |
|||
|
|||
String publicKeyStr = getPublicKeyStr(publicKey); |
|||
String privateKeyStr = getPrivateKeyStr(privateKey); |
|||
|
|||
Map<String, String> map = new HashMap<String, String>(2); |
|||
map.put("publicKey", publicKeyStr); |
|||
map.put("privateKey", privateKeyStr); |
|||
|
|||
System.out.println("公钥\r\n" + publicKeyStr); |
|||
System.out.println("私钥\r\n" + privateKeyStr); |
|||
return map; |
|||
} |
|||
|
|||
/** |
|||
* 使用模和指数生成RSA公钥 |
|||
* 注意:【此代码用了默认补位方式,为RSA/None/PKCS1Padding,不同JDK默认的补位方式可能不同,如Android默认是RSA |
|||
* /None/NoPadding】 |
|||
* |
|||
* @param modulus 模 |
|||
* @param exponent 公钥指数 |
|||
* @return {@link RSAPublicKey} |
|||
*/ |
|||
public static RSAPublicKey getPublicKey(String modulus, String exponent) { |
|||
try { |
|||
BigInteger b1 = new BigInteger(modulus); |
|||
BigInteger b2 = new BigInteger(exponent); |
|||
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM); |
|||
RSAPublicKeySpec keySpec = new RSAPublicKeySpec(b1, b2); |
|||
return (RSAPublicKey) keyFactory.generatePublic(keySpec); |
|||
} catch (Exception e) { |
|||
e.printStackTrace(); |
|||
return null; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 使用模和指数生成RSA私钥 |
|||
* 注意:【此代码用了默认补位方式,为RSA/None/PKCS1Padding,不同JDK默认的补位方式可能不同,如Android默认是RSA |
|||
* /None/NoPadding】 |
|||
* |
|||
* @param modulus 模 |
|||
* @param exponent 指数 |
|||
* @return {@link RSAPrivateKey} |
|||
*/ |
|||
public static RSAPrivateKey getPrivateKey(String modulus, String exponent) { |
|||
try { |
|||
BigInteger b1 = new BigInteger(modulus); |
|||
BigInteger b2 = new BigInteger(exponent); |
|||
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM); |
|||
RSAPrivateKeySpec keySpec = new RSAPrivateKeySpec(b1, b2); |
|||
return (RSAPrivateKey) keyFactory.generatePrivate(keySpec); |
|||
} catch (Exception e) { |
|||
e.printStackTrace(); |
|||
return null; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 公钥加密 |
|||
* |
|||
* @param data 需要加密的数据 |
|||
* @param publicKey 公钥 |
|||
* @return 加密后的数据 |
|||
* @throws Exception 异常信息 |
|||
*/ |
|||
public static String encryptByPublicKey(String data, String publicKey) throws Exception { |
|||
return encryptByPublicKey(data, publicKey, "RSA/ECB/PKCS1Padding"); |
|||
} |
|||
|
|||
/** |
|||
* 公钥加密 |
|||
* |
|||
* @param data 需要加密的数据 |
|||
* @param publicKey 公钥 |
|||
* @return 加密后的数据 |
|||
* @throws Exception 异常信息 |
|||
*/ |
|||
public static String encryptByPublicKeyByWx(String data, String publicKey) throws Exception { |
|||
return encryptByPublicKey(data, publicKey, "RSA/ECB/OAEPWITHSHA-1ANDMGF1PADDING"); |
|||
} |
|||
|
|||
/** |
|||
* 公钥加密 |
|||
* |
|||
* @param data 需要加密的数据 |
|||
* @param publicKey 公钥 |
|||
* @param fillMode 填充模式 |
|||
* @return 加密后的数据 |
|||
* @throws Exception 异常信息 |
|||
*/ |
|||
public static String encryptByPublicKey(String data, String publicKey, String fillMode) throws Exception { |
|||
byte[] dataByte = data.getBytes(StandardCharsets.UTF_8); |
|||
byte[] keyBytes = Base64.decode(publicKey); |
|||
X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(keyBytes); |
|||
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM); |
|||
Key key = keyFactory.generatePublic(x509KeySpec); |
|||
// 对数据加密
|
|||
Cipher cipher = Cipher.getInstance(fillMode); |
|||
cipher.init(Cipher.ENCRYPT_MODE, key); |
|||
int inputLen = dataByte.length; |
|||
ByteArrayOutputStream out = new ByteArrayOutputStream(); |
|||
int offSet = 0; |
|||
byte[] cache; |
|||
int i = 0; |
|||
// 对数据分段加密
|
|||
while (inputLen - offSet > 0) { |
|||
if (inputLen - offSet > MAX_ENCRYPT_BLOCK) { |
|||
cache = cipher.doFinal(dataByte, offSet, MAX_ENCRYPT_BLOCK); |
|||
} else { |
|||
cache = cipher.doFinal(dataByte, offSet, inputLen - offSet); |
|||
} |
|||
out.write(cache, 0, cache.length); |
|||
i++; |
|||
offSet = i * MAX_ENCRYPT_BLOCK; |
|||
} |
|||
byte[] encryptedData = out.toByteArray(); |
|||
out.close(); |
|||
return StrUtil.str(Base64.encode(encryptedData)); |
|||
} |
|||
|
|||
/** |
|||
* 私钥签名 |
|||
* |
|||
* @param data 需要加密的数据 |
|||
* @param privateKey 私钥 |
|||
* @return 加密后的数据 |
|||
* @throws Exception 异常信息 |
|||
*/ |
|||
public static String encryptByPrivateKey(String data, String privateKey) throws Exception { |
|||
PKCS8EncodedKeySpec priPkcs8 = new PKCS8EncodedKeySpec(Base64.decode(privateKey)); |
|||
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM); |
|||
PrivateKey priKey = keyFactory.generatePrivate(priPkcs8); |
|||
Signature signature = Signature.getInstance("SHA256WithRSA"); |
|||
|
|||
signature.initSign(priKey); |
|||
signature.update(data.getBytes(StandardCharsets.UTF_8)); |
|||
byte[] signed = signature.sign(); |
|||
return StrUtil.str(Base64.encode(signed)); |
|||
} |
|||
|
|||
/** |
|||
* 私钥签名 |
|||
* |
|||
* @param data 需要加密的数据 |
|||
* @param privateKey 私钥 |
|||
* @return 加密后的数据 |
|||
* @throws Exception 异常信息 |
|||
*/ |
|||
public static String encryptByPrivateKey(String data, PrivateKey privateKey) throws Exception { |
|||
Signature signature = Signature.getInstance("SHA256WithRSA"); |
|||
signature.initSign(privateKey); |
|||
signature.update(data.getBytes(StandardCharsets.UTF_8)); |
|||
byte[] signed = signature.sign(); |
|||
return StrUtil.str(Base64.encode(signed)); |
|||
} |
|||
|
|||
/** |
|||
* 公钥验证签名 |
|||
* |
|||
* @param data 需要加密的数据 |
|||
* @param sign 签名 |
|||
* @param publicKey 公钥 |
|||
* @return 验证结果 |
|||
* @throws Exception 异常信息 |
|||
*/ |
|||
public static boolean checkByPublicKey(String data, String sign, String publicKey) throws Exception { |
|||
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM); |
|||
byte[] encodedKey = Base64.decode(publicKey); |
|||
PublicKey pubKey = keyFactory.generatePublic(new X509EncodedKeySpec(encodedKey)); |
|||
Signature signature = Signature.getInstance("SHA256WithRSA"); |
|||
signature.initVerify(pubKey); |
|||
signature.update(data.getBytes(StandardCharsets.UTF_8)); |
|||
return signature.verify(Base64.decode(sign.getBytes(StandardCharsets.UTF_8))); |
|||
} |
|||
|
|||
/** |
|||
* 公钥验证签名 |
|||
* |
|||
* @param data 需要加密的数据 |
|||
* @param sign 签名 |
|||
* @param publicKey 公钥 |
|||
* @return 验证结果 |
|||
* @throws Exception 异常信息 |
|||
*/ |
|||
public static boolean checkByPublicKey(String data, String sign, PublicKey publicKey) throws Exception { |
|||
Signature signature = Signature.getInstance("SHA256WithRSA"); |
|||
signature.initVerify(publicKey); |
|||
signature.update(data.getBytes(StandardCharsets.UTF_8)); |
|||
return signature.verify(Base64.decode(sign.getBytes(StandardCharsets.UTF_8))); |
|||
} |
|||
|
|||
/** |
|||
* 私钥解密 |
|||
* |
|||
* @param data 需要解密的数据 |
|||
* @param privateKey 私钥 |
|||
* @return 解密后的数据 |
|||
* @throws Exception 异常信息 |
|||
*/ |
|||
public static String decryptByPrivateKey(String data, String privateKey) throws Exception { |
|||
return decryptByPrivateKey(data, privateKey, "RSA/ECB/PKCS1Padding"); |
|||
} |
|||
|
|||
/** |
|||
* 私钥解密 |
|||
* |
|||
* @param data 需要解密的数据 |
|||
* @param privateKey 私钥 |
|||
* @return 解密后的数据 |
|||
* @throws Exception 异常信息 |
|||
*/ |
|||
public static String decryptByPrivateKeyByWx(String data, String privateKey) throws Exception { |
|||
return decryptByPrivateKey(data, privateKey, "RSA/ECB/OAEPWITHSHA-1ANDMGF1PADDING"); |
|||
} |
|||
|
|||
/** |
|||
* 私钥解密 |
|||
* |
|||
* @param data 需要解密的数据 |
|||
* @param privateKey 私钥 |
|||
* @param fillMode 填充模式 |
|||
* @return 解密后的数据 |
|||
* @throws Exception 异常信息 |
|||
*/ |
|||
public static String decryptByPrivateKey(String data, String privateKey, String fillMode) throws Exception { |
|||
byte[] encryptedData = Base64.decode(data); |
|||
byte[] keyBytes = Base64.decode(privateKey); |
|||
PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes); |
|||
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM); |
|||
Key key = keyFactory.generatePrivate(pkcs8KeySpec); |
|||
Cipher cipher = Cipher.getInstance(fillMode); |
|||
|
|||
cipher.init(Cipher.DECRYPT_MODE, key); |
|||
int inputLen = encryptedData.length; |
|||
ByteArrayOutputStream out = new ByteArrayOutputStream(); |
|||
int offSet = 0; |
|||
byte[] cache; |
|||
int i = 0; |
|||
// 对数据分段解密
|
|||
while (inputLen - offSet > 0) { |
|||
if (inputLen - offSet > MAX_DECRYPT_BLOCK) { |
|||
cache = cipher.doFinal(encryptedData, offSet, MAX_DECRYPT_BLOCK); |
|||
} else { |
|||
cache = cipher.doFinal(encryptedData, offSet, inputLen - offSet); |
|||
} |
|||
out.write(cache, 0, cache.length); |
|||
i++; |
|||
offSet = i * MAX_DECRYPT_BLOCK; |
|||
} |
|||
byte[] decryptedData = out.toByteArray(); |
|||
out.close(); |
|||
return new String(decryptedData); |
|||
} |
|||
|
|||
/** |
|||
* 从字符串中加载公钥 |
|||
* |
|||
* @param publicKeyStr 公钥数据字符串 |
|||
* @throws Exception 异常信息 |
|||
*/ |
|||
public static PublicKey loadPublicKey(String publicKeyStr) throws Exception { |
|||
try { |
|||
byte[] buffer = Base64.decode(publicKeyStr); |
|||
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM); |
|||
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(buffer); |
|||
return keyFactory.generatePublic(keySpec); |
|||
} catch (NoSuchAlgorithmException e) { |
|||
throw new Exception("无此算法"); |
|||
} catch (InvalidKeySpecException e) { |
|||
throw new Exception("公钥非法"); |
|||
} catch (NullPointerException e) { |
|||
throw new Exception("公钥数据为空"); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 从字符串中加载私钥<br> |
|||
* 加载时使用的是PKCS8EncodedKeySpec(PKCS#8编码的Key指令)。 |
|||
* |
|||
* @param privateKeyStr 私钥 |
|||
* @return {@link PrivateKey} |
|||
* @throws Exception 异常信息 |
|||
*/ |
|||
public static PrivateKey loadPrivateKey(String privateKeyStr) throws Exception { |
|||
try { |
|||
byte[] buffer = Base64.decode(privateKeyStr); |
|||
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(buffer); |
|||
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM); |
|||
return keyFactory.generatePrivate(keySpec); |
|||
} catch (NoSuchAlgorithmException e) { |
|||
throw new Exception("无此算法"); |
|||
} catch (InvalidKeySpecException e) { |
|||
throw new Exception("私钥非法"); |
|||
} catch (NullPointerException e) { |
|||
throw new Exception("私钥数据为空"); |
|||
} |
|||
} |
|||
|
|||
public static String getPrivateKeyStr(PrivateKey privateKey) { |
|||
return Base64.encode(privateKey.getEncoded()); |
|||
} |
|||
|
|||
public static String getPublicKeyStr(PublicKey publicKey) { |
|||
return Base64.encode(publicKey.getEncoded()); |
|||
} |
|||
|
|||
public static void main(String[] args) throws Exception { |
|||
Map<String, String> keys = getKeys(); |
|||
String publicKey = keys.get("publicKey"); |
|||
String privateKey = keys.get("privateKey"); |
|||
String content = "我是Javen,I am Javen"; |
|||
String encrypt = encryptByPublicKey(content, publicKey); |
|||
String decrypt = decryptByPrivateKey(encrypt, privateKey); |
|||
System.out.println("加密之后:" + encrypt); |
|||
System.out.println("解密之后:" + decrypt); |
|||
|
|||
System.out.println("======华丽的分割线========="); |
|||
|
|||
content = "我是Javen,I am Javen"; |
|||
encrypt = encryptByPublicKeyByWx(content, publicKey); |
|||
decrypt = decryptByPrivateKeyByWx(encrypt, privateKey); |
|||
System.out.println("加密之后:" + encrypt); |
|||
System.out.println("解密之后:" + decrypt); |
|||
|
|||
//OPPO
|
|||
String sign = encryptByPrivateKey(content, privateKey); |
|||
System.out.println("加密之后:" + sign); |
|||
System.out.println(checkByPublicKey(content, sign, publicKey)); |
|||
} |
|||
} |
@ -0,0 +1,44 @@ |
|||
package com.yxt.pay.utils.wx; |
|||
|
|||
/** |
|||
* <p>IJPay 让支付触手可及,封装了微信支付、支付宝支付、银联支付常用的支付方式以及各种常用的接口。</p> |
|||
* |
|||
* <p>不依赖任何第三方 mvc 框架,仅仅作为工具使用简单快速完成支付模块的开发,可轻松嵌入到任何系统里。 </p> |
|||
* |
|||
* <p>IJPay 交流群: 723992875</p> |
|||
* |
|||
* <p>Node.js 版: https://gitee.com/javen205/TNWX</p>
|
|||
* |
|||
* <p>签名方式</p> |
|||
* |
|||
* @author Javen |
|||
*/ |
|||
public enum SignType { |
|||
/** |
|||
* HMAC-SHA256 加密 |
|||
*/ |
|||
HMACSHA256("HMAC-SHA256"), |
|||
/** |
|||
* MD5 加密 |
|||
*/ |
|||
MD5("MD5"), |
|||
/** |
|||
* RSA |
|||
*/ |
|||
RSA("RSA"); |
|||
|
|||
SignType(String type) { |
|||
this.type = type; |
|||
} |
|||
|
|||
private final String type; |
|||
|
|||
public String getType() { |
|||
return type; |
|||
} |
|||
|
|||
@Override |
|||
public String toString() { |
|||
return type; |
|||
} |
|||
} |
@ -0,0 +1,695 @@ |
|||
package com.yxt.pay.utils.wx; |
|||
|
|||
import cn.hutool.core.io.FileUtil; |
|||
import cn.hutool.core.util.StrUtil; |
|||
import cn.hutool.json.JSONObject; |
|||
import cn.hutool.json.JSONUtil; |
|||
|
|||
import java.io.BufferedInputStream; |
|||
import java.io.InputStream; |
|||
import java.nio.charset.StandardCharsets; |
|||
import java.security.PrivateKey; |
|||
import java.security.PublicKey; |
|||
import java.security.cert.X509Certificate; |
|||
import java.util.ArrayList; |
|||
import java.util.HashMap; |
|||
import java.util.Map; |
|||
|
|||
|
|||
/** |
|||
* <p>IJPay 让支付触手可及,封装了微信支付、支付宝支付、银联支付常用的支付方式以及各种常用的接口。</p> |
|||
* |
|||
* <p>不依赖任何第三方 mvc 框架,仅仅作为工具使用简单快速完成支付模块的开发,可轻松嵌入到任何系统里。 </p> |
|||
* |
|||
* <p>IJPay 交流群: 723992875</p> |
|||
* |
|||
* <p>Node.js 版: https://gitee.com/javen205/TNWX</p>
|
|||
* |
|||
* <p>微信支付工具类</p> |
|||
* |
|||
* @author Javen |
|||
*/ |
|||
public class WxPayKit { |
|||
private static final String FIELD_SIGN = "sign"; |
|||
private static final String FIELD_SIGN_TYPE = "sign_type"; |
|||
|
|||
public static String hmacSha256(String data, String key) { |
|||
return PayKit.hmacSha256(data, key); |
|||
} |
|||
|
|||
public static String md5(String data) { |
|||
return PayKit.md5(data); |
|||
} |
|||
|
|||
/** |
|||
* AES 解密 |
|||
* |
|||
* @param base64Data 需要解密的数据 |
|||
* @param key 密钥 |
|||
* @return 解密后的数据 |
|||
*/ |
|||
public static String decryptData(String base64Data, String key) { |
|||
return PayKit.decryptData(base64Data, key); |
|||
} |
|||
|
|||
/** |
|||
* AES 加密 |
|||
* |
|||
* @param data 需要加密的数据 |
|||
* @param key 密钥 |
|||
* @return 加密后的数据 |
|||
*/ |
|||
public static String encryptData(String data, String key) { |
|||
return PayKit.encryptData(data, key); |
|||
} |
|||
|
|||
public static String generateStr() { |
|||
return PayKit.generateStr(); |
|||
} |
|||
|
|||
|
|||
/** |
|||
* 支付异步通知时校验 sign |
|||
* |
|||
* @param params 参数 |
|||
* @param partnerKey 支付密钥 |
|||
* @return {boolean} |
|||
*/ |
|||
public static boolean verifyNotify(Map<String, String> params, String partnerKey) { |
|||
String sign = params.get("sign"); |
|||
String localSign = createSign(params, partnerKey, SignType.MD5); |
|||
return sign.equals(localSign); |
|||
} |
|||
|
|||
/** |
|||
* 支付异步通知时校验 sign |
|||
* |
|||
* @param params 参数 |
|||
* @param partnerKey 支付密钥 |
|||
* @param signType {@link SignType} |
|||
* @return {@link Boolean} 验证签名结果 |
|||
*/ |
|||
public static boolean verifyNotify(Map<String, String> params, String partnerKey, SignType signType) { |
|||
String sign = params.get("sign"); |
|||
String localSign = createSign(params, partnerKey, signType); |
|||
return sign.equals(localSign); |
|||
} |
|||
|
|||
/** |
|||
* 生成签名 |
|||
* |
|||
* @param params 需要签名的参数 |
|||
* @param partnerKey 密钥 |
|||
* @param signType 签名类型 |
|||
* @return 签名后的数据 |
|||
*/ |
|||
public static String createSign(Map<String, String> params, String partnerKey, SignType signType) { |
|||
if (signType == null) { |
|||
signType = SignType.MD5; |
|||
} |
|||
// 生成签名前先去除sign
|
|||
params.remove(FIELD_SIGN); |
|||
String tempStr = PayKit.createLinkString(params); |
|||
String stringSignTemp = tempStr + "&key=" + partnerKey; |
|||
if (signType == SignType.MD5) { |
|||
return md5(stringSignTemp).toUpperCase(); |
|||
} else { |
|||
return hmacSha256(stringSignTemp, partnerKey).toUpperCase(); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 生成签名 |
|||
* |
|||
* @param params 需要签名的参数 |
|||
* @param secret 企业微信支付应用secret |
|||
* @return 签名后的数据 |
|||
*/ |
|||
public static String createSign(Map<String, String> params, String secret) { |
|||
// 生成签名前先去除sign
|
|||
params.remove(FIELD_SIGN); |
|||
String tempStr = PayKit.createLinkString(params); |
|||
String stringSignTemp = tempStr + "&secret=" + secret; |
|||
return md5(stringSignTemp).toUpperCase(); |
|||
} |
|||
|
|||
/** |
|||
* 构建签名 |
|||
* |
|||
* @param params 需要签名的参数 |
|||
* @param partnerKey 密钥 |
|||
* @param signType 签名类型 |
|||
* @return 签名后的 Map |
|||
*/ |
|||
public static Map<String, String> buildSign(Map<String, String> params, String partnerKey, SignType signType) { |
|||
return buildSign(params, partnerKey, signType, true); |
|||
} |
|||
|
|||
/** |
|||
* 构建签名 |
|||
* |
|||
* @param params 需要签名的参数 |
|||
* @param partnerKey 密钥 |
|||
* @param signType 签名类型 |
|||
* @param haveSignType 签名是否包含 sign_type 字段 |
|||
* @return 签名后的 Map |
|||
*/ |
|||
public static Map<String, String> buildSign(Map<String, String> params, String partnerKey, SignType signType, boolean haveSignType) { |
|||
if (haveSignType) { |
|||
params.put(FIELD_SIGN_TYPE, signType.getType()); |
|||
} |
|||
String sign = createSign(params, partnerKey, signType); |
|||
params.put(FIELD_SIGN, sign); |
|||
return params; |
|||
} |
|||
|
|||
public static StringBuffer forEachMap(Map<String, String> params, String prefix, String suffix) { |
|||
return PayKit.forEachMap(params, prefix, suffix); |
|||
} |
|||
|
|||
/** |
|||
* 微信下单 map to xml |
|||
* |
|||
* @param params Map 参数 |
|||
* @return xml 字符串 |
|||
*/ |
|||
public static String toXml(Map<String, String> params) { |
|||
return PayKit.toXml(params); |
|||
} |
|||
|
|||
/** |
|||
* 针对支付的 xml,没有嵌套节点的简单处理 |
|||
* |
|||
* @param xmlStr xml 字符串 |
|||
* @return 转化后的 Map |
|||
*/ |
|||
public static Map<String, String> xmlToMap(String xmlStr) { |
|||
return PayKit.xmlToMap(xmlStr); |
|||
} |
|||
|
|||
/** |
|||
* <p>生成二维码链接</p> |
|||
* <p>原生支付接口模式一(扫码模式一)</p> |
|||
* |
|||
* @param sign 签名 |
|||
* @param appId 公众账号ID |
|||
* @param mchId 商户号 |
|||
* @param productId 商品ID |
|||
* @param timeStamp 时间戳 |
|||
* @param nonceStr 随机字符串 |
|||
* @return {String} |
|||
*/ |
|||
public static String bizPayUrl(String sign, String appId, String mchId, String productId, String timeStamp, String nonceStr) { |
|||
String rules = "weixin://wxpay/bizpayurl?sign=Temp&appid=Temp&mch_id=Temp&product_id=Temp&time_stamp=Temp&nonce_str=Temp"; |
|||
return replace(rules, "Temp", sign, appId, mchId, productId, timeStamp, nonceStr); |
|||
} |
|||
|
|||
/** |
|||
* <p>生成二维码链接</p> |
|||
* <p>原生支付接口模式一(扫码模式一)</p> |
|||
* |
|||
* @param partnerKey 密钥 |
|||
* @param appId 公众账号ID |
|||
* @param mchId 商户号 |
|||
* @param productId 商品ID |
|||
* @param timeStamp 时间戳 |
|||
* @param nonceStr 随机字符串 |
|||
* @param signType 签名类型 |
|||
* @return {String} |
|||
*/ |
|||
public static String bizPayUrl(String partnerKey, String appId, String mchId, String productId, String timeStamp, String nonceStr, SignType signType) { |
|||
HashMap<String, String> map = new HashMap<>(5); |
|||
map.put("appid", appId); |
|||
map.put("mch_id", mchId); |
|||
map.put("time_stamp", StrUtil.isEmpty(timeStamp) ? Long.toString(System.currentTimeMillis() / 1000) : timeStamp); |
|||
map.put("nonce_str", StrUtil.isEmpty(nonceStr) ? WxPayKit.generateStr() : nonceStr); |
|||
map.put("product_id", productId); |
|||
return bizPayUrl(createSign(map, partnerKey, signType), appId, mchId, productId, timeStamp, nonceStr); |
|||
} |
|||
|
|||
/** |
|||
* <p>生成二维码链接</p> |
|||
* <p>原生支付接口模式一(扫码模式一)</p> |
|||
* |
|||
* @param partnerKey 密钥 |
|||
* @param appId 公众账号ID |
|||
* @param mchId 商户号 |
|||
* @param productId 商品ID |
|||
* @return {String} |
|||
*/ |
|||
public static String bizPayUrl(String partnerKey, String appId, String mchId, String productId) { |
|||
String timeStamp = Long.toString(System.currentTimeMillis() / 1000); |
|||
String nonceStr = WxPayKit.generateStr(); |
|||
HashMap<String, String> map = new HashMap<>(5); |
|||
map.put("appid", appId); |
|||
map.put("mch_id", mchId); |
|||
map.put("time_stamp", timeStamp); |
|||
map.put("nonce_str", nonceStr); |
|||
map.put("product_id", productId); |
|||
return bizPayUrl(createSign(map, partnerKey, null), appId, mchId, productId, timeStamp, nonceStr); |
|||
} |
|||
|
|||
|
|||
/** |
|||
* 替换url中的参数 |
|||
* |
|||
* @param str 原始字符串 |
|||
* @param regex 表达式 |
|||
* @param args 替换字符串 |
|||
* @return {String} |
|||
*/ |
|||
public static String replace(String str, String regex, String... args) { |
|||
for (String arg : args) { |
|||
str = str.replaceFirst(regex, arg); |
|||
} |
|||
return str; |
|||
} |
|||
|
|||
/** |
|||
* 判断接口返回的 code |
|||
* |
|||
* @param codeValue code 值 |
|||
* @return 是否是 SUCCESS |
|||
*/ |
|||
public static boolean codeIsOk(String codeValue) { |
|||
return StrUtil.isNotEmpty(codeValue) && "SUCCESS".equals(codeValue); |
|||
} |
|||
|
|||
/** |
|||
* <p>公众号支付-预付订单再次签名</p> |
|||
* <p>注意此处签名方式需与统一下单的签名类型一致</p> |
|||
* |
|||
* @param prepayId 预付订单号 |
|||
* @param appId 应用编号 |
|||
* @param partnerKey API Key |
|||
* @param signType 签名方式 |
|||
* @return 再次签名后的 Map |
|||
*/ |
|||
public static Map<String, String> prepayIdCreateSign(String prepayId, String appId, String partnerKey, SignType signType) { |
|||
Map<String, String> packageParams = new HashMap<>(6); |
|||
packageParams.put("appId", appId); |
|||
packageParams.put("timeStamp", String.valueOf(System.currentTimeMillis() / 1000)); |
|||
packageParams.put("nonceStr", String.valueOf(System.currentTimeMillis())); |
|||
packageParams.put("package", "prepay_id=" + prepayId); |
|||
if (signType == null) { |
|||
signType = SignType.MD5; |
|||
} |
|||
packageParams.put("signType", signType.getType()); |
|||
String packageSign = WxPayKit.createSign(packageParams, partnerKey, signType); |
|||
packageParams.put("paySign", packageSign); |
|||
return packageParams; |
|||
} |
|||
|
|||
/** |
|||
* JS 调起支付签名 |
|||
* |
|||
* @param appId 应用编号 |
|||
* @param prepayId 预付订单号 |
|||
* @param keyPath key.pem 证书路径 |
|||
* @return 唤起支付需要的参数 |
|||
* @throws Exception 错误信息 |
|||
*/ |
|||
public static Map<String, String> jsApiCreateSign(String appId, String prepayId, String keyPath) throws Exception { |
|||
String timeStamp = String.valueOf(System.currentTimeMillis() / 1000); |
|||
String nonceStr = String.valueOf(System.currentTimeMillis()); |
|||
String packageStr = "prepay_id=" + prepayId; |
|||
Map<String, String> packageParams = new HashMap<>(6); |
|||
packageParams.put("appId", appId); |
|||
packageParams.put("timeStamp", timeStamp); |
|||
packageParams.put("nonceStr", nonceStr); |
|||
packageParams.put("package", packageStr); |
|||
packageParams.put("signType", SignType.RSA.toString()); |
|||
ArrayList<String> list = new ArrayList<>(); |
|||
list.add(appId); |
|||
list.add(timeStamp); |
|||
list.add(nonceStr); |
|||
list.add(packageStr); |
|||
String packageSign = PayKit.createSign( |
|||
PayKit.buildSignMessage(list), |
|||
keyPath |
|||
); |
|||
packageParams.put("paySign", packageSign); |
|||
return packageParams; |
|||
} |
|||
|
|||
/** |
|||
* <p>APP 支付-预付订单再次签名</p> |
|||
* <p>注意此处签名方式需与统一下单的签名类型一致</p> |
|||
* |
|||
* @param appId 应用编号 |
|||
* @param partnerId 商户号 |
|||
* @param prepayId 预付订单号 |
|||
* @param partnerKey API Key |
|||
* @param signType 签名方式 |
|||
* @return 再次签名后的 Map |
|||
*/ |
|||
public static Map<String, String> appPrepayIdCreateSign(String appId, String partnerId, String prepayId, String partnerKey, SignType signType) { |
|||
Map<String, String> packageParams = new HashMap<>(8); |
|||
packageParams.put("appid", appId); |
|||
packageParams.put("partnerid", partnerId); |
|||
packageParams.put("prepayid", prepayId); |
|||
packageParams.put("package", "Sign=WXPay"); |
|||
packageParams.put("noncestr", String.valueOf(System.currentTimeMillis())); |
|||
packageParams.put("timestamp", String.valueOf(System.currentTimeMillis() / 1000)); |
|||
if (signType == null) { |
|||
signType = SignType.MD5; |
|||
} |
|||
String packageSign = createSign(packageParams, partnerKey, signType); |
|||
packageParams.put("sign", packageSign); |
|||
return packageParams; |
|||
} |
|||
|
|||
/** |
|||
* App 调起支付签名 |
|||
* |
|||
* @param appId 应用编号 |
|||
* @param partnerId 商户编号 |
|||
* @param prepayId 预付订单号 |
|||
* @param keyPath key.pem 证书路径 |
|||
* @return 唤起支付需要的参数 |
|||
* @throws Exception 错误信息 |
|||
*/ |
|||
public static Map<String, String> appCreateSign(String appId, String partnerId, String prepayId, String keyPath) throws Exception { |
|||
String timeStamp = String.valueOf(System.currentTimeMillis() / 1000); |
|||
String nonceStr = String.valueOf(System.currentTimeMillis()); |
|||
Map<String, String> packageParams = new HashMap<>(8); |
|||
packageParams.put("appid", appId); |
|||
packageParams.put("partnerid", partnerId); |
|||
packageParams.put("prepayid", prepayId); |
|||
packageParams.put("package", "Sign=WXPay"); |
|||
packageParams.put("timestamp", timeStamp); |
|||
packageParams.put("noncestr", nonceStr); |
|||
packageParams.put("signType", SignType.RSA.toString()); |
|||
ArrayList<String> list = new ArrayList<>(); |
|||
list.add(appId); |
|||
list.add(timeStamp); |
|||
list.add(nonceStr); |
|||
list.add(prepayId); |
|||
String packageSign = PayKit.createSign( |
|||
PayKit.buildSignMessage(list), |
|||
keyPath |
|||
); |
|||
packageParams.put("sign", packageSign); |
|||
return packageParams; |
|||
} |
|||
|
|||
/** |
|||
* <p>小程序-预付订单再次签名</p> |
|||
* <p>注意此处签名方式需与统一下单的签名类型一致</p> |
|||
* |
|||
* @param appId 应用编号 |
|||
* @param prepayId 预付订单号 |
|||
* @param partnerKey API Key |
|||
* @param signType 签名方式 |
|||
* @return 再次签名后的 Map |
|||
*/ |
|||
public static Map<String, String> miniAppPrepayIdCreateSign(String appId, String prepayId, String partnerKey, SignType signType) { |
|||
Map<String, String> packageParams = new HashMap<>(6); |
|||
packageParams.put("appId", appId); |
|||
packageParams.put("timeStamp", String.valueOf(System.currentTimeMillis() / 1000)); |
|||
packageParams.put("nonceStr", String.valueOf(System.currentTimeMillis())); |
|||
packageParams.put("package", "prepay_id=" + prepayId); |
|||
if (signType == null) { |
|||
signType = SignType.MD5; |
|||
} |
|||
packageParams.put("signType", signType.getType()); |
|||
String packageSign = createSign(packageParams, partnerKey, signType); |
|||
packageParams.put("paySign", packageSign); |
|||
return packageParams; |
|||
} |
|||
|
|||
/** |
|||
* 构建 v3 接口所需的 Authorization |
|||
* |
|||
* @param method {@link RequestMethod} 请求方法 |
|||
* @param urlSuffix 可通过 WxApiType 来获取,URL挂载参数需要自行拼接 |
|||
* @param mchId 商户Id |
|||
* @param serialNo 商户 API 证书序列号 |
|||
* @param keyPath key.pem 证书路径 |
|||
* @param body 接口请求参数 |
|||
* @param nonceStr 随机字符库 |
|||
* @param timestamp 时间戳 |
|||
* @param authType 认证类型 |
|||
* @return {@link String} 返回 v3 所需的 Authorization |
|||
* @throws Exception 异常信息 |
|||
*/ |
|||
public static String buildAuthorization(RequestMethod method, String urlSuffix, String mchId, |
|||
String serialNo, String keyPath, String body, String nonceStr, |
|||
long timestamp, String authType) throws Exception { |
|||
// 构建签名参数
|
|||
String buildSignMessage = PayKit.buildSignMessage(method, urlSuffix, timestamp, nonceStr, body); |
|||
String signature = PayKit.createSign(buildSignMessage, keyPath); |
|||
// 根据平台规则生成请求头 authorization
|
|||
return PayKit.getAuthorization(mchId, serialNo, nonceStr, String.valueOf(timestamp), signature, authType); |
|||
} |
|||
|
|||
/** |
|||
* 构建 v3 接口所需的 Authorization |
|||
* |
|||
* @param method {@link RequestMethod} 请求方法 |
|||
* @param urlSuffix 可通过 WxApiType 来获取,URL挂载参数需要自行拼接 |
|||
* @param mchId 商户Id |
|||
* @param serialNo 商户 API 证书序列号 |
|||
* @param privateKey 商户私钥 |
|||
* @param body 接口请求参数 |
|||
* @param nonceStr 随机字符库 |
|||
* @param timestamp 时间戳 |
|||
* @param authType 认证类型 |
|||
* @return {@link String} 返回 v3 所需的 Authorization |
|||
* @throws Exception 异常信息 |
|||
*/ |
|||
public static String buildAuthorization(RequestMethod method, String urlSuffix, String mchId, |
|||
String serialNo, PrivateKey privateKey, String body, String nonceStr, |
|||
long timestamp, String authType) throws Exception { |
|||
// 构建签名参数
|
|||
String buildSignMessage = PayKit.buildSignMessage(method, urlSuffix, timestamp, nonceStr, body); |
|||
String signature = PayKit.createSign(buildSignMessage, privateKey); |
|||
// 根据平台规则生成请求头 authorization
|
|||
return PayKit.getAuthorization(mchId, serialNo, nonceStr, String.valueOf(timestamp), signature, authType); |
|||
} |
|||
|
|||
/** |
|||
* 构建 v3 接口所需的 Authorization |
|||
* |
|||
* @param method {@link RequestMethod} 请求方法 |
|||
* @param urlSuffix 可通过 WxApiType 来获取,URL挂载参数需要自行拼接 |
|||
* @param mchId 商户Id |
|||
* @param serialNo 商户 API 证书序列号 |
|||
* @param keyPath key.pem 证书路径 |
|||
* @param body 接口请求参数 |
|||
* @return {@link String} 返回 v3 所需的 Authorization |
|||
* @throws Exception 异常信息 |
|||
*/ |
|||
public static String buildAuthorization(RequestMethod method, String urlSuffix, String mchId, |
|||
String serialNo, String keyPath, String body) throws Exception { |
|||
|
|||
long timestamp = System.currentTimeMillis() / 1000; |
|||
String authType = "WECHATPAY2-SHA256-RSA2048"; |
|||
String nonceStr = PayKit.generateStr(); |
|||
|
|||
return buildAuthorization(method, urlSuffix, mchId, serialNo, keyPath, body, nonceStr, timestamp, authType); |
|||
} |
|||
|
|||
/** |
|||
* 构建 v3 接口所需的 Authorization |
|||
* |
|||
* @param method {@link RequestMethod} 请求方法 |
|||
* @param urlSuffix 可通过 WxApiType 来获取,URL挂载参数需要自行拼接 |
|||
* @param mchId 商户Id |
|||
* @param serialNo 商户 API 证书序列号 |
|||
* @param privateKey key.pem 证书路径 |
|||
* @param body 接口请求参数 |
|||
* @return {@link String} 返回 v3 所需的 Authorization |
|||
* @throws Exception 异常信息 |
|||
*/ |
|||
public static String buildAuthorization(RequestMethod method, String urlSuffix, String mchId, |
|||
String serialNo, PrivateKey privateKey, String body) throws Exception { |
|||
|
|||
long timestamp = System.currentTimeMillis() / 1000; |
|||
String authType = "WECHATPAY2-SHA256-RSA2048"; |
|||
String nonceStr = PayKit.generateStr(); |
|||
|
|||
return buildAuthorization(method, urlSuffix, mchId, serialNo, privateKey, body, nonceStr, timestamp, authType); |
|||
} |
|||
|
|||
/** |
|||
* 验证签名 |
|||
* |
|||
* @param map 接口请求返回的 Map |
|||
* @param certPath 平台证书路径 |
|||
* @return 签名结果 |
|||
* @throws Exception 异常信息 |
|||
*/ |
|||
@Deprecated |
|||
public static boolean verifySignature(Map<String, Object> map, String certPath) throws Exception { |
|||
String signature = (String) map.get("signature"); |
|||
String body = (String) map.get("body"); |
|||
String nonceStr = (String) map.get("nonceStr"); |
|||
String timestamp = (String) map.get("timestamp"); |
|||
return verifySignature(signature, body, nonceStr, timestamp, FileUtil.getInputStream(certPath)); |
|||
} |
|||
|
|||
/** |
|||
* 验证签名 |
|||
* |
|||
* @param response 接口请求返回的 {@link IJPayHttpResponse} |
|||
* @param certPath 平台证书路径 |
|||
* @return 签名结果 |
|||
* @throws Exception 异常信息 |
|||
*/ |
|||
public static boolean verifySignature(IJPayHttpResponse response, String certPath) throws Exception { |
|||
String timestamp = response.getHeader("Wechatpay-Timestamp"); |
|||
String nonceStr = response.getHeader("Wechatpay-Nonce"); |
|||
String signature = response.getHeader("Wechatpay-Signature"); |
|||
String body = response.getBody(); |
|||
return verifySignature(signature, body, nonceStr, timestamp, FileUtil.getInputStream(certPath)); |
|||
} |
|||
|
|||
/** |
|||
* 验证签名 |
|||
* |
|||
* @param response 接口请求返回的 {@link IJPayHttpResponse} |
|||
* @param certInputStream 平台证书 |
|||
* @return 签名结果 |
|||
* @throws Exception 异常信息 |
|||
*/ |
|||
public static boolean verifySignature(IJPayHttpResponse response, InputStream certInputStream) throws Exception { |
|||
String timestamp = response.getHeader("Wechatpay-Timestamp"); |
|||
String nonceStr = response.getHeader("Wechatpay-Nonce"); |
|||
String signature = response.getHeader("Wechatpay-Signature"); |
|||
String body = response.getBody(); |
|||
return verifySignature(signature, body, nonceStr, timestamp, certInputStream); |
|||
} |
|||
|
|||
/** |
|||
* 验证签名 |
|||
* |
|||
* @param map 接口请求返回的 Map |
|||
* @param certInputStream 平台证书输入流 |
|||
* @return 签名结果 |
|||
* @throws Exception 异常信息 |
|||
*/ |
|||
@Deprecated |
|||
public static boolean verifySignature(Map<String, Object> map, InputStream certInputStream) throws Exception { |
|||
String signature = (String) map.get("signature"); |
|||
String body = (String) map.get("body"); |
|||
String nonceStr = (String) map.get("nonceStr"); |
|||
String timestamp = (String) map.get("timestamp"); |
|||
return verifySignature(signature, body, nonceStr, timestamp, certInputStream); |
|||
} |
|||
|
|||
/** |
|||
* 验证签名 |
|||
* |
|||
* @param signature 待验证的签名 |
|||
* @param body 应答主体 |
|||
* @param nonce 随机串 |
|||
* @param timestamp 时间戳 |
|||
* @param publicKey 微信支付平台公钥 |
|||
* @return 签名结果 |
|||
* @throws Exception 异常信息 |
|||
*/ |
|||
public static boolean verifySignature(String signature, String body, String nonce, String timestamp, String publicKey) throws Exception { |
|||
String buildSignMessage = PayKit.buildSignMessage(timestamp, nonce, body); |
|||
return RsaKit.checkByPublicKey(buildSignMessage, signature, publicKey); |
|||
} |
|||
|
|||
/** |
|||
* 验证签名 |
|||
* |
|||
* @param signature 待验证的签名 |
|||
* @param body 应答主体 |
|||
* @param nonce 随机串 |
|||
* @param timestamp 时间戳 |
|||
* @param publicKey {@link PublicKey} 微信支付平台公钥 |
|||
* @return 签名结果 |
|||
* @throws Exception 异常信息 |
|||
*/ |
|||
public static boolean verifySignature(String signature, String body, String nonce, String timestamp, PublicKey publicKey) throws Exception { |
|||
String buildSignMessage = PayKit.buildSignMessage(timestamp, nonce, body); |
|||
return RsaKit.checkByPublicKey(buildSignMessage, signature, publicKey); |
|||
} |
|||
|
|||
/** |
|||
* 验证签名 |
|||
* |
|||
* @param signature 待验证的签名 |
|||
* @param body 应答主体 |
|||
* @param nonce 随机串 |
|||
* @param timestamp 时间戳 |
|||
* @param certInputStream 微信支付平台证书输入流 |
|||
* @return 签名结果 |
|||
* @throws Exception 异常信息 |
|||
*/ |
|||
public static boolean verifySignature(String signature, String body, String nonce, String timestamp, InputStream certInputStream) throws Exception { |
|||
String buildSignMessage = PayKit.buildSignMessage(timestamp, nonce, body); |
|||
// 获取证书
|
|||
X509Certificate certificate = PayKit.getCertificate(certInputStream); |
|||
PublicKey publicKey = certificate.getPublicKey(); |
|||
return RsaKit.checkByPublicKey(buildSignMessage, signature, publicKey); |
|||
} |
|||
|
|||
/** |
|||
* v3 支付异步通知验证签名 |
|||
* |
|||
* @param serialNo 证书序列号 |
|||
* @param body 异步通知密文 |
|||
* @param signature 签名 |
|||
* @param nonce 随机字符串 |
|||
* @param timestamp 时间戳 |
|||
* @param key api 密钥 |
|||
* @param certInputStream 平台证书 |
|||
* @return 异步通知明文 |
|||
* @throws Exception 异常信息 |
|||
*/ |
|||
public static String verifyNotify(String serialNo, String body, String signature, String nonce, |
|||
String timestamp, String key, InputStream certInputStream) throws Exception { |
|||
// 获取平台证书序列号
|
|||
X509Certificate certificate = PayKit.getCertificate(certInputStream); |
|||
String serialNumber = certificate.getSerialNumber().toString(16).toUpperCase(); |
|||
System.out.println(serialNumber); |
|||
// 验证证书序列号
|
|||
if (serialNumber.equals(serialNo)) { |
|||
boolean verifySignature = WxPayKit.verifySignature(signature, body, nonce, timestamp, |
|||
certificate.getPublicKey()); |
|||
if (verifySignature) { |
|||
JSONObject resultObject = JSONUtil.parseObj(body); |
|||
JSONObject resource = resultObject.getJSONObject("resource"); |
|||
String cipherText = resource.getStr("ciphertext"); |
|||
String nonceStr = resource.getStr("nonce"); |
|||
String associatedData = resource.getStr("associated_data"); |
|||
|
|||
AesUtil aesUtil = new AesUtil(key.getBytes(StandardCharsets.UTF_8)); |
|||
// 密文解密
|
|||
return aesUtil.decryptToString( |
|||
associatedData.getBytes(StandardCharsets.UTF_8), |
|||
nonceStr.getBytes(StandardCharsets.UTF_8), |
|||
cipherText |
|||
); |
|||
} else { |
|||
throw new Exception("签名错误"); |
|||
} |
|||
} else { |
|||
throw new Exception("证书序列号错误"); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* v3 支付异步通知验证签名 |
|||
* |
|||
* @param serialNo 证书序列号 |
|||
* @param body 异步通知密文 |
|||
* @param signature 签名 |
|||
* @param nonce 随机字符串 |
|||
* @param timestamp 时间戳 |
|||
* @param key api 密钥 |
|||
* @param certPath 平台证书路径 |
|||
* @return 异步通知明文 |
|||
* @throws Exception 异常信息 |
|||
*/ |
|||
public static String verifyNotify(String serialNo, String body, String signature, String nonce, |
|||
String timestamp, String key, String certPath) throws Exception { |
|||
BufferedInputStream inputStream = FileUtil.getInputStream(certPath); |
|||
return verifyNotify(serialNo, body, signature, nonce, timestamp, key, inputStream); |
|||
} |
|||
} |
Loading…
Reference in new issue