skyline 2 年 前
コミット
1ba771f57f

+ 5 - 5
admin/src/main/resources/application.yml

@@ -104,15 +104,15 @@ wechat:
 # EN+充电配置
 en-plus:
   # 运营商ID
-  operator-id: MA5HJNDG1
+  operatorId: MA5HJNDG1
   # 运营商密钥
-  operator-secret: c7fd9b753a9f434e
+  operatorSecret: c7fd9b753a9f434e
   # 消息密钥
-  data-secret: 5cb7e12da198420a
+  dataSecret: 5cb7e12da198420a
   # 消息密钥初始化向量
-  data-secret-iv: 8a6ac88326bc4d3f
+  dataSecretIv: 8a6ac88326bc4d3f
   # 签名密钥
-  sig-secret: 2365b20f69e44817
+  sigSecret: 2365b20f69e44817
   # 最小充电余额(分)
   charge-min-amount: 200
 

+ 19 - 0
common/src/main/java/com/kym/common/config/EnPlusConfig.java

@@ -0,0 +1,19 @@
+package com.kym.common.config;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+
+
+@Data
+@Configuration("EnPlusConfig")
+@ConfigurationProperties(prefix = "en-plus")
+public class EnPlusConfig {
+    private String operatorId;
+    private String operatorSecret;
+    private String dataSecret;
+    private String dataSecretIv;
+    private String sigSecret;
+    private int chargeMinAmount;
+    private String apiDomain;
+}

+ 6 - 4
common/src/main/java/com/kym/common/enums/EnPlusApi.java

@@ -1,5 +1,7 @@
 package com.kym.common.enums;
 
+import cn.hutool.extra.spring.SpringUtil;
+import com.kym.common.config.EnPlusConfig;
 import lombok.AllArgsConstructor;
 import lombok.Getter;
 import org.springframework.web.bind.annotation.RequestMethod;
@@ -14,7 +16,6 @@ import org.springframework.web.bind.annotation.RequestMethod;
 @Getter
 public enum EnPlusApi implements Api {
 
-
     // 认证-获取token
     EN_PLUS_QUERY_TOKEN(Constants.DOMAIN + "query_token", RequestMethod.POST), //获取AccessToken
 
@@ -36,8 +37,9 @@ public enum EnPlusApi implements Api {
     private final String api;
     private final RequestMethod requestMethod;
 
+
     private static class Constants {
-        private static final String DOMAIN = "https://api.en-plus.cn:8080/Charge/evcs/v1//MA5HJNDG1/";
+        static EnPlusConfig config = SpringUtil.getBean("EnPlusConfig");
+        private static final String DOMAIN = config.getApiDomain();
     }
-
-}
+}

+ 13 - 8
common/src/main/java/com/kym/common/utils/AESUtil.java

@@ -1,10 +1,10 @@
 package com.kym.common.utils;
 
 
+import com.kym.common.config.EnPlusConfig;
 import jakarta.annotation.PostConstruct;
 import org.apache.tomcat.util.codec.binary.Base64;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.core.env.Environment;
 import org.springframework.stereotype.Component;
 
 import javax.crypto.Cipher;
@@ -36,8 +36,13 @@ public class AESUtil {
     private static String dataSecret;
     private static String dataSecretIV;
     private static String sigSecret;
-    @Autowired
-    Environment environment;
+
+    final
+    EnPlusConfig config;
+
+    public AESUtil(EnPlusConfig config) {
+        this.config = config;
+    }
 
     /**
      * AES 加密操作
@@ -137,11 +142,11 @@ public class AESUtil {
 
     @PostConstruct//初始化调用
     public void init() {
-        operatorId = environment.getProperty("en-plus.operator-id");
-        operatorSecret = environment.getProperty("en-plus.operator-secret");
-        dataSecret = environment.getProperty("en-plus.data-secret");
-        dataSecretIV = environment.getProperty("en-plus.data-secret-iv");
-        sigSecret = environment.getProperty("en-plus.sig-secret");
+        operatorId = config.getOperatorId();
+        operatorSecret = config.getOperatorSecret();
+        dataSecret = config.getDataSecret();
+        dataSecretIV = config.getDataSecretIv();
+        sigSecret = config.getSigSecret();
     }
 
 

+ 9 - 1
miniapp/src/main/java/com/kym/miniapp/controller/PaymentController.java

@@ -1,9 +1,10 @@
 package com.kym.miniapp.controller;
 
-import cn.dev33.satoken.stp.StpUtil;
 import com.alibaba.fastjson2.JSONObject;
 import com.kym.common.R;
 import com.kym.service.wechat.WxPayService;
+import jakarta.servlet.http.HttpServletRequest;
+import lombok.SneakyThrows;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.*;
 
@@ -25,5 +26,12 @@ public class PaymentController {
         return R.success(wxPayService.prepay(params));
     }
 
+    @SneakyThrows
+    @PostMapping("/notify")
+    @ResponseBody
+    R notify(HttpServletRequest request) {
+        return R.success(wxPayService.wxNotify(request));
+    }
+
 
 }

+ 16 - 0
miniapp/src/main/resources/application-dev.yml

@@ -0,0 +1,16 @@
+# EN+充电配置
+en-plus:
+  # 运营商ID
+  operatorId: MA5HJNDG1
+  # 运营商密钥
+  operatorSecret: 5009db3dc1e94ea8
+  # 消息密钥
+  dataSecret: 8c15f5bf050948ba
+  # 消息密钥初始化向量
+  dataSecretIv: 915bea94fa13461d
+  # 签名密钥
+  sigSecret: 46050b0bb5b7415c
+  # 最小充电余额(分)
+  chargeMinAmount: 200
+  # 接口地址
+  apiDomain: https://dev.en-plus.cn/Charge/evcs/v1//MA5HJNDG1/

+ 16 - 0
miniapp/src/main/resources/application-prod.yml

@@ -0,0 +1,16 @@
+# EN+充电配置
+en-plus:
+  # 运营商ID
+  operatorId: MA5HJNDG1
+  # 运营商密钥
+  operatorSecret: c7fd9b753a9f434e
+  # 消息密钥
+  dataSecret: 5cb7e12da198420a
+  # 消息密钥初始化向量
+  dataSecretIv: 8a6ac88326bc4d3f
+  # 签名密钥
+  sigSecret: 2365b20f69e44817
+  # 最小充电余额(分)
+  charge-min-amount: 200
+  # 接口地址
+  api-domain: https://api.en-plus.cn:8080/Charge/evcs/v1//MA5HJNDG1/

+ 2 - 0
miniapp/src/main/resources/application.yml

@@ -1,4 +1,6 @@
 spring:
+  profiles:
+    active: dev
   application:
     name: miniapp
   datasource:

+ 29 - 10
service/src/main/java/com/kym/service/enplus/impl/EnPlusServiceImpl.java

@@ -21,9 +21,13 @@ import org.springframework.beans.factory.annotation.Value;
 import org.springframework.context.annotation.Lazy;
 import org.springframework.data.redis.core.RedisTemplate;
 import org.springframework.stereotype.Service;
+import org.yaml.snakeyaml.Yaml;
 
+import java.io.FileInputStream;
 import java.io.IOException;
+import java.io.InputStream;
 import java.time.LocalDateTime;
+import java.util.Map;
 import java.util.concurrent.TimeUnit;
 
 /**
@@ -37,11 +41,11 @@ public class EnPlusServiceImpl implements EnPlusService {
     public static final MediaType JSON = MediaType.get("application/json; charset=utf-8");
     private static final Logger LOGGER = LoggerFactory.getLogger(EnPlusServiceImpl.class);
     static OkHttpClient HTTP_CLIENT = new OkHttpClient.Builder().build();
-    @Value("${en-plus.operator-id}")
+    @Value("${en-plus.operatorId}")
     private String OperatorId;
-    @Value("${en-plus.operator-secret}")
+    @Value("${en-plus.operatorSecret}")
     private String OperatorSecret;
-    @Value("${en-plus.sig-secret}")
+    @Value("${en-plus.sigSecret}")
     private String SigSecret;
     @Autowired
     private RedisTemplate<String, String> redisTemplate;
@@ -73,11 +77,8 @@ public class EnPlusServiceImpl implements EnPlusService {
     @Override
     public EnResponse enPlusPost(String url, String params) {
         // token获取
-        var token = "";
-        if (!url.contains(EnPlusApi.EN_PLUS_QUERY_TOKEN.getApi())) {
-            token = enPlusServiceHelper.queryToken();
-        }
-        Headers headers = Headers.of("Authorization", "Bearer " + token);
+        var token = enPlusServiceHelper.queryToken();
+        Headers headers = Headers.of("Authorization", "Bearer ".concat(token));
         RequestBody requestBody = RequestBody.create(params, JSON);
         Request request = new Request.Builder()
                 .headers(headers)
@@ -112,9 +113,9 @@ public class EnPlusServiceImpl implements EnPlusService {
 
         var requestParams = buildParams(data);
 
-        var enResponse = enPlusPost(EnPlusApi.EN_PLUS_QUERY_TOKEN.getApi(), requestParams);
+        var enResponse = enGetToken(EnPlusApi.EN_PLUS_QUERY_TOKEN.getApi(), requestParams);
 
-        if (0 == enResponse.getRet()) {
+        if (enResponse != null && 0 == enResponse.getRet()) {
             // 解密Data获取token
             var enRespQueryToken = JSONObject.parseObject(AESUtil.decrypt(enResponse.getData()), EnRespQueryToken.class);
             LOGGER.debug("EN+接口AccessToken:{}", enRespQueryToken.toString());
@@ -128,6 +129,24 @@ public class EnPlusServiceImpl implements EnPlusService {
         }
     }
 
+    public EnResponse enGetToken(String url, String params) {
+        // token获取
+        RequestBody requestBody = RequestBody.create(params, JSON);
+        Request request = new Request.Builder()
+                .post(requestBody)
+                .url(url)
+                .build();
+        var response = parse(synchronizedCall(request), EnResponse.class);
+
+        if (response != null && 0 == response.getRet()) {
+            return response;
+        } else {
+            LOGGER.error("EN+接口数据异常:url:{}\n params:{}\n返回信息:{}", url, params, response);
+            throw new BusinessException(ResponseEnum.EN_PLUS_API_EXCEPTION);
+        }
+    }
+
+
     /**
      * 加密数据、签名、组装请求消息体
      *

+ 1 - 1
service/src/main/java/com/kym/service/miniapp/impl/ChargeServiceImpl.java

@@ -42,7 +42,7 @@ public class ChargeServiceImpl implements ChargeService {
     @Autowired
     private EnPlusService enPlusService;
 
-    @Value("${en-plus.operator-id}")
+    @Value("${en-plus.operatorId}")
     private String operatorId;
 
     /**

+ 6 - 0
service/src/main/java/com/kym/service/wechat/WxPayService.java

@@ -2,6 +2,10 @@ package com.kym.service.wechat;
 
 import com.alibaba.fastjson2.JSONObject;
 import com.wechat.pay.java.service.payments.jsapi.model.PrepayResponse;
+import jakarta.servlet.http.HttpServletRequest;
+import org.springframework.http.ResponseEntity;
+
+import java.io.IOException;
 
 /**
  * @author skyline
@@ -10,4 +14,6 @@ import com.wechat.pay.java.service.payments.jsapi.model.PrepayResponse;
  */
 public interface WxPayService {
     PrepayResponse prepay(JSONObject rechargeAmount);
+
+    ResponseEntity.BodyBuilder wxNotify(HttpServletRequest request) throws IOException;
 }

+ 67 - 65
service/src/main/java/com/kym/service/wechat/impl/WxPayServiceImpl.java

@@ -2,6 +2,7 @@ package com.kym.service.wechat.impl;
 
 import cn.dev33.satoken.stp.StpUtil;
 import cn.hutool.core.io.IoUtil;
+import cn.hutool.core.util.RandomUtil;
 import com.alibaba.fastjson2.JSONObject;
 import com.kym.common.config.WxPayConfig;
 import com.kym.common.constant.ResponseEnum;
@@ -18,16 +19,11 @@ import com.wechat.pay.java.core.notification.NotificationConfig;
 import com.wechat.pay.java.core.notification.NotificationParser;
 import com.wechat.pay.java.core.notification.RequestParam;
 import com.wechat.pay.java.service.payments.jsapi.JsapiService;
-import com.wechat.pay.java.service.payments.jsapi.model.Amount;
-import com.wechat.pay.java.service.payments.jsapi.model.CloseOrderRequest;
-import com.wechat.pay.java.service.payments.jsapi.model.Payer;
-import com.wechat.pay.java.service.payments.jsapi.model.PrepayRequest;
-import com.wechat.pay.java.service.payments.jsapi.model.PrepayResponse;
-import com.wechat.pay.java.service.payments.jsapi.model.QueryOrderByIdRequest;
-import com.wechat.pay.java.service.payments.jsapi.model.QueryOrderByOutTradeNoRequest;
+import com.wechat.pay.java.service.payments.jsapi.model.*;
 import com.wechat.pay.java.service.payments.model.Transaction;
 import jakarta.annotation.PostConstruct;
 import jakarta.servlet.http.HttpServletRequest;
+import lombok.SneakyThrows;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -149,80 +145,86 @@ public class WxPayServiceImpl implements WxPayService {
         service.closeOrder(request);
     }
 
-    public ResponseEntity.BodyBuilder wxNotify(HttpServletRequest request) throws IOException {
+    @SneakyThrows
+    @Override
+    public ResponseEntity.BodyBuilder wxNotify(HttpServletRequest request) {
+        var no = RandomUtil.randomInt(1000, 9999);
         var signature = request.getHeader("Wechatpay-Signature");
         var serial = request.getHeader("Wechatpay-Serial");
         var nonce = request.getHeader("Wechatpay-Nonce");
         var timestamp = request.getHeader("Wechatpay-Timestamp");
         var signatureType = request.getHeader("Wechatpay-Signature-Type");
 
+        LOGGER.info("微信支付回调{}:\n Request参数:\n signature:{},serial:{},nonce:{},timestamp:{},signatureType:{}", no, signature, serial, nonce, timestamp, signatureType);
+
         // request中获取body
         BufferedReader br = request.getReader();
         String str;
         var requestBody = new StringBuilder();
         while ((str = br.readLine()) != null) {
             requestBody.append(str);
+        }
 
+        LOGGER.info("微信支付回调{}:\nBody数据:\n{}", no, requestBody);
 
-            // 构造 RequestParam
-            RequestParam requestParam = new RequestParam.Builder()
-                    .serialNumber(serial)
-                    .nonce(nonce) // 随机数
-                    .signature(signature)
-                    .timestamp(timestamp)
-                    .body(requestBody.toString())
-                    .build();
-
-
-            // 如果已经初始化了 RSAAutoCertificateConfig,可直接使用
-            // 初始化 NotificationParser
-            NotificationParser parser = new NotificationParser((NotificationConfig) config);
-
-            try {
-                // 以支付通知回调为例,验签、解密并转换成 Transaction
-                Transaction transaction = parser.parse(requestParam, Transaction.class);
-                // 判断是否已经接收处理过通知
-                if (payLogService.lambdaQuery().eq(PayLog::getOutTradeNo, transaction.getOutTradeNo()).one() != null) {
-                    return ResponseEntity.status(HttpStatus.OK);
-                }
-
-                // 资金流水
-                var walletDetail = walletDetailService.getWalletDetailByOrderNo(transaction.getOutTradeNo());
-                if (walletDetail != null) {
-                    walletDetail.setType(1); // 充值
-                    walletDetail.setStatus(1);  //已确认
-                    walletDetail.setCurrency(transaction.getAmount().getCurrency());
-                    walletDetail.setAmount(transaction.getAmount().getTotal());
-
-                    // 支付记录
-                    var payLog = new PayLog();
-                    payLog.setUserId(walletDetail.getUserId());
-                    payLog.setOpenid(transaction.getPayer().getOpenid());
-                    payLog.setBankType(transaction.getBankType());
-                    payLog.setMchId(transaction.getMchid());
-                    payLog.setOutTradeNo(transaction.getOutTradeNo());
-                    payLog.setTransactionId(transaction.getTransactionId());
-                    payLog.setSuccessTime(transaction.getSuccessTime());
-                    payLog.setTradeType(transaction.getTradeType().name());
-                    payLog.setTradeState(transaction.getTradeState().name());
-                    payLog.setAttach(transaction.getAttach());
-                    payLog.setTotal(transaction.getAmount().getTotal());
-                    payLog.setCurrency(transaction.getAmount().getCurrency());
-                    payLog.setPayerTotal(transaction.getAmount().getPayerTotal());
-                    payLog.setPayerCurrency(transaction.getAmount().getPayerCurrency());
-                    payLogService.save(payLog);
-
-                } else {
-                    LOGGER.error("微信支付通知处理异常,资金流水为空,回调信息:{}", transaction);
-                    return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR);
-                }
-
-            } catch (ValidationException e) {
-                // 签名验证失败,返回 401 UNAUTHORIZED 状态码
-                LOGGER.error("微信支付通知眼前失败", e);
-                throw e;
+        // 构造 RequestParam
+        RequestParam requestParam = new RequestParam.Builder()
+                .serialNumber(serial)
+                .nonce(nonce) // 随机数
+                .signature(signature)
+                .timestamp(timestamp)
+                .body(requestBody.toString())
+                .build();
+        LOGGER.info("微信支付回调{}:构造 RequestParam完毕", no);
+
+        // 如果已经初始化了 RSAAutoCertificateConfig,可直接使用
+        // 初始化 NotificationParser
+        NotificationParser parser = new NotificationParser((NotificationConfig) config);
+
+        LOGGER.info("微信支付回调{}:初始化NotificationParser完毕", no);
+        try {
+            // 以支付通知回调为例,验签、解密并转换成 Transaction
+            Transaction transaction = parser.parse(requestParam, Transaction.class);
+            LOGGER.info("微信支付回调{}:验签解密完毕,数据:\n{}", no, transaction);
+            // 判断是否已经接收处理过通知
+            if (payLogService.lambdaQuery().eq(PayLog::getOutTradeNo, transaction.getOutTradeNo()).one() != null) {
+                return ResponseEntity.status(HttpStatus.OK);
             }
 
+            // 资金流水
+            var walletDetail = walletDetailService.getWalletDetailByOrderNo(transaction.getOutTradeNo());
+            if (walletDetail != null) {
+                walletDetail.setType(1); // 充值
+                walletDetail.setStatus(1);  //已确认
+                walletDetail.setCurrency(transaction.getAmount().getCurrency());
+                walletDetail.setAmount(transaction.getAmount().getTotal());
+
+                // 支付记录
+                var payLog = new PayLog();
+                payLog.setUserId(walletDetail.getUserId());
+                payLog.setOpenid(transaction.getPayer().getOpenid());
+                payLog.setBankType(transaction.getBankType());
+                payLog.setMchId(transaction.getMchid());
+                payLog.setOutTradeNo(transaction.getOutTradeNo());
+                payLog.setTransactionId(transaction.getTransactionId());
+                payLog.setSuccessTime(transaction.getSuccessTime());
+                payLog.setTradeType(transaction.getTradeType().name());
+                payLog.setTradeState(transaction.getTradeState().name());
+                payLog.setAttach(transaction.getAttach());
+                payLog.setTotal(transaction.getAmount().getTotal());
+                payLog.setCurrency(transaction.getAmount().getCurrency());
+                payLog.setPayerTotal(transaction.getAmount().getPayerTotal());
+                payLog.setPayerCurrency(transaction.getAmount().getPayerCurrency());
+                payLogService.save(payLog);
+                LOGGER.info("微信支付回调{}:业务处理结束", no);
+            } else {
+                LOGGER.error("微信支付通知处理异常,资金流水为空,回调信息:{}", transaction);
+                return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR);
+            }
+        } catch (ValidationException e) {
+            // 签名验证失败,返回 401 UNAUTHORIZED 状态码
+            LOGGER.error("微信支付通知眼前失败", e);
+            return ResponseEntity.status(HttpStatus.UNAUTHORIZED);
         }
 
         // 处理成功,返回 200 OK 状态码