From 59d22699d58ba77838c2bb085ce9e830e6d952b9 Mon Sep 17 00:00:00 2001 From: wanggeng <450292408@qq.com> Date: Thu, 26 Aug 2021 16:11:27 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E6=94=AF=E4=BB=98=E7=AE=A1?= =?UTF-8?q?=E7=90=86=E5=99=A8=EF=BC=8C=E5=AE=8C=E6=88=90=E6=94=AF=E4=BB=98?= =?UTF-8?q?=E9=80=9A=E7=9F=A5=E7=AD=BE=E5=90=8D=E9=AA=8C=E8=AF=81=EF=BC=8C?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E9=BB=98=E8=AE=A4=E9=80=9A=E7=9F=A5=E5=AE=9E?= =?UTF-8?q?=E7=8E=B0=E7=B1=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/miniapp/MiniappPayAppController.java | 2 +- .../wechat/pay/v3/PayController.java | 13 +- .../wechat/manager/pay/v3/PayManager.java | 77 +++++++++++- .../module/wechat/pojo/pay/v3/PayNotice.java | 2 - .../pojo/pay/v3/certificates/Certificate.java | 118 ++++++++++++++++++ .../request/pay/v3/AbstractPayRequest.java | 26 ---- .../CertificatePayRequestImpl.java | 61 +++++++++ .../v3/jsapi/PlaceOrderPayRequestImpl.java | 6 +- .../wechat/service/pay/BasePayService.java | 32 +++++ .../wechat/service/pay/v3/IPayService.java | 8 +- .../v3/certificates/ICertificateService.java | 19 +++ .../impl/CertificateServiceImpl.java | 58 +++++++++ .../pay/v3/impl/DefaultPayNoticeService.java | 2 +- .../service/pay/v3/impl/IPayServiceImpl.java | 31 +++-- .../pay/v3/jsapi/impl/JsapiServiceImpl.java | 16 +-- .../module/wechat/startup/WechatStartUp.java | 25 +++- .../wechat/utils/pay/PayVerifyUtil.java | 6 +- 17 files changed, 432 insertions(+), 70 deletions(-) create mode 100644 module-wechat/src/main/java/ink/wgink/module/wechat/pojo/pay/v3/certificates/Certificate.java create mode 100644 module-wechat/src/main/java/ink/wgink/module/wechat/request/pay/v3/certificates/CertificatePayRequestImpl.java create mode 100644 module-wechat/src/main/java/ink/wgink/module/wechat/service/pay/v3/certificates/ICertificateService.java create mode 100644 module-wechat/src/main/java/ink/wgink/module/wechat/service/pay/v3/certificates/impl/CertificateServiceImpl.java diff --git a/module-wechat/src/main/java/ink/wgink/module/wechat/controller/app/miniapp/MiniappPayAppController.java b/module-wechat/src/main/java/ink/wgink/module/wechat/controller/app/miniapp/MiniappPayAppController.java index 075e5d20..19620860 100644 --- a/module-wechat/src/main/java/ink/wgink/module/wechat/controller/app/miniapp/MiniappPayAppController.java +++ b/module-wechat/src/main/java/ink/wgink/module/wechat/controller/app/miniapp/MiniappPayAppController.java @@ -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); diff --git a/module-wechat/src/main/java/ink/wgink/module/wechat/controller/wechat/pay/v3/PayController.java b/module-wechat/src/main/java/ink/wgink/module/wechat/controller/wechat/pay/v3/PayController.java index 2e0a0881..0c4ba958 100644 --- a/module-wechat/src/main/java/ink/wgink/module/wechat/controller/wechat/pay/v3/PayController.java +++ b/module-wechat/src/main/java/ink/wgink/module/wechat/controller/wechat/pay/v3/PayController.java @@ -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); } } diff --git a/module-wechat/src/main/java/ink/wgink/module/wechat/manager/pay/v3/PayManager.java b/module-wechat/src/main/java/ink/wgink/module/wechat/manager/pay/v3/PayManager.java index 3ff08498..6835142f 100644 --- a/module-wechat/src/main/java/ink/wgink/module/wechat/manager/pay/v3/PayManager.java +++ b/module-wechat/src/main/java/ink/wgink/module/wechat/manager/pay/v3/PayManager.java @@ -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 { diff --git a/module-wechat/src/main/java/ink/wgink/module/wechat/pojo/pay/v3/PayNotice.java b/module-wechat/src/main/java/ink/wgink/module/wechat/pojo/pay/v3/PayNotice.java index 42093d95..e6e593af 100644 --- a/module-wechat/src/main/java/ink/wgink/module/wechat/pojo/pay/v3/PayNotice.java +++ b/module-wechat/src/main/java/ink/wgink/module/wechat/pojo/pay/v3/PayNotice.java @@ -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 = "回调摘要") diff --git a/module-wechat/src/main/java/ink/wgink/module/wechat/pojo/pay/v3/certificates/Certificate.java b/module-wechat/src/main/java/ink/wgink/module/wechat/pojo/pay/v3/certificates/Certificate.java new file mode 100644 index 00000000..7f2192af --- /dev/null +++ b/module-wechat/src/main/java/ink/wgink/module/wechat/pojo/pay/v3/certificates/Certificate.java @@ -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(); + } + } + +} diff --git a/module-wechat/src/main/java/ink/wgink/module/wechat/request/pay/v3/AbstractPayRequest.java b/module-wechat/src/main/java/ink/wgink/module/wechat/request/pay/v3/AbstractPayRequest.java index 72e945ce..030a1214 100644 --- a/module-wechat/src/main/java/ink/wgink/module/wechat/request/pay/v3/AbstractPayRequest.java +++ b/module-wechat/src/main/java/ink/wgink/module/wechat/request/pay/v3/AbstractPayRequest.java @@ -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 { } } - /** - * 认证头 - * - * @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()); - } - /** * 请求头 * diff --git a/module-wechat/src/main/java/ink/wgink/module/wechat/request/pay/v3/certificates/CertificatePayRequestImpl.java b/module-wechat/src/main/java/ink/wgink/module/wechat/request/pay/v3/certificates/CertificatePayRequestImpl.java new file mode 100644 index 00000000..7f737595 --- /dev/null +++ b/module-wechat/src/main/java/ink/wgink/module/wechat/request/pay/v3/certificates/CertificatePayRequestImpl.java @@ -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> { + + @Override + public JSONObject bodyJson(JSONObject jsonObject) { + return null; + } + + @Override + public List response(ResponseEntity responseEntity) { + JSONObject resultObject = JSONObject.parseObject(responseEntity.getBody()); + List 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()); + } +} diff --git a/module-wechat/src/main/java/ink/wgink/module/wechat/request/pay/v3/jsapi/PlaceOrderPayRequestImpl.java b/module-wechat/src/main/java/ink/wgink/module/wechat/request/pay/v3/jsapi/PlaceOrderPayRequestImpl.java index 96a89040..d2045993 100644 --- a/module-wechat/src/main/java/ink/wgink/module/wechat/request/pay/v3/jsapi/PlaceOrderPayRequestImpl.java +++ b/module-wechat/src/main/java/ink/wgink/module/wechat/request/pay/v3/jsapi/PlaceOrderPayRequestImpl.java @@ -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 responseEntity) { - + LOG.debug("Response success status code: {}", responseEntity.getStatusCode()); + if (responseEntity.getStatusCode() == HttpStatus.OK) { + return JSONObject.parseObject(responseEntity.getBody(), PayPrepay.class); + } return null; } diff --git a/module-wechat/src/main/java/ink/wgink/module/wechat/service/pay/BasePayService.java b/module-wechat/src/main/java/ink/wgink/module/wechat/service/pay/BasePayService.java index 8470c0cd..404cc876 100644 --- a/module-wechat/src/main/java/ink/wgink/module/wechat/service/pay/BasePayService.java +++ b/module-wechat/src/main/java/ink/wgink/module/wechat/service/pay/BasePayService.java @@ -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("微信支付未激活"); diff --git a/module-wechat/src/main/java/ink/wgink/module/wechat/service/pay/v3/IPayService.java b/module-wechat/src/main/java/ink/wgink/module/wechat/service/pay/v3/IPayService.java index 41d93632..2346863e 100644 --- a/module-wechat/src/main/java/ink/wgink/module/wechat/service/pay/v3/IPayService.java +++ b/module-wechat/src/main/java/ink/wgink/module/wechat/service/pay/v3/IPayService.java @@ -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; } diff --git a/module-wechat/src/main/java/ink/wgink/module/wechat/service/pay/v3/certificates/ICertificateService.java b/module-wechat/src/main/java/ink/wgink/module/wechat/service/pay/v3/certificates/ICertificateService.java new file mode 100644 index 00000000..144c3e59 --- /dev/null +++ b/module-wechat/src/main/java/ink/wgink/module/wechat/service/pay/v3/certificates/ICertificateService.java @@ -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; + +} diff --git a/module-wechat/src/main/java/ink/wgink/module/wechat/service/pay/v3/certificates/impl/CertificateServiceImpl.java b/module-wechat/src/main/java/ink/wgink/module/wechat/service/pay/v3/certificates/impl/CertificateServiceImpl.java new file mode 100644 index 00000000..f19b606d --- /dev/null +++ b/module-wechat/src/main/java/ink/wgink/module/wechat/service/pay/v3/certificates/impl/CertificateServiceImpl.java @@ -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 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; + } +} diff --git a/module-wechat/src/main/java/ink/wgink/module/wechat/service/pay/v3/impl/DefaultPayNoticeService.java b/module-wechat/src/main/java/ink/wgink/module/wechat/service/pay/v3/impl/DefaultPayNoticeService.java index ed9096e0..e8c61845 100644 --- a/module-wechat/src/main/java/ink/wgink/module/wechat/service/pay/v3/impl/DefaultPayNoticeService.java +++ b/module-wechat/src/main/java/ink/wgink/module/wechat/service/pay/v3/impl/DefaultPayNoticeService.java @@ -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()); } } diff --git a/module-wechat/src/main/java/ink/wgink/module/wechat/service/pay/v3/impl/IPayServiceImpl.java b/module-wechat/src/main/java/ink/wgink/module/wechat/service/pay/v3/impl/IPayServiceImpl.java index 69f73930..cd6d3f53 100644 --- a/module-wechat/src/main/java/ink/wgink/module/wechat/service/pay/v3/impl/IPayServiceImpl.java +++ b/module-wechat/src/main/java/ink/wgink/module/wechat/service/pay/v3/impl/IPayServiceImpl.java @@ -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(); + } + } diff --git a/module-wechat/src/main/java/ink/wgink/module/wechat/service/pay/v3/jsapi/impl/JsapiServiceImpl.java b/module-wechat/src/main/java/ink/wgink/module/wechat/service/pay/v3/jsapi/impl/JsapiServiceImpl.java index 3e955b7b..e33c850d 100644 --- a/module-wechat/src/main/java/ink/wgink/module/wechat/service/pay/v3/jsapi/impl/JsapiServiceImpl.java +++ b/module-wechat/src/main/java/ink/wgink/module/wechat/service/pay/v3/jsapi/impl/JsapiServiceImpl.java @@ -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); } diff --git a/module-wechat/src/main/java/ink/wgink/module/wechat/startup/WechatStartUp.java b/module-wechat/src/main/java/ink/wgink/module/wechat/startup/WechatStartUp.java index a2498ff9..4dc50399 100644 --- a/module-wechat/src/main/java/ink/wgink/module/wechat/startup/WechatStartUp.java +++ b/module-wechat/src/main/java/ink/wgink/module/wechat/startup/WechatStartUp.java @@ -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(); + } + } diff --git a/module-wechat/src/main/java/ink/wgink/module/wechat/utils/pay/PayVerifyUtil.java b/module-wechat/src/main/java/ink/wgink/module/wechat/utils/pay/PayVerifyUtil.java index 85f75b3e..6139f3ae 100644 --- a/module-wechat/src/main/java/ink/wgink/module/wechat/utils/pay/PayVerifyUtil.java +++ b/module-wechat/src/main/java/ink/wgink/module/wechat/utils/pay/PayVerifyUtil.java @@ -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); }