From 1b43670626f48266efb898ec7cd7deedcab9ba10 Mon Sep 17 00:00:00 2001
From: puzhibing <393733352@qq.com>
Date: 星期六, 17 八月 2024 15:31:23 +0800
Subject: [PATCH] Merge branch 'master' of http://120.76.84.145:10101/gitblit/r/java/mx_charging_pile

---
 ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/service/impl/TAppUserServiceImpl.java              |   58 
 ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/wx/pojo/AppletPhoneEncrypteData.java               |   19 
 ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/service/TAppUserService.java                       |   10 
 ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/wx/tools/WebUtils.java                             |   48 
 ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/wx/tools/JsonUtils.java                            |  110 +
 ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/wx/pojo/AppletUserDecodeData.java                  |   52 
 ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/wx/tools/SHA1.java                                 |   36 
 ruoyi-service/ruoyi-payment/src/main/java/com/ruoyi/payment/wx/pojo/Watermark.java                             |    9 
 ruoyi-service/ruoyi-payment/src/main/java/com/ruoyi/payment/wx/model/WeixinProperties.java                     |  101 +
 ruoyi-service/ruoyi-payment/src/main/java/com/ruoyi/payment/wx/utils/WxJsonUtils.java                          |   73 +
 ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/wx/tools/WxCache.java                              |  117 +
 ruoyi-service/ruoyi-payment/src/main/java/com/ruoyi/payment/wx/utils/WxAbstractPay.java                        |  333 +++++
 ruoyi-service/ruoyi-payment/src/main/java/com/ruoyi/payment/wx/utils/WxTimeUtils.java                          |  164 ++
 ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/wx/pojo/AppletUserEncrypteData.java                |   17 
 ruoyi-service/ruoyi-payment/src/main/java/com/ruoyi/payment/wx/model/V3.java                                   |   71 +
 ruoyi-service/ruoyi-payment/src/main/java/com/ruoyi/payment/wx/model/WxPaymentRefundModel.java                 |   84 +
 ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/wx/tools/WxJsonUtils.java                          |  109 +
 ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/constant/CacheConstants.java                |    1 
 ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/wx/tools/WxUtils.java                              |  175 ++
 ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/ali/Constant/AliConstant.java                      |   10 
 ruoyi-auth/src/main/java/com/ruoyi/auth/controller/TokenController.java                                        |   25 
 ruoyi-common/ruoyi-common-security/src/main/java/com/ruoyi/common/security/handler/GlobalExceptionHandler.java |   10 
 ruoyi-api/ruoyi-api-system/src/main/java/com/ruoyi/system/api/model/TAppUserLoginInfo.java                     |   79 +
 ruoyi-common/ruoyi-common-security/src/main/java/com/ruoyi/common/security/service/TokenService.java           |   16 
 ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/ali/tools/AliAppletTools.java                      |   37 
 ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/wx/body/resp/RespBody.java                         |   19 
 ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/wx/pojo/Watermark.java                             |    9 
 ruoyi-api/ruoyi-api-chargingPile/src/main/java/com/ruoyi/chargingPile/api/vo/SiteInfoVO.java                   |   23 
 ruoyi-service/ruoyi-payment/src/main/java/com/ruoyi/payment/wx/controller/WxPayController.java                 |  142 ++
 ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/wx/tools/WxCacheTemplate.java                      |   34 
 ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/wx/tools/WxAppletTools.java                        |  125 +
 ruoyi-service/ruoyi-account/pom.xml                                                                            |    6 
 ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/wx/tools/WxException.java                          |   55 
 ruoyi-service/ruoyi-payment/src/main/java/com/ruoyi/payment/wx/enums/RefundEnum.java                           |   53 
 ruoyi-service/ruoyi-payment/src/main/java/com/ruoyi/payment/wx/config/WxConfig.java                            |   33 
 ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/ali/model/AliProperties.java                       |   70 +
 ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/exception/user/UserAppletException.java     |   48 
 ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/wx/body/resp/Code2SessionRespBody.java             |   29 
 ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/controller/TAppCouponController.java               |    5 
 ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/controller/TAppUserController.java                 |   20 
 ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/wx/tools/WxCaffineCache.java                       |  122 +
 ruoyi-service/ruoyi-payment/src/main/java/com/ruoyi/payment/wx/pojo/AppletUserDecodeData.java                  |   52 
 ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/controller/TChargingOrderController.java               |    7 
 ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/controller/WxLoginController.java                  |   67 +
 ruoyi-service/ruoyi-payment/pom.xml                                                                            |   13 
 ruoyi-service/ruoyi-payment/src/main/java/com/ruoyi/payment/wx/utils/WxV3Pay.java                              |  197 ++
 ruoyi-service/ruoyi-payment/src/main/java/com/ruoyi/payment/wx/enums/TradeStateEnum.java                       |   41 
 ruoyi-service/ruoyi-payment/src/main/java/com/ruoyi/payment/wx/utils/WxUtils.java                              |  217 +++
 ruoyi-service/ruoyi-payment/src/main/java/com/ruoyi/payment/wx/resp/NotifyV3PayDecodeRespBody.java             |  222 +++
 ruoyi-api/ruoyi-api-system/src/main/java/com/ruoyi/system/api/model/LoginUserApplet.java                       |   15 
 ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/wx/body/resq/Code2SessionResqBody.java             |   21 
 ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/controller/AliLoginController.java                 |   91 +
 ruoyi-service/ruoyi-payment/src/main/java/com/ruoyi/payment/wx/utils/SHA1.java                                 |   39 
 ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/wx/model/WeixinProperties.java                     |   80 +
 ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/wx/body/resp/AccessTokenRespBody.java              |   28 
 ruoyi-service/ruoyi-payment/src/main/java/com/ruoyi/payment/wx/exception/WxException.java                      |   55 
 ruoyi-service/ruoyi-payment/src/main/java/com/ruoyi/payment/wx/model/WxPaymentInfoModel.java                   |  201 +++
 57 files changed, 3,874 insertions(+), 29 deletions(-)

diff --git a/ruoyi-api/ruoyi-api-chargingPile/src/main/java/com/ruoyi/chargingPile/api/vo/SiteInfoVO.java b/ruoyi-api/ruoyi-api-chargingPile/src/main/java/com/ruoyi/chargingPile/api/vo/SiteInfoVO.java
new file mode 100644
index 0000000..39fae79
--- /dev/null
+++ b/ruoyi-api/ruoyi-api-chargingPile/src/main/java/com/ruoyi/chargingPile/api/vo/SiteInfoVO.java
@@ -0,0 +1,23 @@
+package com.ruoyi.chargingPile.api.vo;
+
+import com.ruoyi.chargingPile.api.model.Site;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+@Data
+@ApiModel(value = "SiteVO对象", description = "小程序扫一扫-详情页面")
+public class SiteInfoVO  {
+    @ApiModelProperty(value = "电站名称")
+    private String name;
+    @ApiModelProperty(value = "桩编号")
+    private String number;
+    @ApiModelProperty(value = "普通电价")
+    private BigDecimal electrovalence;
+    @ApiModelProperty(value = "会员电价")
+    private BigDecimal vipElectrovalence;
+    @ApiModelProperty(value = "超时占位费说明")
+    private String spaceChargeExplain;
+}
diff --git a/ruoyi-api/ruoyi-api-system/src/main/java/com/ruoyi/system/api/model/LoginUserApplet.java b/ruoyi-api/ruoyi-api-system/src/main/java/com/ruoyi/system/api/model/LoginUserApplet.java
index 2f01d93..a7161a4 100644
--- a/ruoyi-api/ruoyi-api-system/src/main/java/com/ruoyi/system/api/model/LoginUserApplet.java
+++ b/ruoyi-api/ruoyi-api-system/src/main/java/com/ruoyi/system/api/model/LoginUserApplet.java
@@ -25,10 +25,10 @@
     /**
      * 用户名id
      */
-    private Integer userid;
+    private Long userId;
 
     /**
-     * 用户名
+     * 用户手机号
      */
     private String phone;
     /**
@@ -45,7 +45,12 @@
      * 登录IP地址
      */
     private String ipaddr;
-
-
-
+    /**
+     * 头像
+     */
+    private String avatar;
+    /**
+     * 地址
+     */
+    private String address;
 }
diff --git a/ruoyi-api/ruoyi-api-system/src/main/java/com/ruoyi/system/api/model/TAppUserLoginInfo.java b/ruoyi-api/ruoyi-api-system/src/main/java/com/ruoyi/system/api/model/TAppUserLoginInfo.java
new file mode 100644
index 0000000..dad1f6e
--- /dev/null
+++ b/ruoyi-api/ruoyi-api-system/src/main/java/com/ruoyi/system/api/model/TAppUserLoginInfo.java
@@ -0,0 +1,79 @@
+package com.ruoyi.system.api.model;
+
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.ruoyi.common.core.web.domain.BasePojo;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.time.LocalDateTime;
+
+/**
+ * <p>
+ * 
+ * </p>
+ *
+ * @author luodangjia
+ * @since 2024-08-06
+ */
+@Data
+public class TAppUserLoginInfo extends BasePojo {
+
+    private static final long serialVersionUID = 1L;
+    private Long id;
+
+    @ApiModelProperty(value = "用户")
+    private String name;
+
+    @ApiModelProperty(value = "手机号")
+    private String phone;
+
+    @ApiModelProperty(value = "头像")
+    private String avatar;
+
+    @ApiModelProperty(value = "会员id")
+    private Integer vipId;
+
+    @ApiModelProperty(value = "会员到期时间")
+    private LocalDateTime vipEndTime;
+
+    @ApiModelProperty(value = "单位id")
+    private Integer companyId;
+
+    @ApiModelProperty(value = "身份证号")
+    private String idCard;
+
+    @ApiModelProperty(value = "认证状态(0=否,1=是)")
+    private Integer authStatus;
+
+    @ApiModelProperty(value = "微信openid")
+    private String wxOpenid;
+
+    @ApiModelProperty(value = "支付宝openid")
+    private String aliOpenid;
+
+    @ApiModelProperty(value = "积分")
+    private Integer points;
+
+    @ApiModelProperty(value = "省名称")
+    private String province;
+
+    @ApiModelProperty(value = "省区划代码")
+    private String provinceCode;
+
+    @ApiModelProperty(value = "市名称")
+    private String city;
+
+    @ApiModelProperty(value = "市区划代码")
+    private String cityCode;
+
+    @ApiModelProperty(value = "状态(1=正常,2=冻结,3=注销)")
+    private Integer status;
+
+    @ApiModelProperty(value = "最后一次登录时间")
+    private LocalDateTime lastLoginTime;
+
+}
diff --git a/ruoyi-auth/src/main/java/com/ruoyi/auth/controller/TokenController.java b/ruoyi-auth/src/main/java/com/ruoyi/auth/controller/TokenController.java
index 41c1531..8a06c5d 100644
--- a/ruoyi-auth/src/main/java/com/ruoyi/auth/controller/TokenController.java
+++ b/ruoyi-auth/src/main/java/com/ruoyi/auth/controller/TokenController.java
@@ -3,15 +3,16 @@
 import javax.annotation.Resource;
 import javax.servlet.http.HttpServletRequest;
 
+import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.ruoyi.system.api.domain.SysRole;
 import com.ruoyi.system.api.domain.SysUser;
 import com.ruoyi.system.api.feignClient.SysUserClient;
+import io.swagger.annotations.ApiOperation;
+import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.util.CollectionUtils;
-import org.springframework.web.bind.annotation.DeleteMapping;
-import org.springframework.web.bind.annotation.PostMapping;
-import org.springframework.web.bind.annotation.RequestBody;
-import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.bind.annotation.*;
 import com.ruoyi.auth.form.LoginBody;
 import com.ruoyi.auth.form.RegisterBody;
 import com.ruoyi.auth.service.SysLoginService;
@@ -30,6 +31,7 @@
  * 
  * @author ruoyi
  */
+@Slf4j
 @RestController
 public class TokenController
 {
@@ -67,10 +69,6 @@
         userClient.updateSysUser(sysUser);
         return R.ok(map);
     }
-    
-    
-    
-    
 
     @DeleteMapping("logout")
     public R<?> logout(HttpServletRequest request) {
@@ -86,6 +84,17 @@
         return R.ok();
     }
 
+    @DeleteMapping("logoutApplet")
+    public R<?> logoutApplet(HttpServletRequest request) {
+        String token = SecurityUtils.getToken(request);
+        if (StringUtils.isNotEmpty(token))
+        {
+            // 删除用户缓存记录
+            AuthUtil.logoutByToken(token);
+        }
+        return R.ok();
+    }
+
     @PostMapping("refresh")
     public R<?> refresh(HttpServletRequest request)
     {
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/constant/CacheConstants.java b/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/constant/CacheConstants.java
index 1d2510e..0748dd4 100644
--- a/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/constant/CacheConstants.java
+++ b/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/constant/CacheConstants.java
@@ -11,6 +11,7 @@
      * 缓存有效期,默认720(分钟)
      */
     public final static long EXPIRATION = 720;
+    public final static long EXPIRATION_APPLET = 7*24*60*60;
 
     /**
      * 缓存刷新时间,默认120(分钟)
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/exception/user/UserAppletException.java b/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/exception/user/UserAppletException.java
new file mode 100644
index 0000000..b8da2e9
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/exception/user/UserAppletException.java
@@ -0,0 +1,48 @@
+package com.ruoyi.common.core.exception.user;
+
+/**
+ * 学生端登录异常信息
+ *
+ * @author HJL
+ * @version 1.0
+ * @since 2024-05-24 11:35
+ */
+public class UserAppletException extends RuntimeException {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 错误提示
+     */
+    private String message;
+
+    /**
+     * 错误码
+     */
+    private int code;
+
+    public UserAppletException() {
+    }
+
+    public UserAppletException(String message, Integer code) {
+        this.message = message;
+        this.code = code;
+    }
+
+    @Override
+    public String getMessage() {
+        return message;
+    }
+
+    public void setMessage(String message) {
+        this.message = message;
+    }
+
+    public int getCode() {
+        return code;
+    }
+
+    public void setCode(int code) {
+        this.code = code;
+    }
+}
diff --git a/ruoyi-common/ruoyi-common-security/src/main/java/com/ruoyi/common/security/handler/GlobalExceptionHandler.java b/ruoyi-common/ruoyi-common-security/src/main/java/com/ruoyi/common/security/handler/GlobalExceptionHandler.java
index a9cb1b7..262a05f 100644
--- a/ruoyi-common/ruoyi-common-security/src/main/java/com/ruoyi/common/security/handler/GlobalExceptionHandler.java
+++ b/ruoyi-common/ruoyi-common-security/src/main/java/com/ruoyi/common/security/handler/GlobalExceptionHandler.java
@@ -3,6 +3,8 @@
 import javax.naming.SizeLimitExceededException;
 import javax.servlet.http.HttpServletRequest;
 
+import com.ruoyi.common.core.domain.R;
+import com.ruoyi.common.core.exception.user.UserAppletException;
 import org.apache.commons.fileupload.FileUploadBase;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -151,4 +153,12 @@
         log.error("上传文件异常 => : {}", e.getMessage());
         return AjaxResult.error("文件识别大小超出限制,允许的大小在" + maxFileSize);
     }
+
+    /**
+     * 学生端单点登录-异常信息
+     */
+    @ExceptionHandler(UserAppletException.class)
+    public AjaxResult<String> studyLoginExceptionHandler(UserAppletException e) {
+        return AjaxResult.error(e.getCode(), e.getMessage());
+    }
 }
diff --git a/ruoyi-common/ruoyi-common-security/src/main/java/com/ruoyi/common/security/service/TokenService.java b/ruoyi-common/ruoyi-common-security/src/main/java/com/ruoyi/common/security/service/TokenService.java
index 2d7cae5..2735380 100644
--- a/ruoyi-common/ruoyi-common-security/src/main/java/com/ruoyi/common/security/service/TokenService.java
+++ b/ruoyi-common/ruoyi-common-security/src/main/java/com/ruoyi/common/security/service/TokenService.java
@@ -5,6 +5,7 @@
 import java.util.concurrent.TimeUnit;
 import javax.servlet.http.HttpServletRequest;
 
+import com.ruoyi.common.core.exception.user.UserAppletException;
 import com.ruoyi.system.api.model.LoginUserApplet;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
@@ -35,6 +36,7 @@
     protected static final long MILLIS_MINUTE = 60 * MILLIS_SECOND;
 
     private final static long expireTime = CacheConstants.EXPIRATION;
+    private final static long expireAppletTime = CacheConstants.EXPIRATION_APPLET;
 
     private final static String ACCESS_TOKEN = CacheConstants.LOGIN_TOKEN_KEY;
 
@@ -71,7 +73,7 @@
      */
     public Map<String, Object> createTokenApplet(LoginUserApplet loginUser) {
         String token = IdUtils.fastUUID();
-        Integer userId = loginUser.getUserid();
+        Long userId = loginUser.getUserId();
         String name = loginUser.getName();
         loginUser.setToken(token);
         loginUser.setIpaddr(IpUtils.getIpAddr());
@@ -84,11 +86,15 @@
         // 接口返回信息
         Map<String, Object> rspMap = new HashMap<String, Object>();
         rspMap.put("access_token", JwtUtils.createToken(claimsMap));
-        rspMap.put("expires_in", expireTime);
+        rspMap.put("expires_in", expireAppletTime);
         return rspMap;
     }
     public LoginUserApplet getLoginUserApplet() {
-        return getLoginUserAppletToken(ServletUtils.getRequest());
+        LoginUserApplet loginUserAppletToken = getLoginUserAppletToken(ServletUtils.getRequest());
+        if (loginUserAppletToken == null){
+            throw new UserAppletException("登录失效,请重新登录!", 401);
+        }
+        return loginUserAppletToken;
     }
     public LoginUserApplet getLoginUserAppletToken(HttpServletRequest request) {
         // 获取请求携带的令牌
@@ -104,8 +110,8 @@
         LoginUserApplet user = null;
         try {
             if (StringUtils.isNotEmpty(token)) {
-                String userkey = JwtUtils.getUserKeyApplet(token);
-                user = redisService.getCacheObject(getTokenKey(userkey));
+                String userKey = JwtUtils.getUserKeyApplet(token);
+                user = redisService.getCacheObject(getTokenKey(userKey));
                 return user;
             }
         } catch (Exception e) {
diff --git a/ruoyi-service/ruoyi-account/pom.xml b/ruoyi-service/ruoyi-account/pom.xml
index 435d4bc..0390101 100644
--- a/ruoyi-service/ruoyi-account/pom.xml
+++ b/ruoyi-service/ruoyi-account/pom.xml
@@ -137,6 +137,12 @@
             <scope>compile</scope>
         </dependency>
 
+        <dependency>
+            <groupId>com.alipay.sdk</groupId>
+            <artifactId>alipay-sdk-java</artifactId>
+            <version>4.38.10.ALL</version>
+        </dependency>
+
     </dependencies>
 
     <build>
diff --git a/ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/ali/Constant/AliConstant.java b/ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/ali/Constant/AliConstant.java
new file mode 100644
index 0000000..8cb6198
--- /dev/null
+++ b/ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/ali/Constant/AliConstant.java
@@ -0,0 +1,10 @@
+package com.ruoyi.account.ali.Constant;
+
+public class AliConstant {
+
+    /**
+     * 支付宝配置
+     */
+    public static final String GRANT_TYPE = "authorization_code";
+
+}
diff --git a/ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/ali/model/AliProperties.java b/ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/ali/model/AliProperties.java
new file mode 100644
index 0000000..679e7dc
--- /dev/null
+++ b/ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/ali/model/AliProperties.java
@@ -0,0 +1,70 @@
+package com.ruoyi.account.ali.model;
+
+import lombok.ToString;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+
+/**
+ * @author xiaochen
+ * @ClassName ALiProperties
+ * @Description
+ * @date 2024-08-14 13:55
+ */
+@ToString
+@Component
+@ConfigurationProperties(prefix = "ali.conf")
+public class AliProperties {
+
+
+    /**
+     * 商户私钥,您的PKCS8格式RSA2私钥
+     */
+    private String privateKey;
+    /**
+     * 支付宝公钥,查看地址:https://openhome.alipay.com/platform/keyManage.htm 对应APPID下的支付宝公钥。
+     */
+    private String alipayPublicKey;
+    /**
+     * 应用ID,您的APPID。
+     */
+    private String appId;
+
+    /**
+     * HTTP(S) 连接超时时间,单位毫秒
+     *
+     */
+    public int getHttpConnectTimeoutMs() {
+        return 6 * 1000;
+    }
+
+    /**
+     * HTTP(S) 读数据超时时间,单位毫秒
+     */
+    public int getHttpReadTimeoutMs() {
+        return 8 * 1000;
+    }
+
+    public String getPrivateKey() {
+        return privateKey;
+    }
+
+    public void setPrivateKey(String privateKey) {
+        this.privateKey = privateKey;
+    }
+
+    public String getAlipayPublicKey() {
+        return alipayPublicKey;
+    }
+
+    public void setAlipayPublicKey(String alipayPublicKey) {
+        this.alipayPublicKey = alipayPublicKey;
+    }
+
+    public String getAppId() {
+        return appId;
+    }
+
+    public void setAppId(String appId) {
+        this.appId = appId;
+    }
+}
diff --git a/ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/ali/tools/AliAppletTools.java b/ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/ali/tools/AliAppletTools.java
new file mode 100644
index 0000000..52d54d7
--- /dev/null
+++ b/ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/ali/tools/AliAppletTools.java
@@ -0,0 +1,37 @@
+package com.ruoyi.account.ali.tools;
+
+import com.alipay.api.AlipayConfig;
+import com.ruoyi.account.ali.model.AliProperties;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * @author xiaochen
+ * @ClassName WxAppletTools
+ * @Description
+ * @date 2024-8-04 13:55
+ */
+@Slf4j
+public class AliAppletTools {
+
+    private static final String SERVER_URL = "https://openapi.alipay.com/gateway.do";
+
+    private AliProperties aliProperties;
+
+    public AliAppletTools(AliProperties aliProperties) {
+        this.aliProperties = aliProperties;
+    }
+
+    /**
+     * 初始化支付宝配置
+     * @return
+     */
+    public AlipayConfig getAlipayConfig() {
+        AlipayConfig alipayConfig = new AlipayConfig();
+        alipayConfig.setServerUrl(SERVER_URL);
+        alipayConfig.setAppId(aliProperties.getAppId());
+        alipayConfig.setPrivateKey(aliProperties.getPrivateKey());
+        alipayConfig.setAlipayPublicKey(aliProperties.getAlipayPublicKey());
+        return alipayConfig;
+    }
+
+}
diff --git a/ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/controller/AliLoginController.java b/ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/controller/AliLoginController.java
new file mode 100644
index 0000000..b574502
--- /dev/null
+++ b/ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/controller/AliLoginController.java
@@ -0,0 +1,91 @@
+package com.ruoyi.account.controller;
+
+import com.alipay.api.AlipayApiException;
+import com.alipay.api.AlipayClient;
+import com.alipay.api.AlipayConfig;
+import com.alipay.api.DefaultAlipayClient;
+import com.alipay.api.diagnosis.DiagnosisUtils;
+import com.alipay.api.request.AlipaySystemOauthTokenRequest;
+import com.alipay.api.response.AlipaySystemOauthTokenResponse;
+import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.ruoyi.account.ali.Constant.AliConstant;
+import com.ruoyi.account.ali.model.AliProperties;
+import com.ruoyi.account.ali.tools.AliAppletTools;
+import com.ruoyi.account.api.model.TAppUser;
+import com.ruoyi.account.service.TAppUserService;
+import com.ruoyi.common.core.exception.ServiceException;
+import com.ruoyi.common.core.web.domain.AjaxResult;
+import com.ruoyi.common.security.service.TokenService;
+import com.ruoyi.system.api.model.LoginUserApplet;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiParam;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * <p>
+ *  支付宝小程序登录 前端控制器
+ * </p>
+ *
+ * @author xiaochen
+ * @since 2024-08-06
+ */
+@Slf4j
+@RestController
+@RequestMapping("/aliLogin")
+public class AliLoginController {
+
+    @Autowired
+    private AliProperties aliProperties;
+    @Autowired
+    private TAppUserService appUserService;
+    @Autowired
+    private TokenService tokenService;
+    @ApiOperation(value = "通过code获得openid",tags = {"支付宝小程序登录"})
+    @GetMapping("/openIdByJsCode")
+    public AjaxResult<Map<String, Object>> openIdByJsCode(@RequestParam(name = "code")@ApiParam(value = "code", required = true) String code) throws AlipayApiException {
+        log.info("<<<<<<<<换取openid开始<<<<<<<<:{}", code);
+        // 初始化SDK
+        AlipayClient alipayClient = new DefaultAlipayClient(new AliAppletTools(aliProperties).getAlipayConfig());
+        // 构造请求参数以调用接口
+        AlipaySystemOauthTokenRequest request = new AlipaySystemOauthTokenRequest();
+        // 设置授权码
+        request.setCode(code);
+        // 设置授权方式
+        request.setGrantType(AliConstant.GRANT_TYPE);
+        AlipaySystemOauthTokenResponse response = alipayClient.execute(request);
+        TAppUser appUser = null;
+        if (response.isSuccess()) {
+            String openId = response.getOpenId();
+            appUser = appUserService.getOne(Wrappers.lambdaQuery(TAppUser.class).eq(TAppUser::getAliOpenid, openId).last("limit 1"));
+            if (Objects.isNull(appUser)) {
+                appUser = new TAppUser();
+                appUser.setWxOpenid(openId);
+                appUserService.save(appUser);
+            }
+            log.info("支付宝小程序登录调用成功");
+        } else {
+             String diagnosisUrl = DiagnosisUtils.getDiagnosisUrl(response);
+             log.warn("诊断结果:{}",diagnosisUrl);
+             throw new ServiceException("支付宝小程序登录失败");
+        }
+        LoginUserApplet loginUserApplet = new LoginUserApplet();
+        if(ObjectUtils.isNotNull(appUser)){
+            loginUserApplet.setUserId(appUser.getId());
+        }
+        HashMap<String, Object> tokenInfos = new HashMap<>();
+        tokenInfos.put("token",tokenService.createTokenApplet(loginUserApplet));
+        tokenInfos.put("info",loginUserApplet);
+        return AjaxResult.ok(tokenInfos);
+    }
+
+}
diff --git a/ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/controller/TAppCouponController.java b/ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/controller/TAppCouponController.java
index a85603f..2bf8d35 100644
--- a/ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/controller/TAppCouponController.java
+++ b/ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/controller/TAppCouponController.java
@@ -16,6 +16,7 @@
 import com.ruoyi.common.core.web.domain.AjaxResult;
 import com.ruoyi.common.core.web.page.BasePage;
 import com.ruoyi.common.core.web.page.PageInfo;
+import com.ruoyi.common.security.service.TokenService;
 import com.ruoyi.other.api.domain.TCoupon;
 import com.ruoyi.other.api.feignClient.OtherClient;
 import io.swagger.annotations.ApiModelProperty;
@@ -48,6 +49,8 @@
 
     @Autowired
     private TAppUserCarService appUserCarService;
+    @Autowired
+    private TokenService tokenService;
     /**
      * 查询用户可用优惠券数量
      * @param dto
@@ -68,7 +71,7 @@
     public AjaxResult<List<TAppUserCar>> carList() {
         // todo 用户id
         List<TAppUserCar> appUserId = appUserCarService.list(new QueryWrapper<TAppUserCar>()
-                .eq("app_user_id", 11));
+                .eq("app_user_id",tokenService.getLoginUserApplet().getUserid() ));
         return AjaxResult.ok(appUserId);
     }
 
diff --git a/ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/controller/TAppUserController.java b/ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/controller/TAppUserController.java
index f8237ff..217799e 100644
--- a/ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/controller/TAppUserController.java
+++ b/ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/controller/TAppUserController.java
@@ -3,18 +3,25 @@
 
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.ruoyi.account.api.dto.*;
 import com.ruoyi.account.api.model.*;
 import com.ruoyi.account.api.vo.CouponListVOVO;
 import com.ruoyi.account.service.*;
+import com.ruoyi.account.wx.body.resp.Code2SessionRespBody;
+import com.ruoyi.account.wx.body.resq.Code2SessionResqBody;
+import com.ruoyi.account.wx.model.WeixinProperties;
+import com.ruoyi.account.wx.tools.WxAppletTools;
 import com.ruoyi.common.core.domain.R;
 import com.ruoyi.common.core.utils.bean.BeanUtils;
 import com.ruoyi.common.core.web.domain.AjaxResult;
 import com.ruoyi.common.core.web.domain.BasePojo;
 import com.ruoyi.common.core.web.page.PageInfo;
+import com.ruoyi.common.redis.service.RedisService;
 import com.ruoyi.common.security.annotation.RequiresPermissions;
+import com.ruoyi.common.security.service.TokenService;
 import com.ruoyi.order.api.feignClient.ChargingOrderClient;
 import com.ruoyi.order.api.feignClient.ExchangeOrderClient;
 import com.ruoyi.order.api.model.TChargingOrder;
@@ -22,18 +29,20 @@
 import com.ruoyi.other.api.domain.TCompany;
 import com.ruoyi.other.api.domain.TUserTag;
 import com.ruoyi.other.api.feignClient.OtherClient;
+import com.ruoyi.system.api.domain.SysRole;
+import com.ruoyi.system.api.model.LoginUserApplet;
 import io.swagger.annotations.ApiImplicitParam;
 import io.swagger.annotations.ApiImplicitParams;
 import io.swagger.annotations.ApiOperation;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.*;
+import org.springframework.web.client.RestTemplate;
 
 import javax.annotation.Resource;
 import java.time.Duration;
 import java.time.LocalDateTime;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Map;
+import java.util.*;
 import java.util.stream.Collectors;
 
 /**
@@ -44,6 +53,7 @@
  * @author luodangjia
  * @since 2024-08-06
  */
+@Slf4j
 @RestController
 @RequestMapping("/t-app-user")
 public class TAppUserController {
@@ -68,8 +78,6 @@
 
     @Resource
     private ExchangeOrderClient exchangeOrderClient;
-
-
 
     @ApiOperation(value = "管理后台-根据手机号查询用户ids", tags = {"管理后台-活动费用统计"})
     @PostMapping(value = "/user/getUserIdsByPhone")
diff --git a/ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/controller/WxLoginController.java b/ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/controller/WxLoginController.java
new file mode 100644
index 0000000..e3444f7
--- /dev/null
+++ b/ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/controller/WxLoginController.java
@@ -0,0 +1,67 @@
+package com.ruoyi.account.controller;
+
+import com.alibaba.fastjson.JSONObject;
+import com.baomidou.mybatisplus.core.conditions.Wrapper;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.ruoyi.account.api.model.TAppUser;
+import com.ruoyi.account.service.TAppUserService;
+import com.ruoyi.account.wx.body.resp.Code2SessionRespBody;
+import com.ruoyi.account.wx.body.resq.Code2SessionResqBody;
+import com.ruoyi.account.wx.model.WeixinProperties;
+import com.ruoyi.account.wx.pojo.AppletUserDecodeData;
+import com.ruoyi.account.wx.pojo.AppletUserEncrypteData;
+import com.ruoyi.account.wx.tools.WxAppletTools;
+import com.ruoyi.account.wx.tools.WxUtils;
+import com.ruoyi.common.core.exception.ServiceException;
+import com.ruoyi.common.core.web.domain.AjaxResult;
+import com.ruoyi.common.redis.service.RedisService;
+import com.ruoyi.common.security.service.TokenService;
+import com.ruoyi.system.api.model.LoginUserApplet;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiParam;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.client.RestTemplate;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+/**
+ * <p>
+ *  微信小程序登录 前端控制器
+ * </p>
+ *
+ * @author xiaochen
+ * @since 2024-08-06
+ */
+@Slf4j
+@RestController
+@RequestMapping("/wxLogin")
+public class WxLoginController {
+    @Autowired
+    private TAppUserService appUserService;
+    @Autowired
+    private WeixinProperties wxConfig;
+    @Autowired
+    private RestTemplate wxRestTemplate;
+    @ApiOperation(value = "通过code获得openid,获取用户信息",tags = {"微信小程序登录"})
+    @PostMapping("/openIdByJsCode")
+    public AjaxResult<Map<String, Object>> openIdByJsCode(@RequestBody AppletUserEncrypteData data) {
+        log.info("<<<<<<<<换取openid开始<<<<<<<<:{}", data.getCode());
+        WxAppletTools appletTools = new WxAppletTools(wxRestTemplate, wxConfig);
+        Code2SessionRespBody body = appletTools.getOpenIdByJscode2session(new Code2SessionResqBody().build(data.getCode()));
+        String openid = body.getOpenid();
+        String sessionKey = body.getSessionKey();
+        // 用户信息解密 数据验签
+        if (StringUtils.isNotBlank(data.getSignature())) {
+            WxUtils.verifySignature(data.getRawData(), sessionKey, data.getSignature());
+        }
+        AppletUserDecodeData appletUserDecodeData = WxUtils.encryptedData(data.getEncryptedData(), sessionKey,  data.getIv());
+        appletUserDecodeData.setOpenId(openid);
+        return AjaxResult.ok(appUserService.login(appletUserDecodeData));
+    }
+}
diff --git a/ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/service/TAppUserService.java b/ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/service/TAppUserService.java
index 600895f..11fec43 100644
--- a/ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/service/TAppUserService.java
+++ b/ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/service/TAppUserService.java
@@ -2,6 +2,9 @@
 
 import com.baomidou.mybatisplus.extension.service.IService;
 import com.ruoyi.account.api.model.TAppUser;
+import com.ruoyi.account.wx.pojo.AppletUserDecodeData;
+
+import java.util.Map;
 
 /**
  * <p>
@@ -13,4 +16,11 @@
  */
 public interface TAppUserService extends IService<TAppUser> {
 
+    /**
+     * 微信小程序登录用户封装
+     * @param appletUserDecodeData
+     * @return
+     */
+    Map<String, Object> login(AppletUserDecodeData appletUserDecodeData);
+
 }
diff --git a/ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/service/impl/TAppUserServiceImpl.java b/ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/service/impl/TAppUserServiceImpl.java
index 3e3b665..912a5e0 100644
--- a/ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/service/impl/TAppUserServiceImpl.java
+++ b/ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/service/impl/TAppUserServiceImpl.java
@@ -1,10 +1,23 @@
 package com.ruoyi.account.service.impl;
 
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.ruoyi.account.api.model.TAppUser;
 import com.ruoyi.account.mapper.TAppUserMapper;
 import com.ruoyi.account.service.TAppUserService;
+import com.ruoyi.account.wx.model.WeixinProperties;
+import com.ruoyi.account.wx.pojo.AppletUserDecodeData;
+import com.ruoyi.common.security.service.TokenService;
+import com.ruoyi.system.api.model.LoginUserApplet;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
+import org.springframework.web.client.RestTemplate;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
 
 /**
  * <p>
@@ -16,5 +29,48 @@
  */
 @Service
 public class TAppUserServiceImpl extends ServiceImpl<TAppUserMapper, TAppUser> implements TAppUserService {
-
+    @Autowired
+    private TokenService tokenService;
+    @Override
+    public Map<String, Object> login(AppletUserDecodeData appletUserDecodeData) {
+        // 通过手机号查询用户,是否已存在手动导入用户
+        TAppUser appUser = this.getOne(Wrappers.lambdaQuery(TAppUser.class)
+                .eq(TAppUser::getPhone, appletUserDecodeData.getPhoneNumber())
+                .isNull(TAppUser::getWxOpenid)
+                .last("LIMIT 1"));
+        LambdaQueryWrapper<TAppUser> wrapper = Wrappers.lambdaQuery(TAppUser.class)
+                .eq(TAppUser::getWxOpenid, appletUserDecodeData.getOpenId());
+        if(Objects.isNull(appUser)){
+            // 先使用openId和当前手机号进行查询
+            wrapper.eq(TAppUser::getPhone, appletUserDecodeData.getPhoneNumber())
+                    .last("LIMIT 1");
+            appUser = this.getOne(wrapper);
+            if(Objects.isNull(appUser)){
+                appUser = new TAppUser();
+                appUser.setPhone(appletUserDecodeData.getPhoneNumber());
+            }
+        }else {
+            wrapper.last("LIMIT 1");
+            // 删除小程序原有授权用户
+            this.remove(wrapper);
+        }
+        appUser.setAvatar(appletUserDecodeData.getAvatarUrl());
+        appUser.setCity(appletUserDecodeData.getCity());
+        appUser.setName(appletUserDecodeData.getNickName());
+        appUser.setProvince(appletUserDecodeData.getProvince());
+        appUser.setWxOpenid(appletUserDecodeData.getOpenId());
+        this.saveOrUpdate(appUser);
+        LoginUserApplet loginUserApplet = new LoginUserApplet();
+        if(ObjectUtils.isNotNull(appUser)){
+            loginUserApplet.setUserId(appUser.getId());
+            loginUserApplet.setName(appUser.getName());
+            loginUserApplet.setPhone(appUser.getPhone());
+            loginUserApplet.setAvatar(appUser.getAvatar());
+            loginUserApplet.setAddress(appUser.getProvince()+appUser.getCity());
+        }
+        Map<String, Object> tokenInfos = new HashMap<>();
+        tokenInfos.put("token",tokenService.createTokenApplet(loginUserApplet));
+        tokenInfos.put("info",loginUserApplet);
+        return tokenInfos;
+    }
 }
diff --git a/ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/wx/body/resp/AccessTokenRespBody.java b/ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/wx/body/resp/AccessTokenRespBody.java
new file mode 100644
index 0000000..abc6b3c
--- /dev/null
+++ b/ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/wx/body/resp/AccessTokenRespBody.java
@@ -0,0 +1,28 @@
+package com.ruoyi.account.wx.body.resp;
+
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * AccessToken 全局唯一
+ *
+ * @author xiaochen
+ */
+@Data
+public class AccessTokenRespBody extends RespBody implements Serializable {
+
+    /**
+     * 获取到的凭证
+     */
+    @JsonProperty("access_token")
+    private String accessToken;
+    /**
+     * 凭证有效时间,单位:秒
+     */
+    @JsonProperty("expires_in")
+    private int expiresIn;
+
+}
diff --git a/ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/wx/body/resp/Code2SessionRespBody.java b/ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/wx/body/resp/Code2SessionRespBody.java
new file mode 100644
index 0000000..0dcd69a
--- /dev/null
+++ b/ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/wx/body/resp/Code2SessionRespBody.java
@@ -0,0 +1,29 @@
+package com.ruoyi.account.wx.body.resp;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+
+/**
+ * @author xiaochen
+ * @ClassName Code2SessionRespBody
+ * @Description
+ * @date 2021-07-28 12:35
+ */
+@Data
+public class Code2SessionRespBody extends RespBody {
+    /**
+     * 用户唯一标识
+     */
+    @JsonProperty("openid")
+    private String openid;
+    /**
+     * 会话密钥
+     */
+    @JsonProperty("session_key")
+    private String sessionKey;
+    /**
+     * 用户在开放平台的唯一标识符,若当前小程序已绑定到微信开放平台帐号下会返回,详见 UnionID 机制说明。
+     */
+    @JsonProperty("unionid")
+    private String unionid;
+}
diff --git a/ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/wx/body/resp/RespBody.java b/ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/wx/body/resp/RespBody.java
new file mode 100644
index 0000000..4056d18
--- /dev/null
+++ b/ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/wx/body/resp/RespBody.java
@@ -0,0 +1,19 @@
+package com.ruoyi.account.wx.body.resp;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+
+/**
+ * @author xiaochen
+ * @ClassName RespBody
+ * @Description
+ * @date 2021-07-28 11:44
+ */
+@Data
+public class RespBody {
+    @JsonProperty("errcode")
+    private Integer errorCode;
+
+    @JsonProperty("errmsg")
+    private String errorMsg;
+}
diff --git a/ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/wx/body/resq/Code2SessionResqBody.java b/ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/wx/body/resq/Code2SessionResqBody.java
new file mode 100644
index 0000000..193f47f
--- /dev/null
+++ b/ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/wx/body/resq/Code2SessionResqBody.java
@@ -0,0 +1,21 @@
+package com.ruoyi.account.wx.body.resq;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+
+/**
+ * @author xiaochen
+ * @ClassName Code2SessionResqBody
+ * @Description
+ * @date 2021-07-28 11:47
+ */
+@Data
+public class Code2SessionResqBody {
+    @JsonProperty("js_code")
+    private String jsCode;
+
+    public Code2SessionResqBody build(String jsCode) {
+        this.jsCode = jsCode;
+        return this;
+    }
+}
diff --git a/ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/wx/model/WeixinProperties.java b/ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/wx/model/WeixinProperties.java
new file mode 100644
index 0000000..f760c14
--- /dev/null
+++ b/ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/wx/model/WeixinProperties.java
@@ -0,0 +1,80 @@
+package com.ruoyi.account.wx.model;
+
+import lombok.ToString;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.boot.context.properties.NestedConfigurationProperty;
+import org.springframework.stereotype.Component;
+
+/**
+ * @author xiaochen
+ * @ClassName WeixinProperties
+ * @Description
+ * @date 2024-08-14 13:55
+ */
+@ToString
+@Component
+@ConfigurationProperties(prefix = "wx.conf")
+public class WeixinProperties {
+    /**
+     * 默认开启
+     */
+    private boolean enabled = true;
+    /**
+     * 获取 App ID
+     *
+     * @return App ID
+     */
+    private String appId;
+    /**
+     * 获取 Mch ID
+     *
+     * @return Mch ID
+     */
+    private String mchId;
+
+    /**
+     * 获取 secret ID
+     *
+     * @return secret ID
+     */
+    private String secretId;
+
+    public String getSecretId() {
+        return secretId;
+    }
+
+    public void setSecretId(String secretId) {
+        this.secretId = secretId;
+    }
+
+    /**
+     * HTTP(S) 连接超时时间,单位毫秒
+     *
+     */
+    public int getHttpConnectTimeoutMs() {
+        return 6 * 1000;
+    }
+
+    /**
+     * HTTP(S) 读数据超时时间,单位毫秒
+     */
+    public int getHttpReadTimeoutMs() {
+        return 8 * 1000;
+    }
+
+    public String getAppId() {
+        return appId;
+    }
+
+    public void setAppId(String appId) {
+        this.appId = appId;
+    }
+
+    public String getMchId() {
+        return mchId;
+    }
+
+    public void setMchId(String mchId) {
+        this.mchId = mchId;
+    }
+}
diff --git a/ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/wx/pojo/AppletPhoneEncrypteData.java b/ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/wx/pojo/AppletPhoneEncrypteData.java
new file mode 100644
index 0000000..8e48fcc
--- /dev/null
+++ b/ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/wx/pojo/AppletPhoneEncrypteData.java
@@ -0,0 +1,19 @@
+package com.ruoyi.account.wx.pojo;
+
+import lombok.Data;
+
+/**
+ * @author xiaochen
+ * @ClassName AppletUserDecodeData
+ * @Description
+ * @date 2021-08-13 17:46
+ * 小程序加密数据体
+ *
+ */
+@Data
+public class AppletPhoneEncrypteData {
+    private String encryptedData;
+    private String openid;
+    private String unionid;
+    private String iv;
+}
diff --git a/ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/wx/pojo/AppletUserDecodeData.java b/ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/wx/pojo/AppletUserDecodeData.java
new file mode 100644
index 0000000..6040fe9
--- /dev/null
+++ b/ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/wx/pojo/AppletUserDecodeData.java
@@ -0,0 +1,52 @@
+package com.ruoyi.account.wx.pojo;
+
+import lombok.Data;
+
+/**
+ * @author xiaochen
+ * @ClassName AppletUserDecodeData
+ * @Description
+ * 用户主体信息部分
+ * {
+ *     "openId": "OPENID",
+ *     "nickName": "NICKNAME",
+ *     "gender": GENDER,
+ *     "city": "CITY",
+ *     "province": "PROVINCE",
+ *     "country": "COUNTRY",
+ *     "avatarUrl": "AVATARURL",
+ *     "unionId": "UNIONID",
+ *     "watermark":
+ *     {
+ *         "appid":"APPID",
+ *         "timestamp":TIMESTAMP
+ *     }
+ * }
+ * 电话部分
+ * {
+ *     "phoneNumber": "13580006666",
+ *     "purePhoneNumber": "13580006666",
+ *     "countryCode": "86",
+ *     "watermark":
+ *     {
+ *         "appid":"APPID",
+ *         "timestamp": TIMESTAMP
+ *     }
+ * }
+ *
+ */
+@Data
+public class AppletUserDecodeData {
+    private String openId;
+    private String unionId;
+    private String nickName;
+    private int gender;
+    private String city;
+    private String province;
+    private String country;
+    private String avatarUrl;
+    private Watermark watermark;
+    private String phoneNumber;
+    private String purePhoneNumber;
+    private String countryCode;
+}
\ No newline at end of file
diff --git a/ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/wx/pojo/AppletUserEncrypteData.java b/ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/wx/pojo/AppletUserEncrypteData.java
new file mode 100644
index 0000000..45884a8
--- /dev/null
+++ b/ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/wx/pojo/AppletUserEncrypteData.java
@@ -0,0 +1,17 @@
+package com.ruoyi.account.wx.pojo;
+
+import lombok.Data;
+
+/**
+ * @author xiaochen
+ * @ClassName AppletUserDecodeData
+ * @Description
+ * 小程序加密数据体
+ *
+ */
+@Data
+public class AppletUserEncrypteData extends AppletPhoneEncrypteData {
+    private String rawData;
+    private String signature;
+    private String code;
+}
diff --git a/ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/wx/pojo/Watermark.java b/ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/wx/pojo/Watermark.java
new file mode 100644
index 0000000..58df6cc
--- /dev/null
+++ b/ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/wx/pojo/Watermark.java
@@ -0,0 +1,9 @@
+package com.ruoyi.account.wx.pojo;
+
+import lombok.Data;
+
+@Data
+public class Watermark {
+    private String appid;
+    private String timestamp;
+}
\ No newline at end of file
diff --git a/ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/wx/tools/JsonUtils.java b/ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/wx/tools/JsonUtils.java
new file mode 100644
index 0000000..0b9fe63
--- /dev/null
+++ b/ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/wx/tools/JsonUtils.java
@@ -0,0 +1,110 @@
+package com.ruoyi.account.wx.tools;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
+import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
+import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
+import com.hotel.config.JacksonConfig;
+import com.hotel.exception.ServiceException;
+import lombok.extern.slf4j.Slf4j;
+
+import java.io.IOException;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+
+/**
+ * Json转换工具类
+ * 参考:https://blog.csdn.net/weixin_38413579/article/details/82562634
+ * @author madman
+ */
+@Slf4j
+public final class JsonUtils {
+
+    private static final ObjectMapper OM = new ObjectMapper();
+    private static final JavaTimeModule timeModule = new JavaTimeModule();
+
+    /**
+     * 转换LocalDateTime
+     */
+    static class LocalDateTimeSerializer extends JsonSerializer<LocalDateTime> {
+        @Override
+        public void serialize(LocalDateTime localDateTime, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
+            jsonGenerator.writeString(localDateTime.format(DateTimeFormatter.ofPattern(JacksonConfig.dateTimeFormat)));
+        }
+    }
+
+    /**
+     * 转换LocalDate
+     */
+    static class LocalDateSerializer extends JsonSerializer<LocalDate> {
+        @Override
+        public void serialize(LocalDate localDate, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
+            jsonGenerator.writeString(localDate.format(DateTimeFormatter.ofPattern(JacksonConfig.dateFormat)));
+        }
+    }
+
+    /**
+     * 设置 ObjectMapper
+     *
+     * @return
+     */
+    private static ObjectMapper getObjectMapper() {
+        // 序列化
+        timeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer());
+        timeModule.addSerializer(LocalDate.class, new LocalDateSerializer());
+        // 反序列化
+        timeModule.addDeserializer(LocalDateTime.class,
+                new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(JacksonConfig.dateTimeFormat)));
+        timeModule.addDeserializer(LocalDate.class,
+                new LocalDateDeserializer(DateTimeFormatter.ofPattern(JacksonConfig.dateFormat)));
+        // 允许对象忽略json中不存在的属性
+        OM.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
+        OM.registerModule(timeModule);
+        return OM;
+    }
+
+    /**
+     * 将对象序列化
+     */
+    public static <T> String toJsonString(T obj) {
+        try {
+            ObjectMapper om = getObjectMapper();
+            return om.writeValueAsString(obj);
+        } catch (JsonProcessingException e) {
+            log.error("转json字符串失败:{}", obj);
+            return null;
+        }
+    }
+
+    /**
+     * 反序列化对象字符串
+     */
+    public static <T> T parseObject(String json, Class<T> clazz) {
+        try {
+            ObjectMapper om = getObjectMapper();
+            return om.readValue(json, clazz);
+        } catch (JsonProcessingException e) {
+            throw new ServiceException("反序列化对象字符串失败");
+        }
+    }
+
+    /**
+     * 反序列化字符串成为对象
+     */
+    public static <T> T parseObject(String json, TypeReference<T> valueTypeRef) {
+        try {
+            ObjectMapper om = getObjectMapper();
+            return om.readValue(json, valueTypeRef);
+        } catch (JsonProcessingException e) {
+            throw new ServiceException("反序列化字符串成为对象失败");
+        }
+    }
+
+}
diff --git a/ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/wx/tools/SHA1.java b/ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/wx/tools/SHA1.java
new file mode 100644
index 0000000..47c0181
--- /dev/null
+++ b/ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/wx/tools/SHA1.java
@@ -0,0 +1,36 @@
+package com.ruoyi.account.wx.tools;
+
+import java.security.MessageDigest;
+
+public class SHA1 {
+
+
+    /**
+     * 用SHA1算法生成安全签名
+     *
+     * @param str
+     * @return
+     * @throws WxException
+     */
+    public static String getSHA1(String str) throws WxException {
+        try {
+            // SHA1签名生成
+            MessageDigest md = MessageDigest.getInstance("SHA-1");
+            md.update(str.getBytes());
+            byte[] digest = md.digest();
+            StringBuffer hexstr = new StringBuffer();
+            String shaHex;
+            for (int i = 0; i < digest.length; i++) {
+                shaHex = Integer.toHexString(digest[i] & 0xFF);
+                if (shaHex.length() < 2) {
+                    hexstr.append(0);
+                }
+                hexstr.append(shaHex);
+            }
+            return hexstr.toString();
+        } catch (Exception e) {
+            throw new WxException(WxException.ComputeSignatureError);
+        }
+    }
+
+}
diff --git a/ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/wx/tools/WebUtils.java b/ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/wx/tools/WebUtils.java
new file mode 100644
index 0000000..69966eb
--- /dev/null
+++ b/ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/wx/tools/WebUtils.java
@@ -0,0 +1,48 @@
+package com.ruoyi.account.wx.tools;
+
+import org.springframework.web.context.request.RequestContextHolder;
+import org.springframework.web.context.request.ServletRequestAttributes;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+
+/**
+ * @Author xiaochen
+ * @Date 2019/08/26 10:28 AM
+ * @Description
+ */
+public final class WebUtils {
+
+    private WebUtils() {
+    }
+
+    /**
+     * 当前请求
+     */
+    public static HttpServletRequest request() {
+        return contextHolder() == null ? null : contextHolder().getRequest();
+    }
+
+    /**
+     * 当前响应
+     */
+    public static HttpServletResponse response() {
+        return contextHolder() == null ? null : contextHolder().getResponse();
+    }
+
+    /**
+     * 当前session
+     */
+    public static HttpSession session() {
+        return request() == null ? null : request().getSession();
+    }
+
+    /**
+     * 当前ServletRequest
+     */
+    public static ServletRequestAttributes contextHolder() {
+        return (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
+    }
+
+}
diff --git a/ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/wx/tools/WxAppletTools.java b/ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/wx/tools/WxAppletTools.java
new file mode 100644
index 0000000..01feebf
--- /dev/null
+++ b/ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/wx/tools/WxAppletTools.java
@@ -0,0 +1,125 @@
+package com.ruoyi.account.wx.tools;
+
+import com.ruoyi.account.wx.body.resp.AccessTokenRespBody;
+import com.ruoyi.account.wx.body.resp.Code2SessionRespBody;
+import com.ruoyi.account.wx.body.resq.Code2SessionResqBody;
+import com.ruoyi.account.wx.model.WeixinProperties;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.util.StringUtils;
+import org.springframework.web.client.RestTemplate;
+
+import java.text.MessageFormat;
+
+/**
+ * @author xiaochen
+ * @ClassName WxAppletTools
+ * @Description
+ * @date 2024-8-04 13:55
+ */
+@Slf4j
+public class WxAppletTools {
+    private final static String ACCESSTOKEN_CACHE_KEY = "accessToken";
+    /**
+     * 请求参数
+     * 属性	类型	默认值	必填	说明
+     * appid	string		是	小程序 appId
+     * secret	string		是	小程序 appSecret
+     * js_code	string		是	登录时获取的 code
+     * grant_type	string		是	授权类型,此处只需填写 authorization_cod
+     * <p>
+     * 返回值:
+     * <p>
+     * 属性	类型	说明
+     * openid	string	用户唯一标识
+     * session_key	string	会话密钥
+     * unionid	string	用户在开放平台的唯一标识符,若当前小程序已绑定到微信开放平台帐号下会返回,详见 UnionID 机制说明。
+     * errcode	number	错误码
+     * errmsg	string	错误信息
+     */
+    private static final String JSCODE_2_SESSION_URL = "https://api.weixin.qq.com/sns/jscode2session?appid={0}&secret={1}&js_code={2}&grant_type=authorization_code";
+    /**
+     * 请求参数
+     * 属性	类型	默认值	必填	说明
+     * grant_type	string		是	填写 client_credential
+     * appid	string		是	小程序唯一凭证,即 AppID,可在「微信公众平台 - 设置 - 开发设置」页中获得。(需要已经成为开发者,且帐号没有异常状态)
+     * secret	string		是	小程序唯一凭证密钥,即 AppSecret,获取方式同 appid
+     * 返回值
+     * Object
+     * 返回的 JSON 数据包
+     * <p>
+     * 属性	类型	说明
+     * access_token	string	获取到的凭证
+     * expires_in	number	凭证有效时间,单位:秒。目前是7200秒之内的值。
+     * errcode	number	错误码
+     * errmsg	string	错误信息
+     */
+    public static String ACCESS_TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={0}&secret={1}";
+    private WeixinProperties wxConfig;
+    private RestTemplate wxRestTemplate;
+    private WxCacheTemplate<String> wxCacheTemplate;
+
+    public WxAppletTools(RestTemplate wxRestTemplate, WeixinProperties wxConfig, WxCaffineCache wxCacheTemplate) {
+        this.wxRestTemplate = wxRestTemplate;
+        this.wxCacheTemplate = wxCacheTemplate;
+        this.wxConfig = wxConfig;
+    }
+
+    public WxAppletTools(RestTemplate wxRestTemplate, WeixinProperties wxConfig) {
+        this.wxRestTemplate = wxRestTemplate;
+        this.wxConfig = wxConfig;
+    }
+
+    /**
+     * 自定义部分数据
+     *
+     * @param wxConfig
+     * @return
+     */
+    public WxAppletTools build(WeixinProperties wxConfig) {
+        this.wxConfig = wxConfig;
+        return this;
+    }
+
+    /**
+     * @param resqBody
+     * @return
+     */
+    public Code2SessionRespBody getOpenIdByJscode2session(Code2SessionResqBody resqBody) {
+        long start = System.currentTimeMillis();
+        String requestUrl = MessageFormat.format(JSCODE_2_SESSION_URL, wxConfig.getAppId(), wxConfig.getSecretId(), resqBody.getJsCode());
+        long end = System.currentTimeMillis();
+        log.info("code换取sessionKey时间:{}", (end - start));
+        String respBody = wxRestTemplate.getForEntity(requestUrl, String.class).getBody();
+        end = System.currentTimeMillis();
+        log.info("code换取sessionKey时间:{}", (end - start));
+        log.info("Jscode2session:{}", respBody);
+        Code2SessionRespBody code2SessionRespBody = WxJsonUtils.parseObject(respBody, Code2SessionRespBody.class);
+        // 判断有误异常
+        if (StringUtils.hasLength(code2SessionRespBody.getErrorMsg())) {
+            // 抛出错误
+            throw new WxException(code2SessionRespBody.getErrorCode() + ":" + code2SessionRespBody.getErrorMsg());
+        }
+        return code2SessionRespBody;
+    }
+
+    /**
+     * @return
+     */
+    public String getAccessToken(String version) {
+        String accessToken = wxCacheTemplate.getKey(ACCESSTOKEN_CACHE_KEY + version);
+        if (StringUtils.hasLength(accessToken)) {
+            return accessToken;
+        }
+        String requestUrl = MessageFormat.format(ACCESS_TOKEN_URL, wxConfig.getAppId(), wxConfig.getSecretId());
+        String respBody = wxRestTemplate.getForEntity(requestUrl, String.class).getBody();
+        AccessTokenRespBody accessTokenRespBody = WxJsonUtils.parseObject(respBody, AccessTokenRespBody.class);
+        // 判断有误异常
+        if (StringUtils.hasLength(accessTokenRespBody.getErrorMsg())) {
+            // 抛出错误
+            throw new WxException(accessTokenRespBody.getErrorCode() + ":" + accessTokenRespBody.getErrorMsg());
+        }
+        wxCacheTemplate.setKey(ACCESSTOKEN_CACHE_KEY + version, accessTokenRespBody.getAccessToken());
+        return accessTokenRespBody.getAccessToken();
+    }
+
+}
diff --git a/ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/wx/tools/WxCache.java b/ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/wx/tools/WxCache.java
new file mode 100644
index 0000000..a8b3560
--- /dev/null
+++ b/ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/wx/tools/WxCache.java
@@ -0,0 +1,117 @@
+package com.ruoyi.account.wx.tools;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * 缓存
+ *
+ * @author xiaochen
+ */
+class WxCache {
+    /**
+     * 缓存的初始化容量
+     */
+    private int initialCapacity = 50;
+    /**
+     * 缓存最大容量
+     */
+    private long maximumSize = 200L;
+    /**
+     * 缓存时长
+     */
+    private long duration = 7000L;
+    /**
+     * 时长单位,自动转换
+     * 支持:
+     * 时
+     * 分
+     * 秒
+     * 天
+     */
+    private TimeUnit timeunit = TimeUnit.SECONDS;
+
+    public int getInitialCapacity() {
+        return initialCapacity;
+    }
+
+    public void setInitialCapacity(int initialCapacity) {
+        this.initialCapacity = initialCapacity;
+    }
+
+    public long getMaximumSize() {
+        return maximumSize;
+    }
+
+    public void setMaximumSize(long maximumSize) {
+        this.maximumSize = maximumSize;
+    }
+
+
+    public long getDuration() {
+        return duration;
+    }
+
+    public void setDuration(long duration) {
+        this.duration = duration;
+    }
+
+    public TimeUnit getTimeunit() {
+        return timeunit;
+    }
+
+    public void setTimeunit(TimeUnit timeunit) {
+        this.timeunit = timeunit;
+    }
+
+    public static class Builder {
+        private int initialCapacity;
+        private long maximumSize;
+        private long duration;
+        private TimeUnit timeunit;
+
+        public Builder setInitialCapacity(int initialCapacity) {
+            this.initialCapacity = initialCapacity;
+            return this;
+        }
+
+        public Builder setMaximumSize(long maximumSize) {
+            this.maximumSize = maximumSize;
+            return this;
+        }
+
+        public Builder setDuration(long duration) {
+            this.duration = duration;
+            return this;
+        }
+
+        public Builder setTimeUnit(TimeUnit timeunit) {
+            this.timeunit = timeunit;
+            return this;
+        }
+
+        public WxCache build() {
+            return new WxCache(this);
+        }
+    }
+
+    public static Builder options() {
+        return new Builder();
+    }
+
+    private WxCache(Builder builder) {
+        this.initialCapacity = 0 == builder.initialCapacity ? this.initialCapacity : builder.initialCapacity;
+        this.maximumSize = 0L == builder.maximumSize ? this.maximumSize : builder.maximumSize;
+        this.duration = 0L == builder.duration ? this.duration : builder.duration;
+        this.timeunit = null == builder.timeunit ? this.timeunit : builder.timeunit;
+    }
+
+    @Override
+    public String toString() {
+        return "WxCache{" +
+                "initialCapacity=" + initialCapacity +
+                ", maximumSize=" + maximumSize +
+                ", duration=" + duration +
+                ", timeunit=" + timeunit +
+                '}';
+    }
+}
diff --git a/ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/wx/tools/WxCacheTemplate.java b/ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/wx/tools/WxCacheTemplate.java
new file mode 100644
index 0000000..fa43614
--- /dev/null
+++ b/ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/wx/tools/WxCacheTemplate.java
@@ -0,0 +1,34 @@
+package com.ruoyi.account.wx.tools;
+
+/**
+ * @author xiaochen
+ * @ClassName WxCacheTemplate
+ * @Description
+ * @date 2021-01-11 11:27
+ */
+public interface WxCacheTemplate<T> {
+    /**
+     * 保存key
+     *
+     * @param key
+     * @param value
+     * @return
+     */
+     boolean setKey(String key, T value);
+
+    /**
+     * 获取缓存
+     *
+     * @param key
+     * @return
+     */
+    T getKey(String key);
+
+    /**
+     * 删除
+     *
+     * @param key
+     * @return
+     */
+    boolean delKey(String key);
+}
diff --git a/ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/wx/tools/WxCaffineCache.java b/ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/wx/tools/WxCaffineCache.java
new file mode 100644
index 0000000..5c40a14
--- /dev/null
+++ b/ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/wx/tools/WxCaffineCache.java
@@ -0,0 +1,122 @@
+package com.ruoyi.account.wx.tools;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.util.StringUtils;
+
+import java.util.Objects;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @author xiaochen
+ * @ClassName AbstractCaffineCache
+ * @Description
+ * @date 2021-01-11 11:27
+ */
+@Slf4j
+class WxCaffineCache<T>  implements WxCacheTemplate<T> {
+    /**
+     * 缓存环境
+     */
+    private String env = "wx";
+
+    /**
+     * 本地缓存实例
+     */
+    private LoadingCache<String, Object> loadingCache;
+
+    /**
+     * 构造函数
+     *
+     */
+    public WxCaffineCache() {
+        WxCache cache = WxCache.options().setTimeUnit(TimeUnit.SECONDS).build();
+        // 构建本地缓存实例
+        this.loadingCache = caffineCacheManage(cache);
+    }
+
+
+    @Override
+    public boolean setKey(String key, T value) {
+        if (Objects.isNull(this.loadingCache)) {
+            return Boolean.FALSE;
+        }
+        if (StringUtils.hasLength(this.env)) {
+            this.loadingCache.put(this.env + ":" + key, value);
+        } else {
+            this.loadingCache.put(key, value);
+        }
+        return Boolean.TRUE;
+    }
+
+    @Override
+    public T getKey(String key) {
+        if (Objects.isNull(this.loadingCache)) {
+            return null;
+        }
+        try {
+            if (StringUtils.hasLength(this.env)) {
+                return (T) this.loadingCache.get(this.env + ":" + key);
+            } else {
+                return (T) this.loadingCache.get(key);
+            }
+        } catch (Exception e) {
+            return null;
+        }
+    }
+
+    @Override
+    public boolean delKey(String key) {
+        if (Objects.isNull(this.loadingCache)) {
+            return Boolean.FALSE;
+        }
+        if (StringUtils.hasLength(this.env)) {
+            this.loadingCache.invalidate(this.env + ":" + key);
+        } else {
+            this.loadingCache.invalidate(key);
+        }
+        return Boolean.TRUE;
+    }
+
+    /**
+     * 缓存管理
+     *
+     * @param cache
+     * @param <T>
+     * @return
+     */
+    private static <T> LoadingCache<String, T> caffineCacheManage(WxCache cache) {
+        log.info("初始化缓存的实体数据:{}", cache);
+        if (Objects.isNull(cache)) {
+            throw new NullPointerException("请实例化一个Cache对象!");
+        }
+        LoadingCache<String, T> localcache =
+                // 构建本地缓存,调用链的方式
+                // ,1000是设置缓存的初始化容量,maximumSize是设置缓存最大容量,当超过了最大容量,guava将使用LRU算法(最少使用算法),来移除缓存项
+                // expireAfterAccess(12,TimeUnit.HOURS)设置缓存有效期为12个小时
+                Caffeine.newBuilder().initialCapacity(cache.getInitialCapacity()).maximumSize(cache.getMaximumSize())
+                        // 设置写缓存后n秒钟过期
+                        // .expireAfterWrite(30, TimeUnit.SECONDS)
+                        .expireAfterWrite(cache.getDuration(), cache.getTimeunit())
+                        // 设置读写缓存后n秒钟过期,实际很少用到,类似于expireAfterWrite
+                        //.expireAfterAccess(googleCache.getDuration(), googleCache.getTimeunit())
+                        // 只阻塞当前数据加载线程,其他线程返回旧值
+                        //.refreshAfterWrite(10, TimeUnit.SECONDS)
+                        // 设置缓存的移除通知//用户手动移除EXPLICIT,
+                        // //用户手动替换REPLACED,//被垃圾回收COLLECTED,//超时过期EXPIRED,//SIZE由于缓存大小限制
+                        .removalListener(new RemovalListener<String, T>() {
+                            @Override
+                            public void onRemoval(String key, Object value, RemovalCause cause) {
+                                log.info(key + ":" + value + ":" + cause.name());
+                            }
+                        })
+                        // build里面要实现一个匿名抽象类
+                        .build(new CacheLoader<String, T>() {
+                            // 这个方法是默认的数据加载实现,get的时候,如果key没有对应的值,就调用这个方法进行加载。此处是没有默认值则返回null
+                            @Override
+                            public T load(String key) throws Exception {
+                                return null;
+                            }
+                        });
+        return localcache;
+    }
+}
diff --git a/ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/wx/tools/WxException.java b/ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/wx/tools/WxException.java
new file mode 100644
index 0000000..eb510a9
--- /dev/null
+++ b/ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/wx/tools/WxException.java
@@ -0,0 +1,55 @@
+package com.ruoyi.account.wx.tools;
+
+/**
+ * @author lihen
+ */
+public class WxException extends RuntimeException {
+
+    private final static int OK = 0;
+    private final static int ValidateSignatureError = -40001;
+    private final static int ParseXmlError = -40002;
+    public final static int ComputeSignatureError = -40003;
+    private final static int IllegalAesKey = -40004;
+    private final static int ValidateAppidError = -40005;
+    private final static int EncryptAESError = -40006;
+    private final static int DecryptAESError = -40007;
+    private final static int IllegalBuffer = -40008;
+
+    private int code;
+
+    private static String getMessage(int code) {
+        switch (code) {
+            case ValidateSignatureError:
+                return "签名验证错误";
+            case ParseXmlError:
+                return "xml解析失败";
+            case ComputeSignatureError:
+                return "sha加密生成签名失败";
+            case IllegalAesKey:
+                return "SymmetricKey非法";
+            case ValidateAppidError:
+                return "appid校验失败";
+            case EncryptAESError:
+                return "aes加密失败";
+            case DecryptAESError:
+                return "aes解密失败";
+            case IllegalBuffer:
+                return "解密后得到的buffer非法";
+            default:
+                return null;
+        }
+    }
+
+    public int getCode() {
+        return code;
+    }
+
+    WxException(int code) {
+        super(getMessage(code));
+        this.code = code;
+    }
+
+    public WxException(String message) {
+        super(message);
+    }
+}
diff --git a/ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/wx/tools/WxJsonUtils.java b/ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/wx/tools/WxJsonUtils.java
new file mode 100644
index 0000000..83b871d
--- /dev/null
+++ b/ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/wx/tools/WxJsonUtils.java
@@ -0,0 +1,109 @@
+package com.ruoyi.account.wx.tools;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
+import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
+import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
+import lombok.extern.slf4j.Slf4j;
+
+import java.io.IOException;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+
+/**
+ * Json转换工具类
+ * 参考:https://blog.csdn.net/weixin_38413579/article/details/82562634
+ * @author madman
+ */
+@Slf4j
+public final class WxJsonUtils {
+    public static final String dateFormat = "yyyy-MM-dd";
+    public static final String dateTimeFormat = "yyyy-MM-dd HH:mm:ss";
+    private static final ObjectMapper OM = new ObjectMapper();
+    private static final JavaTimeModule timeModule = new JavaTimeModule();
+
+    /**
+     * 转换LocalDateTime
+     */
+    static class LocalDateTimeSerializer extends JsonSerializer<LocalDateTime> {
+        @Override
+        public void serialize(LocalDateTime localDateTime, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
+            jsonGenerator.writeString(localDateTime.format(DateTimeFormatter.ofPattern(dateTimeFormat)));
+        }
+    }
+
+    /**
+     * 转换LocalDate
+     */
+    static class LocalDateSerializer extends JsonSerializer<LocalDate> {
+        @Override
+        public void serialize(LocalDate localDate, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
+            jsonGenerator.writeString(localDate.format(DateTimeFormatter.ofPattern(dateFormat)));
+        }
+    }
+
+    /**
+     * 设置 ObjectMapper
+     *
+     * @return
+     */
+    private static ObjectMapper getObjectMapper() {
+        // 序列化
+        timeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer());
+        timeModule.addSerializer(LocalDate.class, new LocalDateSerializer());
+        // 反序列化
+        timeModule.addDeserializer(LocalDateTime.class,
+                new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(dateTimeFormat)));
+        timeModule.addDeserializer(LocalDate.class,
+                new LocalDateDeserializer(DateTimeFormatter.ofPattern(dateFormat)));
+        // 允许对象忽略json中不存在的属性
+        OM.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
+        OM.registerModule(timeModule);
+        return OM;
+    }
+
+    /**
+     * 将对象序列化
+     */
+    public static <T> String toJsonString(T obj) {
+        try {
+            ObjectMapper om = getObjectMapper();
+            return om.writeValueAsString(obj);
+        } catch (JsonProcessingException e) {
+            log.error("转json字符串失败:{}", obj);
+            return null;
+        }
+    }
+
+    /**
+     * 反序列化对象字符串
+     */
+    public static <T> T parseObject(String json, Class<T> clazz) {
+        try {
+            ObjectMapper om = getObjectMapper();
+            return om.readValue(json, clazz);
+        } catch (JsonProcessingException e) {
+            throw new RuntimeException("反序列化对象字符串失败");
+        }
+    }
+
+    /**
+     * 反序列化字符串成为对象
+     */
+    public static <T> T parseObject(String json, TypeReference<T> valueTypeRef) {
+        try {
+            ObjectMapper om = getObjectMapper();
+            return om.readValue(json, valueTypeRef);
+        } catch (JsonProcessingException e) {
+            throw new RuntimeException("反序列化字符串成为对象失败");
+        }
+    }
+
+}
diff --git a/ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/wx/tools/WxUtils.java b/ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/wx/tools/WxUtils.java
new file mode 100644
index 0000000..47bd8c8
--- /dev/null
+++ b/ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/wx/tools/WxUtils.java
@@ -0,0 +1,175 @@
+package com.ruoyi.account.wx.tools;
+
+import com.ruoyi.account.wx.pojo.AppletUserDecodeData;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.codec.CharEncoding;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.util.encoders.Base64;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.crypto.Cipher;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+import javax.servlet.http.HttpServletRequest;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.security.AlgorithmParameters;
+import java.security.Security;
+import java.util.Arrays;
+
+/**
+ * @Description 获取用户信息工具类
+ * @Author xiaochen
+ * @Date 2021/8/12 15:45
+ */
+@Slf4j
+public class WxUtils {
+
+    /**
+     * 微信小程序API 用户数据的解密
+     *
+     * @param encryptedData
+     * @param sessionKey
+     * @param iv
+     * @return
+     */
+    public static AppletUserDecodeData encryptedData(String encryptedData, String sessionKey, String iv) {
+        // 被加密的数据
+        byte[] dataByte = Base64.decode(encryptedData);
+        // 加密秘钥
+        byte[] keyByte = Base64.decode(sessionKey);
+        // 偏移量
+        byte[] ivByte = Base64.decode(iv);
+        try {
+            // 如果密钥不足16位,那么就补足.  这个if 中的内容很重要
+            int base = 16;
+            if (keyByte.length % base != 0) {
+                int groups = keyByte.length / base + (keyByte.length % base != 0 ? 1 : 0);
+                byte[] temp = new byte[groups * base];
+                Arrays.fill(temp, (byte) 0);
+                System.arraycopy(keyByte, 0, temp, 0, keyByte.length);
+                keyByte = temp;
+            }
+            // 初始化
+            Security.addProvider(new BouncyCastleProvider());
+            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding", "BC");
+            SecretKeySpec spec = new SecretKeySpec(keyByte, "AES");
+            AlgorithmParameters parameters = AlgorithmParameters.getInstance("AES");
+            parameters.init(new IvParameterSpec(ivByte));
+            cipher.init(Cipher.DECRYPT_MODE, spec, parameters);
+            byte[] resultByte = cipher.doFinal(dataByte);
+            if (null != resultByte && resultByte.length > 0) {
+                String result = new String(resultByte, CharEncoding.UTF_8);
+                log.info("解密原串:{}", result);
+                return WxJsonUtils.parseObject(result, AppletUserDecodeData.class);
+            }
+            throw new RuntimeException("解密的数据为空");
+        } catch (Exception e) {
+            log.error("解密失败. error = {}", e.getMessage(), e);
+            throw new RuntimeException(e.getMessage());
+        }
+    }
+
+    /**
+     * 微信小程序API 用户数据的签名验证
+     * signature = sha1( rawData + session_key )
+     *
+     * @param rawData    不包括敏感信息的原始数据字符串,用于计算签名。
+     * @param sessionKey
+     */
+    public static void verifySignature(String rawData, String sessionKey, String signature) {
+        String serverSignature = SHA1.getSHA1(rawData + sessionKey);
+        log.info(rawData + ">>>>>>:" + sessionKey + " === " + serverSignature + "  ======" + signature);
+        if (!signature.equals(serverSignature)) {
+            throw new RuntimeException("数据验签不通过");
+        }
+    }
+
+    /**
+     * 根据流接收请求数据
+     *
+     * @param request
+     * @return
+     */
+    public static String streamBodyByReceive(HttpServletRequest request) throws IOException {
+        log.info("微信异步回调地址:{}", request.getRequestURL());
+        StringBuffer buffer = new StringBuffer();
+        InputStream inputStream = request.getInputStream();
+        InputStreamReader reader = new InputStreamReader(inputStream);
+        BufferedReader bufferedReader = new BufferedReader(reader);
+        String body = null;
+        while ((body = bufferedReader.readLine()) != null) {
+            buffer.append(body);
+        }
+        String data = buffer.toString();
+        reader.close();
+        inputStream.close();
+        log.info("微信异步回调数据:{}", data);
+        return data;
+    }
+
+    /**
+     * 日志
+     *
+     * @return
+     */
+    public static Logger getLogger() {
+        Logger logger = LoggerFactory.getLogger("wxpay java sdk");
+        return logger;
+    }
+
+    /**
+     * debug
+     *
+     * @param msg
+     * @param args
+     */
+    public static void debug(String msg, Object... args) {
+        Logger log = getLogger();
+        if (log.isDebugEnabled()) {
+            log.debug(msg, args);
+        }
+    }
+
+    /**
+     * info
+     *
+     * @param msg
+     * @param args
+     */
+    public static void info(String msg, Object... args) {
+        Logger log = getLogger();
+        if (log.isInfoEnabled()) {
+            log.info(msg, args);
+        }
+    }
+
+    /**
+     * warn
+     *
+     * @param msg
+     * @param args
+     */
+    public static void warn(String msg, Object... args) {
+        Logger log = getLogger();
+        if (log.isWarnEnabled()) {
+            log.warn(msg, args);
+        }
+    }
+
+    /**
+     * error
+     *
+     * @param msg
+     * @param args
+     */
+    public static void error(String msg, Object... args) {
+        Logger log = getLogger();
+        if (log.isErrorEnabled()) {
+            log.error(msg, args);
+        }
+    }
+}
diff --git a/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/controller/TChargingOrderController.java b/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/controller/TChargingOrderController.java
index 14971f7..9b405c1 100644
--- a/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/controller/TChargingOrderController.java
+++ b/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/controller/TChargingOrderController.java
@@ -3,6 +3,7 @@
 
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.ruoyi.common.core.domain.R;
+import com.ruoyi.common.security.service.TokenService;
 import com.ruoyi.common.core.web.domain.AjaxResult;
 import com.ruoyi.common.core.web.page.BasePage;
 import com.ruoyi.common.core.web.page.PageInfo;
@@ -12,6 +13,7 @@
 import com.ruoyi.order.dto.MyChargingOrderInfo;
 import com.ruoyi.order.service.TChargingOrderService;
 import io.swagger.annotations.Api;
+import org.springframework.beans.factory.annotation.Autowired;
 import io.swagger.annotations.ApiOperation;
 import org.springframework.web.bind.annotation.*;
 
@@ -35,6 +37,8 @@
 
     @Resource
     private TChargingOrderService chargingOrderService;
+    @Autowired
+    private TokenService tokenService;
     /**
      * 查询用户最近一次充电记录使用的车辆
      * @param
@@ -42,9 +46,8 @@
      */
     @PostMapping(value = "/getCar")
     public R<Long> getCar() {
-        // todo 获取用户id
         List<TChargingOrder> list = chargingOrderService.list(new LambdaQueryWrapper<TChargingOrder>()
-                .eq(TChargingOrder::getAppUserId, 11)
+                .eq(TChargingOrder::getAppUserId, tokenService.getLoginUserApplet().getUserid())
                 .isNotNull(TChargingOrder::getAppUserCarId));
         if (!list.isEmpty()){
             // 最近使用的车辆id
diff --git a/ruoyi-service/ruoyi-payment/pom.xml b/ruoyi-service/ruoyi-payment/pom.xml
index 5741c73..639a394 100644
--- a/ruoyi-service/ruoyi-payment/pom.xml
+++ b/ruoyi-service/ruoyi-payment/pom.xml
@@ -100,7 +100,18 @@
             <artifactId>spring-boot-starter-test</artifactId>
             <scope>test</scope>
         </dependency>
-
+        <!-- wx sdk -->
+        <!-- https://mvnrepository.com/artifact/com.github.wechatpay-apiv3/wechatpay-apache-httpclient -->
+        <dependency>
+            <groupId>com.github.wechatpay-apiv3</groupId>
+            <artifactId>wechatpay-apache-httpclient</artifactId>
+            <version>0.4.3</version>
+        </dependency>
+        <dependency>
+            <groupId>joda-time</groupId>
+            <artifactId>joda-time</artifactId>
+            <version>2.10.10</version>
+        </dependency>
     </dependencies>
 
     <build>
diff --git a/ruoyi-service/ruoyi-payment/src/main/java/com/ruoyi/payment/wx/config/WxConfig.java b/ruoyi-service/ruoyi-payment/src/main/java/com/ruoyi/payment/wx/config/WxConfig.java
new file mode 100644
index 0000000..3aafb85
--- /dev/null
+++ b/ruoyi-service/ruoyi-payment/src/main/java/com/ruoyi/payment/wx/config/WxConfig.java
@@ -0,0 +1,33 @@
+package com.ruoyi.payment.wx.config;
+
+import com.ruoyi.payment.wx.model.WeixinProperties;
+import com.ruoyi.payment.wx.utils.WxV3Pay;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * 项目中需继承此类
+ *
+ * @author lihen
+ */
+@ConditionalOnProperty(name = "wx.conf.enabled")
+@Configuration
+public class WxConfig {
+
+    private final WeixinProperties weixinProperties;
+
+    @Autowired
+    public WxConfig(WeixinProperties weixinProperties) {
+        this.weixinProperties = weixinProperties;
+    }
+
+    @Bean
+    @ConditionalOnMissingBean(name = "wxV3Pay")
+    public WxV3Pay wxSpV3Pay() {
+        return new WxV3Pay(weixinProperties);
+    }
+
+}
diff --git a/ruoyi-service/ruoyi-payment/src/main/java/com/ruoyi/payment/wx/controller/WxPayController.java b/ruoyi-service/ruoyi-payment/src/main/java/com/ruoyi/payment/wx/controller/WxPayController.java
new file mode 100644
index 0000000..30c22cb
--- /dev/null
+++ b/ruoyi-service/ruoyi-payment/src/main/java/com/ruoyi/payment/wx/controller/WxPayController.java
@@ -0,0 +1,142 @@
+package com.ruoyi.payment.wx.controller;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.ruoyi.common.core.web.domain.AjaxResult;
+import com.ruoyi.payment.wx.enums.RefundEnum;
+import com.ruoyi.payment.wx.model.WxPaymentRefundModel;
+import com.ruoyi.payment.wx.utils.WxV3Pay;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpServletRequest;
+import java.io.IOException;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * 微信相关接口
+ */
+@Slf4j
+@RestController
+@CrossOrigin
+@RequestMapping("/wx/")
+@Api(tags = {"微信支付相关接口"})
+public class WxPayController {
+    @Autowired
+    private WxV3Pay wxV3Pay;
+
+
+    /**
+     * 按实际修改
+     */
+    @PostMapping("order")
+    @ApiOperation("订单支付")
+    public AjaxResult<Map<String, Object>> orderPay(@RequestParam Long orderId) {
+        // 查询订单
+        // 0元订单不走支付
+        // 价格
+        Integer totalPrice = 0;
+        // 生成订单号
+        String orderNo = "";
+        // 查询用户信息 用户openid
+        String openId = "";
+        // 订单做修改
+        // 调用支付方法
+        Map<String, Object> result = wxV3Pay.jsApi(orderNo, totalPrice, openId,"");
+        log.info("支付参数:{}", result);
+        return AjaxResult.ok(result);
+    }
+
+    /**
+     * 微信v3支付-订单退款
+     *
+     * @return
+     */
+    @ApiOperation("订单退款")
+    @PostMapping(value = "refund-order")
+    public AjaxResult<String> refundOrder() {
+         Map<String, Object> result = wxV3Pay.refund(new WxPaymentRefundModel());
+         log.info("退款结果:{}", result);
+        // 微信支付退款单号
+        String refund_id = result.get("refund_id").toString();
+        // 商户退款单号
+        String out_refund_no = result.get("out_refund_no").toString();
+        // 微信支付订单号
+        String transaction_id = result.get("transaction_id").toString();
+        // 商户订单号 tradeNo
+        String out_trade_no = result.get("out_trade_no").toString();
+        // 退款成功时间
+        String success_time = Objects.nonNull(result.get("success_time")) ? result.get("success_time").toString() : null;
+        // 退款状态 RefundEnum
+        String status = result.get("status").toString();
+        // TODO 退款业务处理
+        return AjaxResult.success();
+    }
+
+    /**
+     * 支付回调
+     */
+    @PostMapping("pay/notify")
+    public void payNotify(HttpServletRequest request) throws IOException {
+        try {
+            Map<String, Object> params = wxV3Pay.verifyNotify(request, new TypeReference<Map<String, Object>>() {
+            });
+            log.info("支付回调:{}", params);
+            // 商户订单号
+            String tradeNo = params.get("out_trade_no").toString();
+            // 交易状态
+            String trade_state = params.get("trade_state").toString();
+            // 交易状态描述
+            String trade_state_desc = params.get("trade_state_desc").toString();
+            // 微信支付订单号
+            String transaction_id = params.get("transaction_id").toString();
+            // 支付完成时间
+            // 时间不对的话,可以调用  WxTimeUtils.toRfc3339Date(success_time)转换一下
+            String success_time = params.get("success_time").toString();
+            // 附加数据
+            Integer attach = Integer.parseInt(params.get("attach").toString());
+            //  TODO 业务处理
+        } catch (Exception e) {
+            log.error("支付回调异常:{}", e, e);
+            wxV3Pay.ack(false, e.getMessage());
+        }
+
+    }
+
+    /**
+     * 退款回调
+     */
+    @PostMapping("refund/notify")
+    public void refundNotify(HttpServletRequest request) throws IOException {
+        try {
+            Map<String, Object> params = wxV3Pay.verifyNotify(request, new TypeReference<Map<String, Object>>() {
+            });
+            // 商户订单号
+            String tradeNo = params.get("out_trade_no").toString();
+            // 商户退款单号
+            String out_refund_no = params.get("out_refund_no").toString();
+            // 微信支付订单号
+            String transaction_id = params.get("transaction_id").toString();
+            // 微信支付退款单号
+            String refund_id = params.get("refund_id").toString();
+            // 退款状态
+            String tradeState = params.get("refund_status").toString();
+            // 退款成功时间
+            // 时间不对的话,可以调用  WxTimeUtils.toRfc3339Date(success_time)转换一下
+            String success_time = params.get("success_time").toString();
+            if (tradeState.equals(RefundEnum.SUCCESS.name())) {
+                // TODO 退款成功处理
+                wxV3Pay.ack();
+            } else {
+                wxV3Pay.ack(false, "不是成功的退款状态");
+            }
+        } catch (Exception e) {
+            wxV3Pay.ack(false, e.getMessage());
+        }
+
+    }
+
+}
diff --git a/ruoyi-service/ruoyi-payment/src/main/java/com/ruoyi/payment/wx/enums/RefundEnum.java b/ruoyi-service/ruoyi-payment/src/main/java/com/ruoyi/payment/wx/enums/RefundEnum.java
new file mode 100644
index 0000000..fc5eabf
--- /dev/null
+++ b/ruoyi-service/ruoyi-payment/src/main/java/com/ruoyi/payment/wx/enums/RefundEnum.java
@@ -0,0 +1,53 @@
+package com.ruoyi.payment.wx.enums;
+
+import lombok.Getter;
+
+import java.util.stream.Stream;
+
+/**
+ * @author xiaochen
+ * @ClassName ProfitSharingEnum
+ * @Description
+ * @date 2021-11-21 11:15
+ */
+public enum RefundEnum {
+    /**
+     * 退款成功
+     */
+    SUCCESS("SUCCESS", "退款成功"),
+    /**
+     * 退款关闭
+     */
+    CLOSED("CLOSED", "退款关闭"),
+    /**
+     * 退款处理中
+     */
+    PROCESSING("PROCESSING", "退款处理中"),
+    /**
+     * 退款异常
+     */
+    ABNORMAL("ABNORMAL", "退款异常"),
+    ;
+
+    @Getter
+    private String code;
+    @Getter
+    private String desc;
+
+    RefundEnum(String code, String desc) {
+        this.code = code;
+        this.desc = desc;
+    }
+
+    /**
+     * 通过交易类型执行具体的交易方法
+     *
+     * @param code
+     * @return
+     */
+    public static RefundEnum fromValue(String code) {
+        return Stream.of(RefundEnum.values()).filter(fileType ->
+                fileType.getCode().toLowerCase().equals(code.toLowerCase())
+        ).findFirst().orElse(null);
+    }
+}
diff --git a/ruoyi-service/ruoyi-payment/src/main/java/com/ruoyi/payment/wx/enums/TradeStateEnum.java b/ruoyi-service/ruoyi-payment/src/main/java/com/ruoyi/payment/wx/enums/TradeStateEnum.java
new file mode 100644
index 0000000..413b434
--- /dev/null
+++ b/ruoyi-service/ruoyi-payment/src/main/java/com/ruoyi/payment/wx/enums/TradeStateEnum.java
@@ -0,0 +1,41 @@
+package com.ruoyi.payment.wx.enums;
+
+import lombok.Getter;
+
+import java.util.stream.Stream;
+
+/**
+ * @author xiaochen
+ * @ClassName AliTradeStateEnum
+ * @Description
+ * @date 2022-01-07 11:56
+ */
+public enum TradeStateEnum {
+    SUCCESS("支付成功"),
+    RETURN("已分账回退"),
+    FAIL("已失败"),
+    PROCESSING("处理中"),
+    FINISHED("分账完成"),
+    REFUND("转入退款"),
+    PAYERROR("支付失败(其他原因,如银行返回失败)"),
+    USERPAYING("用户支付中"),
+    CLOSED("已关闭"),
+    NOTPAY("未支付"),
+    UNKNOWN("未知"),
+    DONE("服务订单完成"),
+    // ...
+    ;
+    @Getter
+    private String desc;
+
+    TradeStateEnum(String desc) {
+        this.desc = desc;
+    }
+
+    public static TradeStateEnum tradeState(String code) {
+        return Stream.of(TradeStateEnum.values()).filter(fileType ->
+                fileType.name().equals(code)
+        ).findFirst().orElse(UNKNOWN);
+    }
+
+}
diff --git a/ruoyi-service/ruoyi-payment/src/main/java/com/ruoyi/payment/wx/exception/WxException.java b/ruoyi-service/ruoyi-payment/src/main/java/com/ruoyi/payment/wx/exception/WxException.java
new file mode 100644
index 0000000..0367db9
--- /dev/null
+++ b/ruoyi-service/ruoyi-payment/src/main/java/com/ruoyi/payment/wx/exception/WxException.java
@@ -0,0 +1,55 @@
+package com.ruoyi.payment.wx.exception;
+
+/**
+ * @author lihen
+ */
+public class WxException extends RuntimeException {
+
+    private final static int OK = 0;
+    private final static int ValidateSignatureError = -40001;
+    private final static int ParseXmlError = -40002;
+    public final static int ComputeSignatureError = -40003;
+    private final static int IllegalAesKey = -40004;
+    private final static int ValidateAppidError = -40005;
+    private final static int EncryptAESError = -40006;
+    private final static int DecryptAESError = -40007;
+    private final static int IllegalBuffer = -40008;
+
+    private int code;
+
+    private static String getMessage(int code) {
+        switch (code) {
+            case ValidateSignatureError:
+                return "签名验证错误";
+            case ParseXmlError:
+                return "xml解析失败";
+            case ComputeSignatureError:
+                return "sha加密生成签名失败";
+            case IllegalAesKey:
+                return "SymmetricKey非法";
+            case ValidateAppidError:
+                return "appid校验失败";
+            case EncryptAESError:
+                return "aes加密失败";
+            case DecryptAESError:
+                return "aes解密失败";
+            case IllegalBuffer:
+                return "解密后得到的buffer非法";
+            default:
+                return null;
+        }
+    }
+
+    public int getCode() {
+        return code;
+    }
+
+    public WxException(int code) {
+        super(getMessage(code));
+        this.code = code;
+    }
+
+    public WxException(String message) {
+        super(message);
+    }
+}
diff --git a/ruoyi-service/ruoyi-payment/src/main/java/com/ruoyi/payment/wx/model/V3.java b/ruoyi-service/ruoyi-payment/src/main/java/com/ruoyi/payment/wx/model/V3.java
new file mode 100644
index 0000000..71eb21c
--- /dev/null
+++ b/ruoyi-service/ruoyi-payment/src/main/java/com/ruoyi/payment/wx/model/V3.java
@@ -0,0 +1,71 @@
+package com.ruoyi.payment.wx.model;
+
+import lombok.Data;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.poi.util.IOUtils;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * @author xiaochen
+ * @ClassName V3
+ * @Description
+ */
+@Slf4j
+@Data
+public class V3 {
+    /**
+     * 获取 API 密钥
+     *
+     * @return API密钥
+     */
+    private String apiKey;
+    /**
+     * 秘钥路径,apiclient_key.pem
+     */
+    private String privateKeyPath;
+    /**
+     * 商户证书序列号
+     */
+    private String  mchSerialNo;
+
+    /**
+     * 支付回调地址
+     *
+     * @return
+     */
+    private String notifyPayUrl;
+
+    /**
+     * 退款回调地址
+     *
+     * @return
+     */
+    private String notifyRefundUrl;
+
+    /**
+     * 退款回调地址
+     */
+    private String notifyTravelRefundUrl;
+
+
+    public InputStream getPrivateKeyStream() {
+        // 需要证书释放
+        byte[] certData;
+        InputStream certStream = Thread.currentThread().getContextClassLoader().getResourceAsStream(this.privateKeyPath);
+        try {
+            certData = IOUtils.toByteArray(certStream);
+        } catch (IOException e) {
+            throw new RuntimeException("私钥文件未找到");
+        }finally {
+            try {
+                certStream.close();
+            } catch (IOException e) {
+                log.error("私钥流关闭异常");
+            }
+        }
+        return new ByteArrayInputStream(certData);
+    }
+}
diff --git a/ruoyi-service/ruoyi-payment/src/main/java/com/ruoyi/payment/wx/model/WeixinProperties.java b/ruoyi-service/ruoyi-payment/src/main/java/com/ruoyi/payment/wx/model/WeixinProperties.java
new file mode 100644
index 0000000..5c1960b
--- /dev/null
+++ b/ruoyi-service/ruoyi-payment/src/main/java/com/ruoyi/payment/wx/model/WeixinProperties.java
@@ -0,0 +1,101 @@
+package com.ruoyi.payment.wx.model;
+
+import lombok.ToString;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.boot.context.properties.NestedConfigurationProperty;
+import org.springframework.stereotype.Component;
+
+/**
+ * @author xiaochen
+ * @ClassName WeixinProperties
+ * @Description
+ */
+@ToString
+@Component
+@ConfigurationProperties(prefix = "wx.conf")
+public class WeixinProperties {
+    /**
+     * 默认开启
+     */
+    private boolean enabled = true;
+    /**
+     * 获取 App ID
+     *
+     * @return App ID
+     */
+    private String appId;
+    /**
+     * 获取 Mch ID
+     *
+     * @return Mch ID
+     */
+    private String mchId;
+
+    /**
+     * 获取 secret ID
+     *
+     * @return secret ID
+     */
+    private String secretId;
+
+    public String getSecretId() {
+        return secretId;
+    }
+
+    public void setSecretId(String secretId) {
+        this.secretId = secretId;
+    }
+
+    /**
+     * v3
+     */
+    @NestedConfigurationProperty
+    private V3 v3;
+
+    public boolean isEnabled() {
+        return enabled;
+    }
+
+    public void setEnabled(boolean enabled) {
+        this.enabled = enabled;
+    }
+
+    public V3 getV3() {
+        return v3;
+    }
+
+    public void setV3(V3 v3) {
+        this.v3 = v3;
+    }
+
+    /**
+     * HTTP(S) 连接超时时间,单位毫秒
+     *
+     */
+    public int getHttpConnectTimeoutMs() {
+        return 6 * 1000;
+    }
+
+    /**
+     * HTTP(S) 读数据超时时间,单位毫秒
+     */
+    public int getHttpReadTimeoutMs() {
+        return 8 * 1000;
+    }
+
+    public String getAppId() {
+        return appId;
+    }
+
+    public void setAppId(String appId) {
+        this.appId = appId;
+    }
+
+    public String getMchId() {
+        return mchId;
+    }
+
+    public void setMchId(String mchId) {
+        this.mchId = mchId;
+    }
+}
diff --git a/ruoyi-service/ruoyi-payment/src/main/java/com/ruoyi/payment/wx/model/WxPaymentInfoModel.java b/ruoyi-service/ruoyi-payment/src/main/java/com/ruoyi/payment/wx/model/WxPaymentInfoModel.java
new file mode 100644
index 0000000..a3c4b03
--- /dev/null
+++ b/ruoyi-service/ruoyi-payment/src/main/java/com/ruoyi/payment/wx/model/WxPaymentInfoModel.java
@@ -0,0 +1,201 @@
+package com.ruoyi.payment.wx.model;
+
+import lombok.*;
+
+import java.util.List;
+
+/**
+ * @author xiaochen
+ * @ClassName WxPaymentInfoModel
+ * @Description
+ */
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+@Getter
+@Setter
+@ToString
+public class WxPaymentInfoModel {
+    /**
+     * 合单商户appid
+     */
+    private String combine_appid;
+    /**
+     * 合单商户号
+     */
+    private String combine_mchid;
+    /**
+     * 合单商户订单号
+     */
+    private String combine_out_trade_no;
+    /**
+     * 合单--子单信息
+     */
+    private List<SubOrders> sub_orders;
+    /**
+     * 合单--支付者
+     */
+    private CombinePayerInfo combine_payer_info;
+
+    private String appid;
+    private String sp_appid;
+    private String mchid;
+    private String sp_mchid;
+    private String sub_appid;
+    private String sub_mchid;
+    private String description;
+    private String out_trade_no;
+    private String time_expire;
+    private String attach;
+    private String notify_url;
+    private String goods_tag;
+    private SettleInfo settle_info;
+    private Amount amount;
+    private Payer payer;
+    private Detail detail;
+    private SceneInfo scene_info;
+
+
+    @Builder
+    @AllArgsConstructor
+    @NoArgsConstructor
+    @Getter
+    @Setter
+    @ToString
+    public static class SettleInfo {
+        private Boolean profit_sharing;
+        private Integer subsidy_amount;
+    }
+
+    @Builder
+    @AllArgsConstructor
+    @NoArgsConstructor
+    @Getter
+    @Setter
+    @ToString
+    public static class Amount {
+        private Integer total;
+        /**
+         * 合单支付时需要
+         */
+        private Integer total_amount;
+        @Builder.Default
+        private String currency = "CNY";
+    }
+
+    @Builder
+    @AllArgsConstructor
+    @NoArgsConstructor
+    @Getter
+    @Setter
+    @ToString
+    public static class Payer {
+        private String openid;
+        private String sp_openid;
+        private String sub_openid;
+    }
+
+
+    @Builder
+    @AllArgsConstructor
+    @NoArgsConstructor
+    @Getter
+    @Setter
+    @ToString
+    public static class Detail {
+        private int cost_price;
+        private String invoice_id;
+        private List<GoodsDetail> goods_detail;
+
+        @Builder
+        @AllArgsConstructor
+        @NoArgsConstructor
+        @Getter
+        @Setter
+        @ToString
+        public static class GoodsDetail {
+            private String merchant_goods_id;
+            private String wechatpay_goods_id;
+            private String goods_name;
+            private int quantity;
+            private int unit_price;
+        }
+    }
+
+    @Builder
+    @AllArgsConstructor
+    @NoArgsConstructor
+    @Getter
+    @Setter
+    @ToString
+    public static class SceneInfo {
+        private String payer_client_ip;
+        private String device_id;
+        private StoreInfo store_info;
+        private H5Info h5_info;
+
+        @Builder
+        @AllArgsConstructor
+        @NoArgsConstructor
+        @Getter
+        @Setter
+        @ToString
+        public static class StoreInfo {
+            private String id;
+            private String name;
+            private String area_code;
+            private String address;
+        }
+
+        @Builder
+        @AllArgsConstructor
+        @NoArgsConstructor
+        @Getter
+        @Setter
+        @ToString
+        public static class H5Info {
+            private String type;
+            private String app_name;
+            private String app_url;
+            private String bundle_id;
+            private String package_name;
+        }
+    }
+
+
+    @Builder
+    @AllArgsConstructor
+    @NoArgsConstructor
+    @Getter
+    @Setter
+    @ToString
+    public static class SubOrders {
+
+        private String out_trade_no;
+
+
+        private Amount amount;
+
+        private String mchid;
+
+        private String sub_mchid;
+
+        private String attach;
+
+        private String description;
+        private String goods_tag;
+
+        private SettleInfo settle_info;
+    }
+
+    @Builder
+    @AllArgsConstructor
+    @NoArgsConstructor
+    @Getter
+    @Setter
+    @ToString
+    public static class CombinePayerInfo {
+        private String openid;
+    }
+
+}
diff --git a/ruoyi-service/ruoyi-payment/src/main/java/com/ruoyi/payment/wx/model/WxPaymentRefundModel.java b/ruoyi-service/ruoyi-payment/src/main/java/com/ruoyi/payment/wx/model/WxPaymentRefundModel.java
new file mode 100644
index 0000000..2920b93
--- /dev/null
+++ b/ruoyi-service/ruoyi-payment/src/main/java/com/ruoyi/payment/wx/model/WxPaymentRefundModel.java
@@ -0,0 +1,84 @@
+package com.ruoyi.payment.wx.model;
+
+import lombok.*;
+
+import java.util.List;
+
+/**
+ * @author xiaochen
+ * @ClassName WxPaymentRefundModel
+ * @Description
+ */
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+@Getter
+@Setter
+@ToString
+public class WxPaymentRefundModel {
+    /**
+     * 子商户,二级商户号
+     */
+    private String sub_mchid;
+    /**
+     * 电商平台APPID
+     */
+    private String sp_appid;
+    private String transaction_id;
+    private String out_trade_no;
+    /**
+     * 商户退款单号
+     */
+    private String out_refund_no;
+    /**
+     * 退款原因
+     */
+    private String reason;
+    private String notify_url;
+    /**
+     * 资金账户,否
+     */
+    private String funds_account;
+    /**
+     * 退款金额信息
+     */
+    private RefundAmount amount;
+    /**
+     * 退款商品
+     */
+    private List<RefundGoodsDetail> goods_detail;
+
+    @Builder
+    @AllArgsConstructor
+    @NoArgsConstructor
+    @Getter
+    @Setter
+    @ToString
+    public static class RefundAmount {
+        /**
+         * 原订单金额
+         */
+        private int total;
+        @Builder.Default
+        private String currency = "CNY";
+        /**
+         * 退款金额
+         */
+        private int refund;
+    }
+
+    @Builder
+    @AllArgsConstructor
+    @NoArgsConstructor
+    @Getter
+    @Setter
+    @ToString
+    public static class RefundGoodsDetail {
+        private String merchant_goods_id;
+        private String wechatpay_goods_id;
+        private String goods_name;
+        private int unit_price;
+        private int refund_amount;
+        private int refund_quantity;
+    }
+}
diff --git a/ruoyi-service/ruoyi-payment/src/main/java/com/ruoyi/payment/wx/pojo/AppletUserDecodeData.java b/ruoyi-service/ruoyi-payment/src/main/java/com/ruoyi/payment/wx/pojo/AppletUserDecodeData.java
new file mode 100644
index 0000000..f805118
--- /dev/null
+++ b/ruoyi-service/ruoyi-payment/src/main/java/com/ruoyi/payment/wx/pojo/AppletUserDecodeData.java
@@ -0,0 +1,52 @@
+package com.ruoyi.payment.wx.pojo;
+
+import lombok.Data;
+
+/**
+ * @author xiaochen
+ * @ClassName AppletUserDecodeData
+ * @Description
+ * 用户主体信息部分
+ * {
+ *     "openId": "OPENID",
+ *     "nickName": "NICKNAME",
+ *     "gender": GENDER,
+ *     "city": "CITY",
+ *     "province": "PROVINCE",
+ *     "country": "COUNTRY",
+ *     "avatarUrl": "AVATARURL",
+ *     "unionId": "UNIONID",
+ *     "watermark":
+ *     {
+ *         "appid":"APPID",
+ *         "timestamp":TIMESTAMP
+ *     }
+ * }
+ * 电话部分
+ * {
+ *     "phoneNumber": "13580006666",
+ *     "purePhoneNumber": "13580006666",
+ *     "countryCode": "86",
+ *     "watermark":
+ *     {
+ *         "appid":"APPID",
+ *         "timestamp": TIMESTAMP
+ *     }
+ * }
+ *
+ */
+@Data
+public class AppletUserDecodeData {
+    private String openId;
+    private String unionId;
+    private String nickName;
+    private int gender;
+    private String city;
+    private String province;
+    private String country;
+    private String avatarUrl;
+    private Watermark watermark;
+    private String phoneNumber;
+    private String purePhoneNumber;
+    private String countryCode;
+}
\ No newline at end of file
diff --git a/ruoyi-service/ruoyi-payment/src/main/java/com/ruoyi/payment/wx/pojo/Watermark.java b/ruoyi-service/ruoyi-payment/src/main/java/com/ruoyi/payment/wx/pojo/Watermark.java
new file mode 100644
index 0000000..8044644
--- /dev/null
+++ b/ruoyi-service/ruoyi-payment/src/main/java/com/ruoyi/payment/wx/pojo/Watermark.java
@@ -0,0 +1,9 @@
+package com.ruoyi.payment.wx.pojo;
+
+import lombok.Data;
+
+@Data
+public class Watermark {
+    private String appid;
+    private String timestamp;
+}
\ No newline at end of file
diff --git a/ruoyi-service/ruoyi-payment/src/main/java/com/ruoyi/payment/wx/resp/NotifyV3PayDecodeRespBody.java b/ruoyi-service/ruoyi-payment/src/main/java/com/ruoyi/payment/wx/resp/NotifyV3PayDecodeRespBody.java
new file mode 100644
index 0000000..94ef56e
--- /dev/null
+++ b/ruoyi-service/ruoyi-payment/src/main/java/com/ruoyi/payment/wx/resp/NotifyV3PayDecodeRespBody.java
@@ -0,0 +1,222 @@
+package com.ruoyi.payment.wx.resp;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * @author xiaochen
+ * @ClassName FacilV3PayNotifyRespBody
+ * @Description
+ */
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+@Data
+public class NotifyV3PayDecodeRespBody implements Serializable {
+    // 合单--开始
+    private String combine_appid;
+    private String combine_mchid;
+    private String combine_out_trade_no;
+    private List<SubOrders> sub_orders;
+    // 合单--结束
+    /**
+     * 服务商应用ID
+     */
+    private String sp_appid;
+    /**
+     * 服务商户号
+     */
+    private String sp_mchid;
+    /**
+     * 商户号
+     */
+    private String mchid;
+    /**
+     * 子商户应用ID
+     */
+    private String sub_appid;
+    /**
+     * 子商户号
+     */
+    private String sub_mchid;
+    /**
+     * 商户订单号
+     */
+    private String out_trade_no;
+    /**
+     * 交易状态描述
+     */
+    private String trade_state_desc;
+    /**
+     * 交易类型,枚举值:
+     * JSAPI:公众号支付
+     * NATIVE:扫码支付
+     * APP:APP支付
+     * MICROPAY:付款码支付
+     * MWEB:H5支付
+     * FACEPAY:刷脸支付
+     */
+    private String trade_type;
+    /**
+     * 附加数据,在查询API和支付通知中原样返回,可作为自定义参数使用
+     */
+    private String attach;
+    /**
+     * 微信支付订单号
+     */
+    private String transaction_id;
+    /**
+     * 交易状态,枚举值:
+     * SUCCESS:支付成功
+     * REFUND:转入退款
+     * NOTPAY:未支付
+     * CLOSED:已关闭
+     * REVOKED:已撤销(付款码支付)
+     * USERPAYING:用户支付中(付款码支付)
+     * PAYERROR:支付失败(其他原因,如银行返回失败)
+     */
+    private String trade_state;
+    /**
+     * 银行类型,采用字符串类型的银行标识。银行标识请参考《银行类型对照表》
+     * https://pay.weixin.qq.com/wiki/doc/apiv3_partner/terms_definition/chapter1_1_3.shtml#part-6
+     */
+    private String bank_type;
+    /**
+     * 支付完成时间,遵循rfc3339标准格式,格式为YYYY-MM-DDTHH:mm:ss+TIMEZONE,
+     * YYYY-MM-DD表示年月日,T出现在字符串中,表示time元素的开头,HH:mm:ss表示时分秒,
+     * TIMEZONE表示时区(+08:00表示东八区时间,领先UTC 8小时,即北京时间)。
+     * 例如:2015-05-20T13:29:35+08:00表示,北京时间2015年5月20日 13点29分35秒。
+     * 示例值:2018-06-08T10:34:56+08:00
+     */
+    private String success_time;
+    /**
+     * 支付者信息
+     */
+    private Payer payer;
+
+    /**
+     * 支付者
+     */
+    private Payer combine_payer_info;
+
+    /**
+     * 订单金额信息
+     */
+    private Amount amount;
+    /**
+     * 场景信息
+     */
+    private SceneInfo scene_info;
+    /**
+     * 优惠功能,享受优惠时返回该字段
+     */
+    private List<PromotionDetail> promotion_detail;
+
+    @Builder
+    @NoArgsConstructor
+    @AllArgsConstructor
+    @Data
+    public static class Amount implements Serializable{
+        /**
+         * 用户支付金额
+         */
+        private int payer_total;
+        /**
+         * 总金额
+         */
+        private int total;
+        /**
+         * 标价金额
+         */
+        private int total_amount;
+        /**
+         * 现金支付金额
+         */
+        private int payer_amount;
+        private String currency;
+        private String payer_currency;
+    }
+
+    @Builder
+    @NoArgsConstructor
+    @AllArgsConstructor
+    @Data
+    public static class GoodsDetail implements Serializable{
+        private String goods_id;
+        private int quantity;
+        private int unit_price;
+        private int discount_amount;
+        private String goods_remark;
+
+    }
+
+    @Builder
+    @NoArgsConstructor
+    @AllArgsConstructor
+    @Data
+    public static class Payer implements Serializable{
+        private String openid;
+        private String sp_openid;
+        private String sub_openid;
+    }
+
+    @Builder
+    @NoArgsConstructor
+    @AllArgsConstructor
+    @Data
+    public static class PromotionDetail implements Serializable{
+        private String coupon_id;
+        private String name;
+        private String scope;
+        private String type;
+        private int amount;
+        private String stock_id;
+        private int wechatpay_contribute;
+
+        private int merchant_contribute;
+        private int other_contribute;
+        private String currency;
+
+        private List<GoodsDetail> goods_detail;
+
+    }
+
+    @Builder
+    @NoArgsConstructor
+    @AllArgsConstructor
+    @Data
+    public static class SceneInfo implements Serializable{
+        /**
+         * 商户端设备号
+         */
+        private String device_id;
+    }
+
+
+    @Builder
+    @NoArgsConstructor
+    @AllArgsConstructor
+    @Data
+    public static class SubOrders implements Serializable{
+        private String mchid;
+        private String trade_type;
+        private String trade_state;
+        private String trade_state_desc;
+        private String bank_type;
+        private String attach;
+        private String success_time;
+        private String transaction_id;
+        private String out_trade_no;
+        private String sub_mchid;
+        private Amount amount;
+        /**
+         * 优惠功能,享受优惠时返回该字段
+         */
+        private List<PromotionDetail> promotion_detail;
+    }
+}
diff --git a/ruoyi-service/ruoyi-payment/src/main/java/com/ruoyi/payment/wx/utils/SHA1.java b/ruoyi-service/ruoyi-payment/src/main/java/com/ruoyi/payment/wx/utils/SHA1.java
new file mode 100644
index 0000000..2b8c9d9
--- /dev/null
+++ b/ruoyi-service/ruoyi-payment/src/main/java/com/ruoyi/payment/wx/utils/SHA1.java
@@ -0,0 +1,39 @@
+package com.ruoyi.payment.wx.utils;
+
+
+import com.ruoyi.payment.wx.exception.WxException;
+
+import java.security.MessageDigest;
+
+public class SHA1 {
+
+
+    /**
+     * 用SHA1算法生成安全签名
+     *
+     * @param str
+     * @return
+     * @throws WxException
+     */
+    public static String getSHA1(String str) throws WxException {
+        try {
+            // SHA1签名生成
+            MessageDigest md = MessageDigest.getInstance("SHA-1");
+            md.update(str.getBytes());
+            byte[] digest = md.digest();
+            StringBuffer hexstr = new StringBuffer();
+            String shaHex;
+            for (int i = 0; i < digest.length; i++) {
+                shaHex = Integer.toHexString(digest[i] & 0xFF);
+                if (shaHex.length() < 2) {
+                    hexstr.append(0);
+                }
+                hexstr.append(shaHex);
+            }
+            return hexstr.toString();
+        } catch (Exception e) {
+            throw new WxException(WxException.ComputeSignatureError);
+        }
+    }
+
+}
diff --git a/ruoyi-service/ruoyi-payment/src/main/java/com/ruoyi/payment/wx/utils/WxAbstractPay.java b/ruoyi-service/ruoyi-payment/src/main/java/com/ruoyi/payment/wx/utils/WxAbstractPay.java
new file mode 100644
index 0000000..a17e3bd
--- /dev/null
+++ b/ruoyi-service/ruoyi-payment/src/main/java/com/ruoyi/payment/wx/utils/WxAbstractPay.java
@@ -0,0 +1,333 @@
+package com.ruoyi.payment.wx.utils;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.ruoyi.common.core.utils.WebUtils;
+import com.ruoyi.payment.wx.model.WxPaymentInfoModel;
+import com.ruoyi.payment.wx.model.WxPaymentRefundModel;
+import com.ruoyi.payment.wx.resp.NotifyV3PayDecodeRespBody;
+import com.wechat.pay.contrib.apache.httpclient.auth.PrivateKeySigner;
+import com.wechat.pay.contrib.apache.httpclient.auth.Verifier;
+import com.wechat.pay.contrib.apache.httpclient.notification.Notification;
+import com.wechat.pay.contrib.apache.httpclient.notification.NotificationHandler;
+import com.wechat.pay.contrib.apache.httpclient.notification.NotificationRequest;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.http.client.config.RequestConfig;
+import org.apache.http.client.methods.*;
+import org.apache.http.conn.ConnectTimeoutException;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.util.EntityUtils;
+import org.springframework.util.StringUtils;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.net.SocketTimeoutException;
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import static com.wechat.pay.contrib.apache.httpclient.constant.WechatPayHttpHeaders.*;
+
+/**
+ * @author xiaochen
+ * @ClassName WxWifiV3Pay
+ * @Description
+ * @date 2021-11-13 21:10
+ */
+@Slf4j
+public abstract class WxAbstractPay {
+    /**
+     * 请求成功相应码
+     */
+    private static final int STATUS_CODE = 200;
+    /**
+     * 请求成功相应码
+     */
+    private static final int OTHER_STATUS_CODE = 204;
+    /**
+     * 请求根地址
+     */
+    private static final String HOST = "https://api.mch.weixin.qq.com";
+
+    private static RuntimeException parameterError(String message, Object... args) {
+        message = String.format(message, args);
+        return new IllegalArgumentException("parameter error: " + message);
+    }
+
+    /**
+     * 封装基础数据
+     *
+     * @param requestBody
+     * @param notifyUrl
+     * @return
+     */
+    protected String buildBaseParam(WxPaymentInfoModel requestBody, String notifyUrl) {
+        // 封装基础数据
+        requestBody.setNotify_url(notifyUrl);
+        String reqBody = WxJsonUtils.toJsonString(requestBody);
+        return reqBody;
+    }
+
+    /**
+     * 微信调起支付参数
+     * 返回参数如有不理解 请访问微信官方文档
+     * https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter4_1_4.shtml
+     *
+     * @param prepayId 微信下单返回的prepay_id
+     * @param appId    应用ID(appid)
+     * @return 当前调起支付所需的参数
+     * @throws Exception
+     */
+    protected Map<String, Object> wxTuneUp(PrivateKeySigner privateKeySigner, String appId, String prepayId) {
+        String timeStamp = String.valueOf(System.currentTimeMillis() / 1000);
+        String nonceStr = WxUtils.generateNonceStr();
+        String packageStr = "prepay_id=" + prepayId;
+        //加载签名
+        String signStr = Stream.of(appId, timeStamp, nonceStr, packageStr).collect(Collectors.joining("\n", "", "\n"));
+        String packageSign = privateKeySigner.sign(signStr.getBytes(StandardCharsets.UTF_8)).getSign();
+        Map<String, Object> map = new HashMap<>(6);
+        map.put("appId", appId);
+        map.put("timeStamp", timeStamp);
+        map.put("nonceStr", nonceStr);
+        map.put("package", packageStr);
+        map.put("signType", "RSA");
+        map.put("paySign", packageSign);
+        return map;
+    }
+
+    /**
+     * 构建方法请求
+     *
+     * @param uri
+     * @param socketTimeout
+     * @param connectTimeout
+     * @return
+     */
+    protected HttpGet requestGet(String uri, int socketTimeout, int connectTimeout) {
+        //请求URL
+        HttpGet httpGet = new HttpGet(HOST + uri);
+        RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(socketTimeout)
+                .setConnectTimeout(connectTimeout).build();
+        httpGet.setConfig(requestConfig);
+        httpGet.setHeader("Content-type", "application/json");
+        httpGet.setHeader("Accept", "application/json");
+        return httpGet;
+    }
+
+    /**
+     * 构建方法请求
+     *
+     * @param uri
+     * @param socketTimeout
+     * @param connectTimeout
+     * @param reqdata
+     * @return
+     */
+    protected HttpPost requestPost(String uri, int socketTimeout, int connectTimeout, String reqdata) {
+        //请求URL
+        HttpPost httpPost = new HttpPost(HOST + uri);
+        RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(socketTimeout)
+                .setConnectTimeout(connectTimeout).build();
+        httpPost.setConfig(requestConfig);
+        StringEntity entity = new StringEntity(reqdata, StandardCharsets.UTF_8);
+        entity.setContentType("application/json");
+        httpPost.setEntity(entity);
+        httpPost.setHeader("Accept", "application/json");
+        return httpPost;
+    }
+
+    public abstract <T> T verifyNotify(HttpServletRequest request, TypeReference<T> valueTypeRef) throws Exception;
+
+    /**
+     * 接收回调
+     *
+     * @param request
+     * @return
+     * @throws Exception
+     */
+    public <T> T verifyNotify(HttpServletRequest request, Verifier verifier, String apiKey, TypeReference<T> valueTypeRef) throws Exception {
+        String body = WxUtils.streamBodyByReceive(request);
+        String requestId = request.getHeader(REQUEST_ID);
+        String[] headers = {WECHAT_PAY_SERIAL, WECHAT_PAY_SIGNATURE, WECHAT_PAY_NONCE, WECHAT_PAY_TIMESTAMP};
+        String value;
+        // 验签必须参数检验
+        for (String headerName : headers) {
+            value = request.getHeader(headerName);
+            if (value == null || "".equals(value)) {
+                throw parameterError("empty [%s], request-id=[%s]", headerName, requestId);
+            }
+        }
+        String serial = request.getHeader(WECHAT_PAY_SERIAL);
+        String signature = request.getHeader(WECHAT_PAY_SIGNATURE);
+        String timestamp = request.getHeader(WECHAT_PAY_TIMESTAMP);
+        String nonce = request.getHeader(WECHAT_PAY_NONCE);
+        // 构建request,传入必要参数
+        NotificationRequest notificationRequest = new NotificationRequest.Builder().withSerialNumber(serial)
+                .withNonce(nonce)
+                .withTimestamp(timestamp)
+                .withSignature(signature)
+                .withBody(body)
+                .build();
+        NotificationHandler handler = new NotificationHandler(verifier, apiKey.getBytes(StandardCharsets.UTF_8));
+        // 验签和解析请求体
+        Notification notification = handler.parse(notificationRequest);
+        assert notification != null;
+        T respBody = WxJsonUtils.parseObject(notification.getDecryptData(), valueTypeRef);
+        return respBody;
+    }
+
+    /**
+     * 订单查询
+     *
+     * @param httpClient
+     * @param socketTimeout
+     * @param connectTimeout
+     * @param url
+     * @return com.abl.biz.center.payment.wx.v3.NotifyV3PayDecodeRespBody
+     * @author xiaochen
+     * @date 2021-12-20 17:12
+     */
+    protected NotifyV3PayDecodeRespBody query(CloseableHttpClient httpClient, int socketTimeout, int connectTimeout, String url) {
+        //请求URL
+        HttpGet httpGet = requestGet(
+                url
+                , socketTimeout
+                , connectTimeout);
+        String repBody = result(httpClient, httpGet);
+        NotifyV3PayDecodeRespBody body = WxJsonUtils.parseObject(repBody, NotifyV3PayDecodeRespBody.class);
+        return body;
+    }
+
+    /**
+     * 子级实现
+     *
+     * @param out_trade_no
+     * @param mchid
+     * @return
+     */
+    public abstract NotifyV3PayDecodeRespBody query(String out_trade_no, String mchid);
+
+
+    /**
+     * 订单退款
+     *
+     * @param refundModel
+     * @return
+     */
+    public abstract Map<String, Object> refund(WxPaymentRefundModel refundModel);
+
+    /**
+     * 订单退款
+     *
+     * @param httpClient
+     * @param uri
+     * @param httpReadTimeoutMs
+     * @param httpConnectTimeoutMs
+     * @param refundModel
+     * @return
+     */
+    public Map<String, Object> refund(CloseableHttpClient httpClient,
+                                      String uri,
+                                      int httpReadTimeoutMs,
+                                      int httpConnectTimeoutMs,
+                                      WxPaymentRefundModel refundModel) {
+        String reqBody = WxJsonUtils.toJsonString(refundModel);
+        //请求URL
+        HttpEntityEnclosingRequestBase httpPost = requestPost(
+                uri
+                , httpReadTimeoutMs
+                , httpConnectTimeoutMs, reqBody);
+        String repBody = result(httpClient, httpPost);
+        Map<String, Object> body = WxJsonUtils.parseObject(repBody, Map.class);
+        return body;
+    }
+
+
+    /**
+     * 请求结果
+     *
+     * @param request
+     * @return
+     */
+    protected String result(CloseableHttpClient httpClient, HttpRequestBase request) {
+        CloseableHttpResponse response = null;
+        try {
+            response = httpClient.execute(request);
+            int statusCode = response.getStatusLine().getStatusCode();
+            String respBodyStr = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8);
+            if (WxUtils.getLogger().isDebugEnabled()) {
+                WxUtils.debug("请求成功:{}", respBodyStr);
+            }
+            // 成功相应
+            if (STATUS_CODE == statusCode || OTHER_STATUS_CODE == statusCode) {
+                return respBodyStr;
+            } else {
+                WxUtils.error("failed,resp code = {},return body = {}", statusCode, respBodyStr);
+                throw new RuntimeException(respBodyStr);
+            }
+        } catch (ConnectTimeoutException e) {
+            e.printStackTrace();
+            throw new RuntimeException("接口超时");
+        } catch (SocketTimeoutException e) {
+            e.printStackTrace();
+            throw new RuntimeException("读取接口数据超时");
+        } catch (IOException e) {
+            e.printStackTrace();
+            throw new RuntimeException("接口请求失败,请尝试检查网络环境或请求接口是否能正常访问");
+        } finally {
+            // 关闭响应
+            try {
+                if (response != null) {
+                    //关闭结果集
+                    response.getEntity().getContent().close();
+                    response.close();
+                }
+            } catch (IOException e) {
+                throw new RuntimeException("关闭流异常");
+            }
+        }
+    }
+
+    /**
+     * 微信结果确认应答
+     * 支付通知http应答码为200或204才会当作正常接收,当回调处理异常时,应答的HTTP状态码应为500,或者4xx。
+     *
+     * @param
+     * @throws IOException
+     */
+    public void ack() throws IOException {
+        ack(true, null);
+    }
+
+    /**
+     * 微信结果确认应答
+     * 支付通知http应答码为200或204才会当作正常接收,当回调处理异常时,应答的HTTP状态码应为500,或者4xx。
+     *
+     * @param
+     * @throws IOException
+     */
+    public void ack(boolean ackSucc, String erroMsg) throws IOException {
+        HttpServletResponse response = WebUtils.response();
+        PrintWriter writer = response.getWriter();
+        if (ackSucc) {
+            log.info("响应微信回调成功!");
+            response.setStatus(200);
+            writer.write("{\"code\": \"SUCCESS\",\"message\": \"成功\"}");
+
+        } else {
+            log.info("响应微信回调失败:{}!", erroMsg);
+            response.setStatus(500);
+            writer.write("{\"code\": \"FAIL\",\"message\": " + (StringUtils.hasLength(erroMsg) ? erroMsg : "业务处理失败") + "}");
+        }
+        // 关闭流
+        if (Objects.nonNull(writer)) {
+            writer.close();
+        }
+    }
+
+}
diff --git a/ruoyi-service/ruoyi-payment/src/main/java/com/ruoyi/payment/wx/utils/WxJsonUtils.java b/ruoyi-service/ruoyi-payment/src/main/java/com/ruoyi/payment/wx/utils/WxJsonUtils.java
new file mode 100644
index 0000000..648525d
--- /dev/null
+++ b/ruoyi-service/ruoyi-payment/src/main/java/com/ruoyi/payment/wx/utils/WxJsonUtils.java
@@ -0,0 +1,73 @@
+package com.ruoyi.payment.wx.utils;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * Json转换工具类
+ *
+ * @author madman
+ */
+@Slf4j
+public final class WxJsonUtils {
+    private static final ObjectMapper OM = new ObjectMapper();
+    private static final JavaTimeModule timeModule = new JavaTimeModule();
+
+
+    /**
+     * 设置 ObjectMapper
+     *
+     * @return
+     */
+    private static ObjectMapper getObjectMapper() {
+        // 允许对象忽略json中不存在的属性
+        OM.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
+        OM.registerModule(timeModule);
+        return OM;
+    }
+
+    /**
+     * 将对象序列化
+     */
+    public static <T> String toJsonString(T obj) {
+        try {
+            ObjectMapper om = getObjectMapper();
+            // 忽略空值
+            om.setSerializationInclusion(JsonInclude.Include.NON_EMPTY);
+            return om.writeValueAsString(obj);
+        } catch (JsonProcessingException e) {
+            log.error("转json字符串失败:{}", obj);
+            return null;
+        }
+    }
+
+    /**
+     * 反序列化对象字符串
+     */
+    public static <T> T parseObject(String json, Class<T> clazz) {
+        try {
+            ObjectMapper om = getObjectMapper();
+            return om.readValue(json, clazz);
+        } catch (JsonProcessingException e) {
+            throw new RuntimeException("反序列化对象字符串失败");
+        }
+    }
+
+    /**
+     * 反序列化字符串成为对象
+     */
+    public static <T> T parseObject(String json, TypeReference<T> valueTypeRef) {
+        try {
+            ObjectMapper om = getObjectMapper();
+            return om.readValue(json, valueTypeRef);
+        } catch (JsonProcessingException e) {
+            throw new RuntimeException("反序列化字符串成为对象失败");
+        }
+    }
+
+}
diff --git a/ruoyi-service/ruoyi-payment/src/main/java/com/ruoyi/payment/wx/utils/WxTimeUtils.java b/ruoyi-service/ruoyi-payment/src/main/java/com/ruoyi/payment/wx/utils/WxTimeUtils.java
new file mode 100644
index 0000000..07476c1
--- /dev/null
+++ b/ruoyi-service/ruoyi-payment/src/main/java/com/ruoyi/payment/wx/utils/WxTimeUtils.java
@@ -0,0 +1,164 @@
+package com.ruoyi.payment.wx.utils;
+
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
+import org.springframework.util.StringUtils;
+
+import java.time.Instant;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.Date;
+import java.util.Objects;
+import java.util.TimeZone;
+
+/**
+ * @author xiaochen
+ * @ClassName WxTimeUtils
+ * @Description
+ * @date 2021-12-16 16:07
+ */
+public class WxTimeUtils {
+    /**
+     * 系统默认时区
+     */
+    private static final ZoneId ZONE = ZoneId.systemDefault();
+    /**
+     * yyyy-MM-dd'T'HH:mm:ssxxx 比如:2020-05-23T17:06:30+08:00 0时区时末尾 为+00:00
+     */
+    public static final DateTimeFormatter YYYY_MM_DD_T_HH_MM_SS_XXX_FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssxxx");
+    /**
+     * yyyy-MM-dd HH:mm:ss 比如:2020-05-23 17:06:30
+     */
+    public static final DateTimeFormatter YYYY_MM_DD_HH_MM_SS_FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").withZone(ZONE);
+
+    /**
+     * 时间转 TimeZone
+     *
+     * @param date
+     * @return
+     * @throws Exception
+     */
+    public static String dateToTimeZone(Date date) throws Exception {
+        String time;
+        if (date == null) {
+            throw new Exception("date is not null");
+        }
+        ZonedDateTime zonedDateTime = toZonedDateTime(date);
+        time = format(zonedDateTime, YYYY_MM_DD_T_HH_MM_SS_XXX_FMT);
+        return time;
+    }
+
+    /**
+     * Date转ZonedDateTime,时区为系统默认时区
+     *
+     * @param date Date
+     * @return ZonedDateTime
+     */
+    public static ZonedDateTime toZonedDateTime(Date date) {
+        Objects.requireNonNull(date, "date");
+        return Instant.ofEpochMilli(date.getTime()).atZone(ZoneId.systemDefault());
+    }
+
+    /**
+     * 根据 formatter格式化 zonedDateTime
+     *
+     * @param zonedDateTime ZonedDateTime
+     * @param formatter     DateTimeFormatter
+     * @return String
+     */
+    public static String format(ZonedDateTime zonedDateTime, DateTimeFormatter formatter) {
+        Objects.requireNonNull(zonedDateTime, "zonedDateTime");
+        Objects.requireNonNull(formatter, "formatter");
+        return zonedDateTime.format(formatter);
+    }
+
+
+    /**
+     * TimeZone 时间转标准时间
+     *
+     * @param date
+     * @return
+     * @throws Exception
+     */
+    public static String toTimeZoneStr(String date) {
+        String time;
+        if (!StringUtils.hasLength(date)) {
+            throw new RuntimeException("str is not null");
+        }
+        ZonedDateTime zonedDateTime = parseToZonedDateTime(date, YYYY_MM_DD_T_HH_MM_SS_XXX_FMT);
+        if (zonedDateTime == null) {
+            throw new RuntimeException("str to zonedDateTime fail");
+        }
+        time = zonedDateTime.format(YYYY_MM_DD_HH_MM_SS_FMT);
+        return time;
+    }
+
+    /**
+     * 转date
+     *
+     * @param date
+     * @return
+     * @throws Exception
+     */
+    public static Date toDate(String date) {
+        String time;
+        if (!StringUtils.hasLength(date)) {
+            throw new RuntimeException("str is not null");
+        }
+        ZonedDateTime zonedDateTime = parseToZonedDateTime(date, YYYY_MM_DD_T_HH_MM_SS_XXX_FMT);
+        if (zonedDateTime == null) {
+            throw new RuntimeException("str to zonedDateTime fail");
+        }
+        return Date.from(zonedDateTime.toInstant());
+    }
+
+    /**
+     * str --> Date
+     *
+     * @param date
+     * @return java.util.Date
+     * @author xiaochen
+     * @date 2022-01-20 18:20
+     */
+    public static Date toRfc3339Date(String date) {
+        DateTime dt2 = new DateTime(date);
+        return dt2.toDate();
+    }
+
+    /**
+     * 将 Date 转为 LocalDateTime
+     *
+     * @param date
+     * @return java.time.LocalDateTime;
+     */
+    public static LocalDateTime dateToLocalDateTime(Date date) {
+        return date.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
+    }
+
+
+    /**
+     * str --> Date
+     *
+     * @param date
+     * @return java.util.Date
+     * @author xiaochen
+     * @date 2022-01-20 18:20
+     */
+    public static String toRfc3339Str(Date date) {
+        DateTime dt1 = new DateTime(new Date(), DateTimeZone.forTimeZone(TimeZone.getTimeZone("Asia/Shanghai")));
+        return dt1.toString();
+    }
+
+    /**
+     * 根据 formatter解析为 ZonedDateTime
+     *
+     * @param text      待解析字符串
+     * @param formatter DateTimeFormatter
+     * @return ZonedDateTime
+     */
+    public static ZonedDateTime parseToZonedDateTime(String text, DateTimeFormatter formatter) {
+        return ZonedDateTime.parse(text, formatter);
+    }
+}
diff --git a/ruoyi-service/ruoyi-payment/src/main/java/com/ruoyi/payment/wx/utils/WxUtils.java b/ruoyi-service/ruoyi-payment/src/main/java/com/ruoyi/payment/wx/utils/WxUtils.java
new file mode 100644
index 0000000..ed04671
--- /dev/null
+++ b/ruoyi-service/ruoyi-payment/src/main/java/com/ruoyi/payment/wx/utils/WxUtils.java
@@ -0,0 +1,217 @@
+package com.ruoyi.payment.wx.utils;
+
+import com.ruoyi.payment.wx.pojo.AppletUserDecodeData;
+import lombok.extern.slf4j.Slf4j;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.util.encoders.Base64;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.crypto.Cipher;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+import javax.servlet.ServletInputStream;
+import javax.servlet.http.HttpServletRequest;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.security.AlgorithmParameters;
+import java.security.SecureRandom;
+import java.security.Security;
+import java.util.Arrays;
+import java.util.Random;
+
+/**
+ * @Description 获取用户信息工具类
+ * @Author xiaochen
+ * @Date 2021/8/12 15:45
+ */
+@Slf4j
+public class WxUtils {
+    /**
+     * 随机字符
+     */
+    private static final String SYMBOLS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
+
+    private static final Random RANDOM = new SecureRandom();
+
+    /**
+     * 微信小程序API 用户数据的解密
+     * @param encryptedData
+     * @param sessionKey
+     * @param iv
+     * @return
+     */
+    public static AppletUserDecodeData encryptedData(String encryptedData, String sessionKey, String iv) {
+        // 被加密的数据
+        byte[] dataByte = Base64.decode(encryptedData);
+        // 加密秘钥
+        byte[] keyByte = Base64.decode(sessionKey);
+        // 偏移量
+        byte[] ivByte = Base64.decode(iv);
+        try {
+            // 如果密钥不足16位,那么就补足.  这个if 中的内容很重要
+            int base = 16;
+            if (keyByte.length % base != 0) {
+                int groups = keyByte.length / base + (keyByte.length % base != 0 ? 1 : 0);
+                byte[] temp = new byte[groups * base];
+                Arrays.fill(temp, (byte) 0);
+                System.arraycopy(keyByte, 0, temp, 0, keyByte.length);
+                keyByte = temp;
+            }
+            // 初始化
+            Security.addProvider(new BouncyCastleProvider());
+            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding", "BC");
+            SecretKeySpec spec = new SecretKeySpec(keyByte, "AES");
+            AlgorithmParameters parameters = AlgorithmParameters.getInstance("AES");
+            parameters.init(new IvParameterSpec(ivByte));
+            cipher.init(Cipher.DECRYPT_MODE, spec, parameters);
+            byte[] resultByte = cipher.doFinal(dataByte);
+            if (null != resultByte && resultByte.length > 0) {
+                String result = new String(resultByte, "UTF-8");
+                log.info("解密原串:{}",result);
+                return WxJsonUtils.parseObject(result, AppletUserDecodeData.class);
+            }
+            throw new RuntimeException("解密的数据为空");
+        } catch (Exception e) {
+            log.error("解密失败. error = {}", e.getMessage(), e);
+            throw new RuntimeException(e.getMessage());
+        }
+    }
+
+    /**
+     * 微信小程序API 用户数据的签名验证
+     * signature = sha1( rawData + session_key )
+     *
+     * @param rawData    不包括敏感信息的原始数据字符串,用于计算签名。
+     * @param sessionKey
+     */
+    public static void verifySignature(String rawData, String sessionKey, String signature) {
+        String serverSignature = SHA1.getSHA1(rawData + sessionKey);
+        System.out.println(rawData + sessionKey);
+        log.info(rawData + ">>>>>>:" + sessionKey + " === " + serverSignature + "  ======" + signature);
+        if (!signature.equals(serverSignature)) {
+            throw new RuntimeException("数据验签不通过");
+        }
+    }
+
+    /**
+     * 根据流接收请求数据
+     *
+     * @param request
+     * @return
+     */
+    public static String streamBodyByReceive(HttpServletRequest request) throws IOException {
+        BufferedReader reader = null;
+        StringBuffer sb = new StringBuffer();
+        try {
+            ServletInputStream stream = request.getInputStream();
+            // 获取响应
+            reader = new BufferedReader(new InputStreamReader(stream));
+            String line;
+            while ((line = reader.readLine()) != null) {
+                sb.append(line);
+            }
+        } catch (IOException e) {
+            throw new RuntimeException("读取微信支付接口数据流出现异常!");
+        } finally {
+            reader.close();
+            WxUtils.info(sb.toString());
+        }
+        return sb.toString();
+    }
+
+    /**
+     * 获取随机字符串 Nonce Str
+     *
+     * @return String 随机字符串
+     */
+    public static String generateNonceStr() {
+        char[] nonceChars = new char[32];
+        for (int index = 0; index < nonceChars.length; ++index) {
+            nonceChars[index] = SYMBOLS.charAt(RANDOM.nextInt(SYMBOLS.length()));
+        }
+        return new String(nonceChars);
+    }
+
+    /**
+     * 获取当前时间戳,单位秒
+     *
+     * @return
+     */
+    public static long getCurrentTimestamp() {
+        return System.currentTimeMillis() / 1000;
+    }
+
+    /**
+     * 获取当前时间戳,单位毫秒
+     *
+     * @return
+     */
+    public static long getCurrentTimestampMs() {
+        return System.currentTimeMillis();
+    }
+
+
+    /**
+     * 日志
+     *
+     * @return
+     */
+    public static Logger getLogger() {
+        Logger logger = LoggerFactory.getLogger("wxpay java sdk  --->");
+        return logger;
+    }
+
+    /**
+     * debug
+     *
+     * @param msg
+     * @param args
+     */
+    public static void debug(String msg, Object... args) {
+        Logger log = getLogger();
+        if (log.isDebugEnabled()) {
+            log.debug(msg, args);
+        }
+    }
+
+    /**
+     * info
+     *
+     * @param msg
+     * @param args
+     */
+    public static void info(String msg, Object... args) {
+        Logger log = getLogger();
+        if (log.isInfoEnabled()) {
+            log.info(msg, args);
+        }
+    }
+
+    /**
+     * warn
+     *
+     * @param msg
+     * @param args
+     */
+    public static void warn(String msg, Object... args) {
+        Logger log = getLogger();
+        if (log.isWarnEnabled()) {
+            log.warn(msg, args);
+        }
+    }
+
+    /**
+     * error
+     *
+     * @param msg
+     * @param args
+     */
+    public static void error(String msg, Object... args) {
+        Logger log = getLogger();
+        if (log.isErrorEnabled()) {
+            log.error(msg, args);
+        }
+    }
+}
diff --git a/ruoyi-service/ruoyi-payment/src/main/java/com/ruoyi/payment/wx/utils/WxV3Pay.java b/ruoyi-service/ruoyi-payment/src/main/java/com/ruoyi/payment/wx/utils/WxV3Pay.java
new file mode 100644
index 0000000..b76bbfd
--- /dev/null
+++ b/ruoyi-service/ruoyi-payment/src/main/java/com/ruoyi/payment/wx/utils/WxV3Pay.java
@@ -0,0 +1,197 @@
+package com.ruoyi.payment.wx.utils;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.ruoyi.payment.wx.model.WeixinProperties;
+import com.ruoyi.payment.wx.model.WxPaymentInfoModel;
+import com.ruoyi.payment.wx.model.WxPaymentRefundModel;
+import com.ruoyi.payment.wx.resp.NotifyV3PayDecodeRespBody;
+import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder;
+import com.wechat.pay.contrib.apache.httpclient.auth.PrivateKeySigner;
+import com.wechat.pay.contrib.apache.httpclient.auth.Verifier;
+import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Credentials;
+import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Validator;
+import com.wechat.pay.contrib.apache.httpclient.cert.CertificatesManager;
+import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
+import lombok.Getter;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.http.client.methods.HttpEntityEnclosingRequestBase;
+import org.apache.http.impl.client.CloseableHttpClient;
+
+import javax.servlet.http.HttpServletRequest;
+import java.nio.charset.StandardCharsets;
+import java.security.PrivateKey;
+import java.util.Map;
+
+/**
+ * @author xiaochen
+ * @ClassName WxWifiV3Pay
+ * @Description
+ * @date 2021-11-13 21:10
+ */
+@Slf4j
+public class WxV3Pay extends WxAbstractPay {
+    @Getter
+    private WeixinProperties config;
+    @Getter
+    private Verifier verifier;
+    private WechatPayHttpClientBuilder builder;
+    @Getter
+    private CloseableHttpClient httpClient;
+    private PrivateKeySigner privateKeySigner;
+    private WechatPay2Validator validator;
+    @Getter
+    private PrivateKey privateKey;
+
+    /**
+     * 初始化
+     *
+     * @param config
+     */
+    public WxV3Pay(WeixinProperties config) {
+        // 检查v3支付配置信息
+        if (WxUtils.getLogger().isDebugEnabled()) {
+            WxUtils.debug("开始检查v3支付配置信息....");
+        }
+        try {
+            this.config = config;
+            checkWxConfig();
+            this.privateKey = PemUtil.loadPrivateKey(config.getV3().getPrivateKeyStream());
+            this.privateKeySigner = new PrivateKeySigner(config.getV3().getMchSerialNo(), privateKey);
+            // 获取证书管理器实例
+            CertificatesManager certificatesManager = CertificatesManager.getInstance();
+            // 向证书管理器增加需要自动更新平台证书的商户信息
+            certificatesManager.putMerchant(config.getMchId(), new WechatPay2Credentials(config.getMchId(),
+                    this.privateKeySigner), config.getV3().getApiKey().getBytes(StandardCharsets.UTF_8));
+            // 从证书管理器中获取verifier
+            this.verifier = certificatesManager.getVerifier(config.getMchId());
+            this.validator = new WechatPay2Validator(verifier);
+            this.builder = WechatPayHttpClientBuilder.create()
+                    .withMerchant(config.getMchId(), config.getV3().getMchSerialNo(), this.privateKey)
+                    .withValidator(this.validator);
+            this.httpClient = this.builder.build();
+        } catch (Exception e) {
+            // 打印异常信息
+            e.printStackTrace();
+            WxUtils.warn("检查v3支付配置信息出现错误,直连商户商户号:{},{}", config.getMchId(), e.getMessage());
+            return;
+        }
+        if (WxUtils.getLogger().isDebugEnabled()) {
+            WxUtils.debug("检查v3支付配置信息完成,未出现异常....");
+        }
+    }
+
+    /**
+     * 检查支付配置信息
+     *
+     * @throws Exception
+     */
+    private void checkWxConfig() throws Exception {
+        if (this.config == null) {
+            throw new Exception("config is null");
+        }
+        if (config.getMchId() == null || config.getMchId().trim().length() == 0) {
+            throw new Exception("MchID in config is empty");
+        }
+        if (config.getV3().getMchSerialNo() == null) {
+            throw new Exception("mchSerialNo in config is empty");
+        }
+        if (config.getV3().getPrivateKeyStream() == null) {
+            throw new Exception("cert stream in config is empty");
+        }
+        if (this.config.getHttpConnectTimeoutMs() < 10) {
+            throw new Exception("http connect timeout is too small");
+        }
+        if (this.config.getHttpReadTimeoutMs() < 10) {
+            throw new Exception("http read timeout is too small");
+        }
+    }
+
+    /**
+     * jsApi下单
+     *
+     * @param tradeNo     订单号
+     * @param amount      金额 分
+     * @param openid      openid
+     * @param description 订单描述
+     * @return java.util.Map<java.lang.String, java.lang.Object>
+     * @author xiaochen
+     * @date 2022-03-22 12:47
+     */
+    public Map<String, Object> jsApi(String tradeNo, Integer amount, String openid, String description) {
+        WxPaymentInfoModel requestBody = WxPaymentInfoModel.builder()
+                .mchid(this.config.getMchId())
+                .appid(this.config.getAppId())
+                .description(description)
+                .out_trade_no(tradeNo)
+//                .attach("")
+                .amount(WxPaymentInfoModel.Amount.builder().total(amount).build())
+                .payer(WxPaymentInfoModel.Payer.builder().openid(openid).build())
+                // 分不分账
+                //.settle_info(WxPaymentInfoModel.SettleInfo.builder().profit_sharing(true).build())
+                .build();
+        // 封装基础数据
+        String reqBody = buildBaseParam(requestBody
+                , this.config.getV3().getNotifyPayUrl());
+        //请求URL
+        HttpEntityEnclosingRequestBase httpPost = requestPost(
+                "/v3/pay/transactions/jsapi"
+                , this.config.getHttpReadTimeoutMs()
+                , this.config.getHttpConnectTimeoutMs()
+                , reqBody);
+        String repBody = result(httpClient, httpPost);
+        ObjectMapper om = new ObjectMapper();
+        try {
+            JsonNode rootNode = om.readTree(repBody);
+            String prepayId = rootNode.path("prepay_id").asText();
+            return wxTuneUp(this.privateKeySigner, requestBody.getAppid(), prepayId);
+        } catch (JsonProcessingException e) {
+            throw new RuntimeException("获取支付数据错误!");
+        }
+    }
+
+
+    /**
+     * 接收回调
+     *
+     * @param request
+     * @return
+     * @throws Exception
+     */
+    @Override
+    public <T> T verifyNotify(HttpServletRequest request, TypeReference<T> valueTypeRef) throws Exception {
+        return verifyNotify(request, this.verifier, this.config.getV3().getApiKey(), valueTypeRef);
+    }
+
+    /**
+     * 订单查询
+     *
+     * @param out_trade_no
+     * @param mchid
+     * @return com.abl.biz.center.payment.wx.v3.NotifyV3PayDecodeRespBody
+     * @author xiaochen
+     * @date 2021-12-20 16:47
+     */
+    @Override
+    public NotifyV3PayDecodeRespBody query(String out_trade_no, String mchid) {
+        String url =
+                String.format("/v3/pay/transactions/out-trade-no/%s", out_trade_no) + String.format("?mchid=%s", mchid);
+        return query(this.httpClient, this.config.getHttpReadTimeoutMs(), this.config.getHttpConnectTimeoutMs(), url);
+    }
+
+    /**
+     * 退款
+     *
+     * @param refundModel
+     * @return java.util.Map<java.lang.String, java.lang.Object>
+     * @author xiaochen
+     */
+    @Override
+    public Map<String, Object> refund(WxPaymentRefundModel refundModel) {
+//        refundModel.setNotify_url(this.config.getV3().getNotifyRefundUrl());
+        return refund(this.httpClient, "/v3/refund/domestic/refunds", this.config.getHttpReadTimeoutMs(), this.config.getHttpConnectTimeoutMs(), refundModel);
+    }
+
+}

--
Gitblit v1.7.1