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

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.setDescription("测试商品");
payPlaceOrder.setOutTradeNo(atomicOrder); payPlaceOrder.setOutTradeNo(atomicOrder);
payPlaceOrder.setTimeExpire(DateTime.now().plusMinutes(1).toString(DateTimeFormat.forPattern(ISystemConstant.DATE_YYYY_MM_DD_T_HH_MM_SS_TIMEZONE))); 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(); PayPlaceOrder.Amount amount = new PayPlaceOrder.Amount();
amount.setTotal(1); amount.setTotal(1);

View File

@ -4,14 +4,17 @@ import ink.wgink.annotation.CheckRequestBodyAnnotation;
import ink.wgink.common.base.DefaultBaseController; import ink.wgink.common.base.DefaultBaseController;
import ink.wgink.interfaces.consts.ISystemConstant; import ink.wgink.interfaces.consts.ISystemConstant;
import ink.wgink.module.wechat.pojo.pay.v3.PayErrorResponse; 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 ink.wgink.module.wechat.service.pay.v3.IPayService;
import io.swagger.annotations.*; import io.swagger.annotations.*;
import org.springframework.beans.factory.annotation.Autowired; 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.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/** /**
* @ClassName: PayController * @ClassName: PayController
@ -36,8 +39,10 @@ public class PayController extends DefaultBaseController {
@PostMapping("notice") @PostMapping("notice")
@CheckRequestBodyAnnotation @CheckRequestBodyAnnotation
public void notice(HttpServletRequest request, HttpServletResponse response, public void notice(HttpServletRequest request, HttpServletResponse response,
@RequestHeader("Wechatpay-Signature") String wechatpaySignature, @RequestBody PayNotice payNotice) { @RequestHeader("Wechatpay-Nonce") String nonce,
payService.notice(request, response, wechatpaySignature, payNotice); @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; 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.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.FileInputStream;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.security.cert.X509Certificate; import java.security.cert.X509Certificate;
/** /**
* 1.系统调用微信接口需要用商家证书认证
* 2.系统接收微信回调参数时需要平台证书平台证书定期更新每6小时需要通过接口下载保存
* 3.商家证书每次读取文件
* 4.平台证书读取缓存
*
* @ClassName: PayMiniappManager * @ClassName: PayMiniappManager
* @Description: 支付管理 * @Description: 支付管理
* @Author: wanggeng * @Author: wanggeng
@ -16,7 +30,9 @@ import java.security.cert.X509Certificate;
public class PayManager { public class PayManager {
private static final PayManager PAY_MANAGER = PayMiniappManagerBuilder.payManager; private static final PayManager PAY_MANAGER = PayMiniappManagerBuilder.payManager;
private X509Certificate x509Certificate; private ICertificateService certificateService;
private PayProperties payProperties;
private X509Certificate platformCertificate;
private PayManager() { private PayManager() {
} }
@ -25,12 +41,63 @@ public class PayManager {
return PAY_MANAGER; 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 { 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.CheckBeanAnnotation;
import ink.wgink.annotation.CheckEmptyAnnotation; import ink.wgink.annotation.CheckEmptyAnnotation;
import ink.wgink.annotation.CheckNumberAnnotation;
/** /**
* @ClassName: PayNotice * @ClassName: PayNotice
@ -21,7 +20,6 @@ public class PayNotice {
private String event_type; private String event_type;
@CheckEmptyAnnotation(name = "通知数据类型") @CheckEmptyAnnotation(name = "通知数据类型")
private String resource_type; private String resource_type;
@CheckNumberAnnotation(name = "通知数据")
@CheckBeanAnnotation @CheckBeanAnnotation
private Resource resource; private Resource resource;
@CheckEmptyAnnotation(name = "回调摘要") @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.config.FastJsonConfig;
import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter; import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter;
import ink.wgink.exceptions.base.SystemException; 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.apache.commons.lang3.StringUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -19,7 +14,6 @@ import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter; import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap; import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.client.HttpClientErrorException; import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.RestTemplate; import org.springframework.web.client.RestTemplate;
@ -28,7 +22,6 @@ import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.GCMParameterSpec; import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec; import javax.crypto.spec.SecretKeySpec;
import java.io.File; import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.security.GeneralSecurityException; 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.PayPlaceOrder;
import ink.wgink.module.wechat.pojo.pay.v3.jsapi.pojo.PayPrepay; import ink.wgink.module.wechat.pojo.pay.v3.jsapi.pojo.PayPrepay;
import ink.wgink.module.wechat.request.pay.v3.AbstractPayRequest; import ink.wgink.module.wechat.request.pay.v3.AbstractPayRequest;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.client.HttpClientErrorException; import org.springframework.web.client.HttpClientErrorException;
@ -48,7 +49,10 @@ public class PlaceOrderPayRequestImpl extends AbstractPayRequest<PayPlaceOrder,
@Override @Override
public PayPrepay response(ResponseEntity<String> responseEntity) { 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; return null;
} }

View File

@ -2,8 +2,14 @@ package ink.wgink.module.wechat.service.pay;
import ink.wgink.common.base.DefaultBaseService; import ink.wgink.common.base.DefaultBaseService;
import ink.wgink.exceptions.PropertiesException; 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 ink.wgink.properties.wechat.pay.v3.PayProperties;
import org.springframework.beans.factory.annotation.Autowired; 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 * @ClassName: BasePayService
@ -20,6 +26,32 @@ public class BasePayService extends DefaultBaseService {
@Autowired @Autowired
protected PayProperties payProperties; 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() { protected String getPayBaseUrl() {
if (!payProperties.getActive()) { if (!payProperties.getActive()) {
throw new PropertiesException("微信支付未激活"); throw new PropertiesException("微信支付未激活");

View File

@ -1,9 +1,8 @@
package ink.wgink.module.wechat.service.pay.v3; 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.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/** /**
* @ClassName: IPayService * @ClassName: IPayService
@ -19,8 +18,9 @@ public interface IPayService {
* *
* @param request * @param request
* @param response * @param response
* @param nonce
* @param timestamp
* @param wechatpaySignature * @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 @Override
public void handle(PayNoticeCiphertext payNoticeCiphertext) throws Exception { 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 com.alibaba.fastjson.JSONObject;
import ink.wgink.common.base.DefaultBaseService; import ink.wgink.common.base.DefaultBaseService;
import ink.wgink.exceptions.ParamsException;
import ink.wgink.module.wechat.enums.PayErrorMsgEnum; 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.PayErrorResponse;
import ink.wgink.module.wechat.pojo.pay.v3.PayNotice; import ink.wgink.module.wechat.pojo.pay.v3.PayNotice;
import ink.wgink.module.wechat.pojo.pay.v3.PayNoticeCiphertext; 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.service.pay.v3.IPayService;
import ink.wgink.module.wechat.utils.pay.PayVerifyUtil; import ink.wgink.module.wechat.utils.pay.PayVerifyUtil;
import ink.wgink.properties.wechat.pay.v3.PayProperties; 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.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import java.io.FileInputStream; import java.io.BufferedReader;
import java.io.IOException; import java.io.IOException;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
@ -39,15 +41,17 @@ public class IPayServiceImpl extends DefaultBaseService implements IPayService {
private IPayNoticeService payNoticeService; private IPayNoticeService payNoticeService;
@Override @Override
public void notice(HttpServletRequest request, HttpServletResponse response, String wechatpaySignature, PayNotice payNotice) { public void notice(HttpServletRequest request, HttpServletResponse response, String nonce, String timestamp, String wechatpaySignature) throws IOException {
String nonce = payNotice.getResource().getNonce(); String payNoticeJsonString = getBodyString(request);
DateTime createTimeDateTime = DateTime.parse(payNotice.getCreate_time()); if (StringUtils.isBlank(payNoticeJsonString)) {
long millis = createTimeDateTime.getMillis(); throw new ParamsException("请求body为空");
String payNoticeJsonString = JSONObject.toJSONString(payNotice); }
LOG.debug("payNoticeJsonString: {}", payNoticeJsonString);
PayNotice payNotice = JSONObject.parseObject(payNoticeJsonString, PayNotice.class);
try { try {
LOG.debug("wechatpaySignature: {}", wechatpaySignature);
LOG.debug("验证签名"); 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) { if (!checkVerify) {
errorResult(response, PayErrorMsgEnum.SIGN_ERROR_401); errorResult(response, PayErrorMsgEnum.SIGN_ERROR_401);
return; 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.request.pay.v3.jsapi.PlaceOrderPayRequestImpl;
import ink.wgink.module.wechat.service.pay.BasePayService; import ink.wgink.module.wechat.service.pay.BasePayService;
import ink.wgink.module.wechat.service.pay.v3.jsapi.IJsapiService; 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.module.wechat.utils.pay.PayPrivateKeyUtil;
import ink.wgink.properties.wechat.miniapp.MiniappProperties; import ink.wgink.properties.wechat.miniapp.MiniappProperties;
import ink.wgink.util.BeanPropertyCheckUtil; import ink.wgink.util.BeanPropertyCheckUtil;
@ -18,9 +17,6 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestMethod;
import java.io.FileInputStream;
import java.security.cert.X509Certificate;
/** /**
* @ClassName: JsapiServiceImpl * @ClassName: JsapiServiceImpl
* @Description: Jsapi业务 * @Description: Jsapi业务
@ -43,19 +39,19 @@ public class JsapiServiceImpl extends BasePayService implements IJsapiService {
String urlSuffix = "/v3/pay/transactions/jsapi"; String urlSuffix = "/v3/pay/transactions/jsapi";
String url = getPayBaseUrl() + urlSuffix; String url = getPayBaseUrl() + urlSuffix;
LOG.debug("获得证书"); LOG.debug("获得证书序列号");
X509Certificate certificate = PayCertificateUtil.getCertificate(new FileInputStream(payProperties.getCertificatePath())); String serialNumber = getSerialNumber();
String serialNumber = certificate.getSerialNumber().toString(16);
LOG.debug("调用微信支付,发起预支付"); LOG.debug("调用微信支付,发起预支付");
PlaceOrderPayRequestImpl placeOrderPayRequest = new PlaceOrderPayRequestImpl(); PlaceOrderPayRequestImpl placeOrderPayRequest = new PlaceOrderPayRequestImpl();
JSONObject body = placeOrderPayRequest.bodyJson(payPlaceOrder); 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("请求获得结果"); LOG.debug("请求获得结果");
PayPrepay payPrepay = placeOrderPayRequest.post(url, authorization, serialNumber, body); PayPrepay payPrepay = placeOrderPayRequest.post(url, authorization, serialNumber, body);
LOG.debug("生成返回结果"); LOG.debug("生成返回结果");
long timestamp = System.currentTimeMillis(); long timestamp = System.currentTimeMillis() / 1000;
String nonceStr = WStringUtil.randomSubStr(UUIDUtil.get32UUID(), 10).toUpperCase(); String nonceStr = WStringUtil.randomSubStr(UUIDUtil.get32UUID(), 10).toUpperCase();
PaySign paySign = new PaySign(); PaySign paySign = new PaySign();
@ -82,7 +78,7 @@ public class JsapiServiceImpl extends BasePayService implements IJsapiService {
.append(timestamp).append("\n") .append(timestamp).append("\n")
.append(nonceStr).append("\n") .append(nonceStr).append("\n")
.append("prepay_id=").append(prepayId).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); 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.MiniappManager;
import ink.wgink.module.wechat.manager.OfficialAccountAccessTokenManager; 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.miniapp.MiniappProperties;
import ink.wgink.properties.wechat.official.account.OfficialAccountProperties; import ink.wgink.properties.wechat.official.account.OfficialAccountProperties;
import ink.wgink.properties.wechat.pay.v3.PayProperties; import ink.wgink.properties.wechat.pay.v3.PayProperties;
@ -32,12 +34,18 @@ public class WechatStartUp implements ApplicationRunner {
private OfficialAccountProperties officialAccountProperties; private OfficialAccountProperties officialAccountProperties;
@Autowired @Autowired
private PayProperties payProperties; private PayProperties payProperties;
@Autowired
private ICertificateService certificateService;
@Override @Override
public void run(ApplicationArguments args) throws Exception { public void run(ApplicationArguments args) throws Exception {
new Thread(() -> {
refreshOfficialAccountAccessToken(); refreshOfficialAccountAccessToken();
}).start(); removeTimeoutMiniAppUser();
// 支付
PayManager payManager = PayManager.getInstance();
payManager.setCertificateService(certificateService);
payManager.setPayProperties(payProperties);
refreshCertificate();
} }
@Scheduled(cron = "0 0/5 * * * ?") @Scheduled(cron = "0 0/5 * * * ?")
@ -59,4 +67,15 @@ public class WechatStartUp implements ApplicationRunner {
MiniappManager.getInstance().removeTimeout(); 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 org.bouncycastle.util.encoders.Base64;
import java.io.InputStream;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.security.PublicKey; import java.security.PublicKey;
import java.security.cert.X509Certificate; import java.security.cert.X509Certificate;
@ -15,7 +14,6 @@ import java.security.cert.X509Certificate;
* @Version: 1.0 * @Version: 1.0
*/ */
public class PayVerifyUtil { public class PayVerifyUtil {
/** /**
* 验证签名 * 验证签名
* *
@ -27,10 +25,10 @@ public class PayVerifyUtil {
* @return * @return
* @throws Exception * @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); String buildSignMessage = buildSignMessage(timestamp, nonce, body);
System.out.println("buildSignMessage: " + buildSignMessage);
// 获取证书 // 获取证书
X509Certificate certificate = PayCertificateUtil.getCertificate(certInputStream);
PublicKey publicKey = certificate.getPublicKey(); PublicKey publicKey = certificate.getPublicKey();
return checkByPublicKey(buildSignMessage, signature, publicKey); return checkByPublicKey(buildSignMessage, signature, publicKey);
} }