支付相关
This commit is contained in:
@@ -6,14 +6,13 @@ import com.yxt.pay.api.order.OrderQuery;
|
|||||||
import com.yxt.pay.api.order.PayOrderVo;
|
import com.yxt.pay.api.order.PayOrderVo;
|
||||||
import com.yxt.pay.utils.ApiBaseAction;
|
import com.yxt.pay.utils.ApiBaseAction;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.*;
|
||||||
import org.springframework.web.bind.annotation.RequestBody;
|
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description:
|
* @description:
|
||||||
@@ -42,9 +41,14 @@ public class OrderRest extends ApiBaseAction {
|
|||||||
return orderService.pay(query, getClientIp());
|
return orderService.pay(query, getClientIp());
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/wxPayNotify")
|
/* @PostMapping("/wxPayNotify")
|
||||||
ResultBean wxPayNotify(HttpServletRequest request, HttpServletResponse response) throws IOException {
|
ResultBean wxPayNotify(HttpServletRequest request, HttpServletResponse response) throws IOException {
|
||||||
return orderService.wxPayNotify(request, response);
|
return orderService.wxPayNotify(request, response);
|
||||||
|
}*/
|
||||||
|
|
||||||
|
@PostMapping( "/wxPayNotify")
|
||||||
|
public String payNotify(HttpServletRequest request) {
|
||||||
|
return orderService.payNotify(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("selectOrder")
|
@PostMapping("selectOrder")
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.yxt.pay.biz.order;
|
package com.yxt.pay.biz.order;
|
||||||
|
|
||||||
|
import com.sun.org.apache.bcel.internal.generic.RETURN;
|
||||||
import com.yxt.common.base.service.MybatisBaseService;
|
import com.yxt.common.base.service.MybatisBaseService;
|
||||||
import com.yxt.common.core.result.ResultBean;
|
import com.yxt.common.core.result.ResultBean;
|
||||||
import com.yxt.pay.api.order.*;
|
import com.yxt.pay.api.order.*;
|
||||||
@@ -7,10 +8,14 @@ import com.yxt.pay.api.wxpay.WxPayVo;
|
|||||||
import com.yxt.pay.utils.*;
|
import com.yxt.pay.utils.*;
|
||||||
import com.yxt.pay.utils.applet.WechatRefundApiResult;
|
import com.yxt.pay.utils.applet.WechatRefundApiResult;
|
||||||
import com.yxt.pay.utils.applet.WechatUtil;
|
import com.yxt.pay.utils.applet.WechatUtil;
|
||||||
|
import com.yxt.pay.utils.wx.HttpKit;
|
||||||
|
import com.yxt.pay.utils.wx.SignType;
|
||||||
|
import com.yxt.pay.utils.wx.WxPayKit;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
import org.springframework.web.client.RestTemplate;
|
import org.springframework.web.client.RestTemplate;
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
@@ -103,7 +108,7 @@ public class OrderService extends MybatisBaseService<OrderMapper, PayOrder> {
|
|||||||
//支付金额
|
//支付金额
|
||||||
parame.put("total_fee", new BigDecimal(payOrder.getTotalTee()).multiply(new BigDecimal(100)).intValue());
|
parame.put("total_fee", new BigDecimal(payOrder.getTotalTee()).multiply(new BigDecimal(100)).intValue());
|
||||||
// 回调地址
|
// 回调地址
|
||||||
parame.put("notify_url", urlComponent.getDoMainUrl() + "/wxPay/payNotify");
|
parame.put("notify_url", urlComponent.getDoMainUrl() + "order/wxPayNotify");
|
||||||
// 交易类型APP
|
// 交易类型APP
|
||||||
parame.put("trade_type", tradeType);
|
parame.put("trade_type", tradeType);
|
||||||
parame.put("spbill_create_ip", ip);
|
parame.put("spbill_create_ip", ip);
|
||||||
@@ -155,7 +160,7 @@ public class OrderService extends MybatisBaseService<OrderMapper, PayOrder> {
|
|||||||
return rb;
|
return rb;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ResultBean wxPayNotify(HttpServletRequest request, HttpServletResponse response) {
|
/* public ResultBean wxPayNotify(HttpServletRequest request, HttpServletResponse response) {
|
||||||
ResultBean rb = ResultBean.fireFail();
|
ResultBean rb = ResultBean.fireFail();
|
||||||
try {
|
try {
|
||||||
request.setCharacterEncoding("UTF-8");
|
request.setCharacterEncoding("UTF-8");
|
||||||
@@ -201,7 +206,7 @@ public class OrderService extends MybatisBaseService<OrderMapper, PayOrder> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return rb;
|
return rb;
|
||||||
}
|
}*/
|
||||||
|
|
||||||
public ResultBean selectOrder(OrderQuery query) {
|
public ResultBean selectOrder(OrderQuery query) {
|
||||||
ResultBean rb = ResultBean.fireFail();
|
ResultBean rb = ResultBean.fireFail();
|
||||||
@@ -290,4 +295,40 @@ public class OrderService extends MybatisBaseService<OrderMapper, PayOrder> {
|
|||||||
public List<PayOrder> selectOrderList() {
|
public List<PayOrder> selectOrderList() {
|
||||||
return baseMapper.selectOrderList();
|
return baseMapper.selectOrderList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String payNotify(HttpServletRequest request) {
|
||||||
|
String xmlMsg = HttpKit.readData(request);
|
||||||
|
Map<String, String> params = WxPayKit.xmlToMap(xmlMsg);
|
||||||
|
log.info("微信支付通知=" + params);
|
||||||
|
String returnCode = params.get("return_code");
|
||||||
|
//订单编号
|
||||||
|
String out_trade_no = params.get("out_trade_no");
|
||||||
|
//根据订单编号查询
|
||||||
|
PayOrder payOrder = baseMapper.selectBySn(out_trade_no);
|
||||||
|
if (payOrder == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
payOrder.setState(2);//已支付.state:1待支付,2已支付,3已过期
|
||||||
|
payOrder.setModifyTime(new Date());
|
||||||
|
payOrder.setPayTypeValue("微信");
|
||||||
|
WxPayVo wxPayVo = new WxPayVo(payOrder.getSource());
|
||||||
|
// 注意重复通知的情况,同一订单号可能收到多次通知,请注意一定先判断订单状态
|
||||||
|
// 注意此处签名方式需与统一下单的签名类型一致
|
||||||
|
if (WxPayKit.verifyNotify(params, wxPayVo.getSecret(), SignType.MD5)) {
|
||||||
|
if (WxPayKit.codeIsOk(returnCode)) {
|
||||||
|
baseMapper.updateById(payOrder);
|
||||||
|
//调业务的回调地址接口。mainSid传过去。
|
||||||
|
ResponseEntity<ResultBean> postForEntity = new RestTemplate().getForEntity(payOrder.getReturnUrl() + payOrder.getSid(), ResultBean.class);
|
||||||
|
// 发送通知等
|
||||||
|
Map<String, String> xml = new HashMap<String, String>(2);
|
||||||
|
xml.put("return_code", "SUCCESS");
|
||||||
|
xml.put("return_msg", "OK");
|
||||||
|
return WxPayKit.toXml(xml);
|
||||||
|
} else {
|
||||||
|
log.error("订单" + out_trade_no + "支付失败");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
504
src/main/java/com/yxt/pay/utils/wx/AbstractHttpDelegate.java
Normal file
504
src/main/java/com/yxt/pay/utils/wx/AbstractHttpDelegate.java
Normal file
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
70
src/main/java/com/yxt/pay/utils/wx/AesUtil.java
Normal file
70
src/main/java/com/yxt/pay/utils/wx/AesUtil.java
Normal file
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
87
src/main/java/com/yxt/pay/utils/wx/HttpKit.java
Normal file
87
src/main/java/com/yxt/pay/utils/wx/HttpKit.java
Normal file
@@ -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 {
|
||||||
|
}
|
||||||
77
src/main/java/com/yxt/pay/utils/wx/IJPayHttpResponse.java
Normal file
77
src/main/java/com/yxt/pay/utils/wx/IJPayHttpResponse.java
Normal file
@@ -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 +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
||||||
521
src/main/java/com/yxt/pay/utils/wx/PayKit.java
Normal file
521
src/main/java/com/yxt/pay/utils/wx/PayKit.java
Normal file
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
64
src/main/java/com/yxt/pay/utils/wx/RequestMethod.java
Normal file
64
src/main/java/com/yxt/pay/utils/wx/RequestMethod.java
Normal file
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
387
src/main/java/com/yxt/pay/utils/wx/RsaKit.java
Normal file
387
src/main/java/com/yxt/pay/utils/wx/RsaKit.java
Normal file
@@ -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));
|
||||||
|
}
|
||||||
|
}
|
||||||
44
src/main/java/com/yxt/pay/utils/wx/SignType.java
Normal file
44
src/main/java/com/yxt/pay/utils/wx/SignType.java
Normal file
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
695
src/main/java/com/yxt/pay/utils/wx/WxPayKit.java
Normal file
695
src/main/java/com/yxt/pay/utils/wx/WxPayKit.java
Normal file
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user