新增支付管理器,完成支付通知签名验证,添加默认通知实现类

This commit is contained in:
wanggeng 2021-08-26 16:11:27 +08:00
parent 193a8e2a65
commit 59d22699d5
17 changed files with 432 additions and 70 deletions

View File

@ -44,7 +44,7 @@ public class MiniappPayAppController {
payPlaceOrder.setDescription("测试商品");
payPlaceOrder.setOutTradeNo(atomicOrder);
payPlaceOrder.setTimeExpire(DateTime.now().plusMinutes(1).toString(DateTimeFormat.forPattern(ISystemConstant.DATE_YYYY_MM_DD_T_HH_MM_SS_TIMEZONE)));
payPlaceOrder.setNotifyUrl("https://www.wgink.ink/study/wechat/pay/notice");
payPlaceOrder.setNotifyUrl("https://wg.wgink.ink/study/wechat/pay/notice");
PayPlaceOrder.Amount amount = new PayPlaceOrder.Amount();
amount.setTotal(1);

View File

@ -4,14 +4,17 @@ import ink.wgink.annotation.CheckRequestBodyAnnotation;
import ink.wgink.common.base.DefaultBaseController;
import ink.wgink.interfaces.consts.ISystemConstant;
import ink.wgink.module.wechat.pojo.pay.v3.PayErrorResponse;
import ink.wgink.module.wechat.pojo.pay.v3.PayNotice;
import ink.wgink.module.wechat.service.pay.v3.IPayService;
import io.swagger.annotations.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @ClassName: PayController
@ -36,8 +39,10 @@ public class PayController extends DefaultBaseController {
@PostMapping("notice")
@CheckRequestBodyAnnotation
public void notice(HttpServletRequest request, HttpServletResponse response,
@RequestHeader("Wechatpay-Signature") String wechatpaySignature, @RequestBody PayNotice payNotice) {
payService.notice(request, response, wechatpaySignature, payNotice);
@RequestHeader("Wechatpay-Nonce") String nonce,
@RequestHeader("Wechatpay-Timestamp") String timestamp,
@RequestHeader("Wechatpay-Signature") String wechatpaySignature) throws IOException {
payService.notice(request, response, nonce, timestamp, wechatpaySignature);
}
}

View File

@ -1,12 +1,26 @@
package ink.wgink.module.wechat.manager.pay.v3;
import ink.wgink.exceptions.base.SystemException;
import ink.wgink.module.wechat.enums.PayAuthorizationTypeEnum;
import ink.wgink.module.wechat.service.pay.v3.certificates.ICertificateService;
import ink.wgink.module.wechat.utils.pay.PayCertificateUtil;
import ink.wgink.module.wechat.utils.pay.PaySignAuthorizationUtil;
import ink.wgink.properties.wechat.pay.v3.PayProperties;
import ink.wgink.util.UUIDUtil;
import ink.wgink.util.string.WStringUtil;
import org.springframework.web.bind.annotation.RequestMethod;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.security.cert.X509Certificate;
/**
* 1.系统调用微信接口需要用商家证书认证
* 2.系统接收微信回调参数时需要平台证书平台证书定期更新每6小时需要通过接口下载保存
* 3.商家证书每次读取文件
* 4.平台证书读取缓存
*
* @ClassName: PayMiniappManager
* @Description: 支付管理
* @Author: wanggeng
@ -16,7 +30,9 @@ import java.security.cert.X509Certificate;
public class PayManager {
private static final PayManager PAY_MANAGER = PayMiniappManagerBuilder.payManager;
private X509Certificate x509Certificate;
private ICertificateService certificateService;
private PayProperties payProperties;
private X509Certificate platformCertificate;
private PayManager() {
}
@ -25,12 +41,63 @@ public class PayManager {
return PAY_MANAGER;
}
public X509Certificate getX509Certificate() {
return x509Certificate;
/**
* 获取商户证书
*
* @return
* @throws FileNotFoundException
*/
public X509Certificate getBusinessCertificate() throws FileNotFoundException {
return PayCertificateUtil.getCertificate(new FileInputStream(payProperties.getCertificatePath() + File.separator + payProperties.getCertificateName()));
}
public void setX509Certificate(String certificatePath) throws FileNotFoundException {
this.x509Certificate = PayCertificateUtil.getCertificate(new FileInputStream(certificatePath));
/**
* 获取平台证书
*
* @return
* @throws FileNotFoundException
*/
public X509Certificate getPlatformCertificate() {
// 不存在读取默认证书
if (platformCertificate == null) {
throw new SystemException("平台证书为不存在");
}
return platformCertificate;
}
/**
* 认证头
*
* @param requestMethod
* @param urlSuffix
* @param payProperties
* @param jsonBody
* @param payAuthorizationTypeEnum
* @return
* @throws FileNotFoundException
*/
public String getAuthorization(RequestMethod requestMethod, String urlSuffix, String serialNumber, PayProperties payProperties, String jsonBody, PayAuthorizationTypeEnum payAuthorizationTypeEnum) throws Exception {
String randomSubStr = WStringUtil.randomSubStr(UUIDUtil.get32UUID(), 10).toUpperCase();
return PaySignAuthorizationUtil.buildAuthorization(requestMethod, urlSuffix,
payProperties.getMchid(), serialNumber,
payProperties.getKeyFilePath(), jsonBody, randomSubStr, System.currentTimeMillis() / 1000,
payAuthorizationTypeEnum.getValue());
}
/**
* 刷新证书
*/
public void refreshPlatformCertificate() throws Exception {
String newestCertificate = certificateService.getNewestPlatformCertificate();
this.platformCertificate = PayCertificateUtil.getCertificate(new FileInputStream(newestCertificate));
}
public void setPayProperties(PayProperties payProperties) {
this.payProperties = payProperties;
}
public void setCertificateService(ICertificateService certificateService) {
this.certificateService = certificateService;
}
private static class PayMiniappManagerBuilder {

View File

@ -2,7 +2,6 @@ package ink.wgink.module.wechat.pojo.pay.v3;
import ink.wgink.annotation.CheckBeanAnnotation;
import ink.wgink.annotation.CheckEmptyAnnotation;
import ink.wgink.annotation.CheckNumberAnnotation;
/**
* @ClassName: PayNotice
@ -21,7 +20,6 @@ public class PayNotice {
private String event_type;
@CheckEmptyAnnotation(name = "通知数据类型")
private String resource_type;
@CheckNumberAnnotation(name = "通知数据")
@CheckBeanAnnotation
private Resource resource;
@CheckEmptyAnnotation(name = "回调摘要")

View File

@ -0,0 +1,118 @@
package ink.wgink.module.wechat.pojo.pay.v3.certificates;
/**
* @ClassName: Certificates
* @Description: 平台证书
* @Author: wanggeng
* @Date: 2021/8/26 2:19 下午
* @Version: 1.0
*/
public class Certificate {
private String serialNo;
private String effectiveTime;
private String expireTime;
private EncryptCertificate encryptCertificate;
public String getSerialNo() {
return serialNo == null ? "" : serialNo.trim();
}
public void setSerialNo(String serialNo) {
this.serialNo = serialNo;
}
public String getEffectiveTime() {
return effectiveTime == null ? "" : effectiveTime.trim();
}
public void setEffectiveTime(String effectiveTime) {
this.effectiveTime = effectiveTime;
}
public String getExpireTime() {
return expireTime == null ? "" : expireTime.trim();
}
public void setExpireTime(String expireTime) {
this.expireTime = expireTime;
}
public EncryptCertificate getEncryptCertificate() {
return encryptCertificate;
}
public void setEncryptCertificate(EncryptCertificate encryptCertificate) {
this.encryptCertificate = encryptCertificate;
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder("{");
sb.append("\"serialNo\":\"")
.append(serialNo).append('\"');
sb.append(",\"effectiveTime\":\"")
.append(effectiveTime).append('\"');
sb.append(",\"expireTime\":\"")
.append(expireTime).append('\"');
sb.append(",\"encryptCertificate\":")
.append(encryptCertificate);
sb.append('}');
return sb.toString();
}
public static class EncryptCertificate {
private String algorithm;
private String nonce;
private String associatedData;
private String ciphertext;
public String getAlgorithm() {
return algorithm == null ? "" : algorithm.trim();
}
public void setAlgorithm(String algorithm) {
this.algorithm = algorithm;
}
public String getNonce() {
return nonce == null ? "" : nonce.trim();
}
public void setNonce(String nonce) {
this.nonce = nonce;
}
public String getAssociatedData() {
return associatedData == null ? "" : associatedData.trim();
}
public void setAssociatedData(String associatedData) {
this.associatedData = associatedData;
}
public String getCiphertext() {
return ciphertext == null ? "" : ciphertext.trim();
}
public void setCiphertext(String ciphertext) {
this.ciphertext = ciphertext;
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder("{");
sb.append("\"algorithm\":\"")
.append(algorithm).append('\"');
sb.append(",\"nonce\":\"")
.append(nonce).append('\"');
sb.append(",\"associatedData\":\"")
.append(associatedData).append('\"');
sb.append(",\"ciphertext\":\"")
.append(ciphertext).append('\"');
sb.append('}');
return sb.toString();
}
}
}

View File

@ -4,11 +4,6 @@ import com.alibaba.fastjson.serializer.SerializerFeature;
import com.alibaba.fastjson.support.config.FastJsonConfig;
import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter;
import ink.wgink.exceptions.base.SystemException;
import ink.wgink.module.wechat.enums.PayAuthorizationTypeEnum;
import ink.wgink.module.wechat.utils.pay.PaySignAuthorizationUtil;
import ink.wgink.properties.wechat.pay.v3.PayProperties;
import ink.wgink.util.UUIDUtil;
import ink.wgink.util.string.WStringUtil;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -19,7 +14,6 @@ import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.RestTemplate;
@ -28,7 +22,6 @@ import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.charset.Charset;
import java.security.GeneralSecurityException;
@ -190,25 +183,6 @@ public abstract class AbstractPayRequest<RequestBean, BodyType, ResponseType> {
}
}
/**
* 认证头
*
* @param requestMethod
* @param urlSuffix
* @param payProperties
* @param jsonBody
* @param payAuthorizationTypeEnum
* @return
* @throws FileNotFoundException
*/
public String getAuthorization(RequestMethod requestMethod, String urlSuffix, String serialNumber, PayProperties payProperties, String jsonBody, PayAuthorizationTypeEnum payAuthorizationTypeEnum) throws Exception {
String randomSubStr = WStringUtil.randomSubStr(UUIDUtil.get32UUID(), 10).toUpperCase();
return PaySignAuthorizationUtil.buildAuthorization(requestMethod, urlSuffix,
payProperties.getMchid(), serialNumber,
payProperties.getKeyPath(), jsonBody, randomSubStr, System.currentTimeMillis() / 1000,
payAuthorizationTypeEnum.getValue());
}
/**
* 请求头
*

View File

@ -0,0 +1,61 @@
package ink.wgink.module.wechat.request.pay.v3.certificates;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import ink.wgink.exceptions.base.SystemException;
import ink.wgink.module.wechat.pojo.pay.v3.PayErrorResponse;
import ink.wgink.module.wechat.pojo.pay.v3.certificates.Certificate;
import ink.wgink.module.wechat.request.pay.v3.AbstractPayRequest;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.HttpClientErrorException;
import java.util.ArrayList;
import java.util.List;
/**
* @ClassName: CertificatesPayRequest
* @Description: 获取平台证书
* @Author: wanggeng
* @Date: 2021/8/26 2:13 下午
* @Version: 1.0
*/
public class CertificatePayRequestImpl extends AbstractPayRequest<JSONObject, JSONObject, List<Certificate>> {
@Override
public JSONObject bodyJson(JSONObject jsonObject) {
return null;
}
@Override
public List<Certificate> response(ResponseEntity<String> responseEntity) {
JSONObject resultObject = JSONObject.parseObject(responseEntity.getBody());
List<Certificate> certificateList = new ArrayList<>();
JSONArray certificatesData = resultObject.getJSONArray("data");
for (int i = 0; i < certificatesData.size(); i++) {
JSONObject certificateJsonObject = certificatesData.getJSONObject(i);
Certificate certificate = new Certificate();
certificate.setSerialNo(certificateJsonObject.getString("serial_no"));
certificate.setEffectiveTime(certificateJsonObject.getString("effective_time"));
certificate.setExpireTime(certificateJsonObject.getString("expire_time"));
JSONObject encryptCertificateJsonObject = certificateJsonObject.getJSONObject("encrypt_certificate");
Certificate.EncryptCertificate encryptCertificate = new Certificate.EncryptCertificate();
encryptCertificate.setAlgorithm(encryptCertificateJsonObject.getString("algorithm"));
encryptCertificate.setNonce(encryptCertificateJsonObject.getString("nonce"));
encryptCertificate.setAssociatedData(encryptCertificateJsonObject.getString("associated_data"));
encryptCertificate.setCiphertext(encryptCertificateJsonObject.getString("ciphertext"));
certificate.setEncryptCertificate(encryptCertificate);
certificateList.add(certificate);
}
return certificateList;
}
@Override
public void error(HttpClientErrorException e) {
LOG.debug("Response error status code{}", e.getStatusCode());
PayErrorResponse payErrorResponse = JSONObject.parseObject(e.getResponseBodyAsString(), PayErrorResponse.class);
LOG.debug("Response error detail: {}", payErrorResponse.getDetail());
throw new SystemException(payErrorResponse.getMessage());
}
}

View File

@ -7,6 +7,7 @@ import ink.wgink.module.wechat.pojo.pay.v3.PayErrorResponse;
import ink.wgink.module.wechat.pojo.pay.v3.jsapi.pojo.PayPlaceOrder;
import ink.wgink.module.wechat.pojo.pay.v3.jsapi.pojo.PayPrepay;
import ink.wgink.module.wechat.request.pay.v3.AbstractPayRequest;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.HttpClientErrorException;
@ -48,7 +49,10 @@ public class PlaceOrderPayRequestImpl extends AbstractPayRequest<PayPlaceOrder,
@Override
public PayPrepay response(ResponseEntity<String> responseEntity) {
LOG.debug("Response success status code: {}", responseEntity.getStatusCode());
if (responseEntity.getStatusCode() == HttpStatus.OK) {
return JSONObject.parseObject(responseEntity.getBody(), PayPrepay.class);
}
return null;
}

View File

@ -2,8 +2,14 @@ package ink.wgink.module.wechat.service.pay;
import ink.wgink.common.base.DefaultBaseService;
import ink.wgink.exceptions.PropertiesException;
import ink.wgink.module.wechat.enums.PayAuthorizationTypeEnum;
import ink.wgink.module.wechat.manager.pay.v3.PayManager;
import ink.wgink.properties.wechat.pay.v3.PayProperties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMethod;
import java.io.FileNotFoundException;
import java.security.cert.X509Certificate;
/**
* @ClassName: BasePayService
@ -20,6 +26,32 @@ public class BasePayService extends DefaultBaseService {
@Autowired
protected PayProperties payProperties;
/**
* 获得证书序列号
*
* @return
*/
protected String getSerialNumber() throws FileNotFoundException {
X509Certificate x509Certificate = PayManager.getInstance().getBusinessCertificate();
String serialNumber = x509Certificate.getSerialNumber().toString(16);
return serialNumber;
}
/**
* 认证头
*
* @param requestMethod
* @param urlSuffix
* @param payProperties
* @param jsonBody
* @param payAuthorizationTypeEnum
* @return
* @throws FileNotFoundException
*/
public String getAuthorization(RequestMethod requestMethod, String urlSuffix, String serialNumber, PayProperties payProperties, String jsonBody, PayAuthorizationTypeEnum payAuthorizationTypeEnum) throws Exception {
return PayManager.getInstance().getAuthorization(requestMethod, urlSuffix, serialNumber, payProperties, jsonBody, payAuthorizationTypeEnum);
}
protected String getPayBaseUrl() {
if (!payProperties.getActive()) {
throw new PropertiesException("微信支付未激活");

View File

@ -1,9 +1,8 @@
package ink.wgink.module.wechat.service.pay.v3;
import ink.wgink.module.wechat.pojo.pay.v3.PayNotice;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @ClassName: IPayService
@ -19,8 +18,9 @@ public interface IPayService {
*
* @param request
* @param response
* @param nonce
* @param timestamp
* @param wechatpaySignature
* @param payNotice
*/
void notice(HttpServletRequest request, HttpServletResponse response, String wechatpaySignature, PayNotice payNotice);
void notice(HttpServletRequest request, HttpServletResponse response, String nonce, String timestamp, String wechatpaySignature) throws IOException;
}

View File

@ -0,0 +1,19 @@
package ink.wgink.module.wechat.service.pay.v3.certificates;
/**
* @ClassName: ICertificatesService
* @Description: 平台证书业务
* @Author: wanggeng
* @Date: 2021/8/26 2:40 下午
* @Version: 1.0
*/
public interface ICertificateService {
/**
* 最新的平台证书
*
* @return
*/
String getNewestPlatformCertificate() throws Exception;
}

View File

@ -0,0 +1,58 @@
package ink.wgink.module.wechat.service.pay.v3.certificates.impl;
import ink.wgink.exceptions.base.SystemException;
import ink.wgink.module.wechat.enums.PayAuthorizationTypeEnum;
import ink.wgink.module.wechat.pojo.pay.v3.certificates.Certificate;
import ink.wgink.module.wechat.request.pay.v3.AbstractPayRequest;
import ink.wgink.module.wechat.request.pay.v3.certificates.CertificatePayRequestImpl;
import ink.wgink.module.wechat.service.pay.BasePayService;
import ink.wgink.module.wechat.service.pay.v3.certificates.ICertificateService;
import ink.wgink.util.date.DateUtil;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.RequestMethod;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.List;
/**
* @ClassName: ICertificatesService
* @Description: 平台证书业务
* @Author: wanggeng
* @Date: 2021/8/26 2:40 下午
* @Version: 1.0
*/
@Service
public class CertificateServiceImpl extends BasePayService implements ICertificateService {
@Override
public String getNewestPlatformCertificate() throws Exception {
String urlSuffix = "/v3/certificates";
String url = getPayBaseUrl() + urlSuffix;
LOG.debug("获得证书序列号");
String serialNumber = getSerialNumber();
CertificatePayRequestImpl certificatesPayRequest = new CertificatePayRequestImpl();
String authorization = getAuthorization(RequestMethod.GET, urlSuffix, serialNumber, payProperties, "", PayAuthorizationTypeEnum.WECHATPAY2_SHA256_RSA2048);
List<Certificate> certificates = certificatesPayRequest.get(url, authorization, serialNumber);
LOG.debug("获取最新的平台证书");
Certificate newestCertificate = certificates.get(certificates.size() - 1);
LOG.debug("解析密文");
AbstractPayRequest.AesUtil aesUtil = new AbstractPayRequest.AesUtil(payProperties.getApiV3Secretkey().getBytes(StandardCharsets.UTF_8));
Certificate.EncryptCertificate encryptCertificate = newestCertificate.getEncryptCertificate();
String ciphertext = aesUtil.decryptToString(encryptCertificate.getAssociatedData().getBytes(StandardCharsets.UTF_8), encryptCertificate.getNonce().getBytes(StandardCharsets.UTF_8), encryptCertificate.getCiphertext());
String certificateFilePath = payProperties.getCertificatePath() + File.separator + DateUtil.getDays()+ "_wxp_pub.pem";
LOG.debug("新证书文件路径:{}", certificateFilePath);
try (FileWriter fileWriter = new FileWriter(certificateFilePath)) {
fileWriter.write(ciphertext);
fileWriter.flush();
} catch (IOException e) {
LOG.error(e.getMessage(), e);
throw new SystemException("证书生成异常");
}
LOG.debug("保存新平台证书成功");
return certificateFilePath;
}
}

View File

@ -17,7 +17,7 @@ public class DefaultPayNoticeService extends DefaultBaseService implements IPayN
@Override
public void handle(PayNoticeCiphertext payNoticeCiphertext) throws Exception {
LOG.debug(payNoticeCiphertext.toString());
LOG.debug("Pay notice: {}", payNoticeCiphertext.toString());
}
}

View File

@ -2,7 +2,9 @@ package ink.wgink.module.wechat.service.pay.v3.impl;
import com.alibaba.fastjson.JSONObject;
import ink.wgink.common.base.DefaultBaseService;
import ink.wgink.exceptions.ParamsException;
import ink.wgink.module.wechat.enums.PayErrorMsgEnum;
import ink.wgink.module.wechat.manager.pay.v3.PayManager;
import ink.wgink.module.wechat.pojo.pay.v3.PayErrorResponse;
import ink.wgink.module.wechat.pojo.pay.v3.PayNotice;
import ink.wgink.module.wechat.pojo.pay.v3.PayNoticeCiphertext;
@ -11,14 +13,14 @@ import ink.wgink.module.wechat.service.pay.v3.IPayNoticeService;
import ink.wgink.module.wechat.service.pay.v3.IPayService;
import ink.wgink.module.wechat.utils.pay.PayVerifyUtil;
import ink.wgink.properties.wechat.pay.v3.PayProperties;
import org.joda.time.DateTime;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.FileInputStream;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
@ -39,15 +41,17 @@ public class IPayServiceImpl extends DefaultBaseService implements IPayService {
private IPayNoticeService payNoticeService;
@Override
public void notice(HttpServletRequest request, HttpServletResponse response, String wechatpaySignature, PayNotice payNotice) {
String nonce = payNotice.getResource().getNonce();
DateTime createTimeDateTime = DateTime.parse(payNotice.getCreate_time());
long millis = createTimeDateTime.getMillis();
String payNoticeJsonString = JSONObject.toJSONString(payNotice);
public void notice(HttpServletRequest request, HttpServletResponse response, String nonce, String timestamp, String wechatpaySignature) throws IOException {
String payNoticeJsonString = getBodyString(request);
if (StringUtils.isBlank(payNoticeJsonString)) {
throw new ParamsException("请求body为空");
}
LOG.debug("payNoticeJsonString: {}", payNoticeJsonString);
PayNotice payNotice = JSONObject.parseObject(payNoticeJsonString, PayNotice.class);
try {
LOG.debug("wechatpaySignature: {}", wechatpaySignature);
LOG.debug("验证签名");
boolean checkVerify = PayVerifyUtil.verifySignature(wechatpaySignature, payNoticeJsonString, nonce, String.valueOf(millis), new FileInputStream(payProperties.getCertificatePath()));
boolean checkVerify = PayVerifyUtil.verifySignature(wechatpaySignature, payNoticeJsonString, nonce, timestamp, PayManager.getInstance().getPlatformCertificate());
if (!checkVerify) {
errorResult(response, PayErrorMsgEnum.SIGN_ERROR_401);
return;
@ -104,4 +108,13 @@ public class IPayServiceImpl extends DefaultBaseService implements IPayService {
}
}
private String getBodyString(HttpServletRequest request) throws IOException {
BufferedReader reader = request.getReader();
StringBuilder bodyString = new StringBuilder();
for (String line; (line = reader.readLine()) != null; ) {
bodyString.append(line);
}
return bodyString.toString();
}
}

View File

@ -8,7 +8,6 @@ import ink.wgink.module.wechat.pojo.pay.v3.jsapi.pojo.PayPrepay;
import ink.wgink.module.wechat.request.pay.v3.jsapi.PlaceOrderPayRequestImpl;
import ink.wgink.module.wechat.service.pay.BasePayService;
import ink.wgink.module.wechat.service.pay.v3.jsapi.IJsapiService;
import ink.wgink.module.wechat.utils.pay.PayCertificateUtil;
import ink.wgink.module.wechat.utils.pay.PayPrivateKeyUtil;
import ink.wgink.properties.wechat.miniapp.MiniappProperties;
import ink.wgink.util.BeanPropertyCheckUtil;
@ -18,9 +17,6 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.RequestMethod;
import java.io.FileInputStream;
import java.security.cert.X509Certificate;
/**
* @ClassName: JsapiServiceImpl
* @Description: Jsapi业务
@ -43,19 +39,19 @@ public class JsapiServiceImpl extends BasePayService implements IJsapiService {
String urlSuffix = "/v3/pay/transactions/jsapi";
String url = getPayBaseUrl() + urlSuffix;
LOG.debug("获得证书");
X509Certificate certificate = PayCertificateUtil.getCertificate(new FileInputStream(payProperties.getCertificatePath()));
String serialNumber = certificate.getSerialNumber().toString(16);
LOG.debug("获得证书序列号");
String serialNumber = getSerialNumber();
LOG.debug("调用微信支付,发起预支付");
PlaceOrderPayRequestImpl placeOrderPayRequest = new PlaceOrderPayRequestImpl();
JSONObject body = placeOrderPayRequest.bodyJson(payPlaceOrder);
String authorization = placeOrderPayRequest.getAuthorization(RequestMethod.POST, urlSuffix, serialNumber, payProperties, body.toString(), PayAuthorizationTypeEnum.WECHATPAY2_SHA256_RSA2048);
String authorization = getAuthorization(RequestMethod.POST, urlSuffix, serialNumber, payProperties, body.toString(), PayAuthorizationTypeEnum.WECHATPAY2_SHA256_RSA2048);
LOG.debug("请求获得结果");
PayPrepay payPrepay = placeOrderPayRequest.post(url, authorization, serialNumber, body);
LOG.debug("生成返回结果");
long timestamp = System.currentTimeMillis();
long timestamp = System.currentTimeMillis() / 1000;
String nonceStr = WStringUtil.randomSubStr(UUIDUtil.get32UUID(), 10).toUpperCase();
PaySign paySign = new PaySign();
@ -82,7 +78,7 @@ public class JsapiServiceImpl extends BasePayService implements IJsapiService {
.append(timestamp).append("\n")
.append(nonceStr).append("\n")
.append("prepay_id=").append(prepayId).append("\n");
String key = PayPrivateKeyUtil.getPrivateKey(payProperties.getKeyPath());
String key = PayPrivateKeyUtil.getPrivateKey(payProperties.getKeyFilePath());
// 生成签名
return PayPrivateKeyUtil.encryptByPrivateKey(signBS.toString(), key);
}

View File

@ -2,6 +2,8 @@ package ink.wgink.module.wechat.startup;
import ink.wgink.module.wechat.manager.MiniappManager;
import ink.wgink.module.wechat.manager.OfficialAccountAccessTokenManager;
import ink.wgink.module.wechat.manager.pay.v3.PayManager;
import ink.wgink.module.wechat.service.pay.v3.certificates.ICertificateService;
import ink.wgink.properties.wechat.miniapp.MiniappProperties;
import ink.wgink.properties.wechat.official.account.OfficialAccountProperties;
import ink.wgink.properties.wechat.pay.v3.PayProperties;
@ -32,12 +34,18 @@ public class WechatStartUp implements ApplicationRunner {
private OfficialAccountProperties officialAccountProperties;
@Autowired
private PayProperties payProperties;
@Autowired
private ICertificateService certificateService;
@Override
public void run(ApplicationArguments args) throws Exception {
new Thread(() -> {
refreshOfficialAccountAccessToken();
}).start();
refreshOfficialAccountAccessToken();
removeTimeoutMiniAppUser();
// 支付
PayManager payManager = PayManager.getInstance();
payManager.setCertificateService(certificateService);
payManager.setPayProperties(payProperties);
refreshCertificate();
}
@Scheduled(cron = "0 0/5 * * * ?")
@ -59,4 +67,15 @@ public class WechatStartUp implements ApplicationRunner {
MiniappManager.getInstance().removeTimeout();
}
/**
* 刷新证书0小时开始每6小时执行一次
*/
@Scheduled(cron = "0 0 0/6 * * ?")
public void refreshCertificate() throws Exception {
if (!payProperties.getActive()) {
return;
}
PayManager.getInstance().refreshPlatformCertificate();
}
}

View File

@ -2,7 +2,6 @@ package ink.wgink.module.wechat.utils.pay;
import org.bouncycastle.util.encoders.Base64;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.security.PublicKey;
import java.security.cert.X509Certificate;
@ -15,7 +14,6 @@ import java.security.cert.X509Certificate;
* @Version: 1.0
*/
public class PayVerifyUtil {
/**
* 验证签名
*
@ -27,10 +25,10 @@ public class PayVerifyUtil {
* @return
* @throws Exception
*/
public static boolean verifySignature(String signature, String body, String nonce, String timestamp, InputStream certInputStream) throws Exception {
public static boolean verifySignature(String signature, String body, String nonce, String timestamp, X509Certificate certificate) throws Exception {
String buildSignMessage = buildSignMessage(timestamp, nonce, body);
System.out.println("buildSignMessage: " + buildSignMessage);
// 获取证书
X509Certificate certificate = PayCertificateUtil.getCertificate(certInputStream);
PublicKey publicKey = certificate.getPublicKey();
return checkByPublicKey(buildSignMessage, signature, publicKey);
}