Sfoglia il codice sorgente

账户资金变动+ 支付回调

wulei 3 mesi fa
parent
commit
a834586be8
18 ha cambiato i file con 974 aggiunte e 14 eliminazioni
  1. 45 0
      snowy-common/src/main/java/vip/xiaonuo/common/wx/HttpServletUtils.java
  2. 2 2
      snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/rechargerecord/controller/BizRechargeRecordController.java
  3. 8 0
      snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/rechargerecord/entity/BizRechargeRecord.java
  4. 5 4
      snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/rechargerecord/param/BizRechargeRecordAddParam.java
  5. 2 1
      snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/rechargerecord/service/BizRechargeRecordService.java
  6. 128 7
      snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/rechargerecord/service/impl/BizRechargeRecordServiceImpl.java
  7. 75 0
      snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/userfundchangerecord/controller/BizUserFundChangeRecordController.java
  8. 87 0
      snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/userfundchangerecord/entity/BizUserFundChangeRecord.java
  9. 34 0
      snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/userfundchangerecord/enums/BizUserFundChangeRecordEnum.java
  10. 25 0
      snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/userfundchangerecord/mapper/BizUserFundChangeRecordMapper.java
  11. 5 0
      snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/userfundchangerecord/mapper/mapping/BizUserFundChangeRecordMapper.xml
  12. 58 0
      snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/userfundchangerecord/param/BizUserFundChangeRecordAddParam.java
  13. 63 0
      snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/userfundchangerecord/param/BizUserFundChangeRecordEditParam.java
  14. 35 0
      snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/userfundchangerecord/param/BizUserFundChangeRecordIdParam.java
  15. 51 0
      snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/userfundchangerecord/param/BizUserFundChangeRecordPageParam.java
  16. 80 0
      snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/userfundchangerecord/service/BizUserFundChangeRecordService.java
  17. 94 0
      snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/userfundchangerecord/service/impl/BizUserFundChangeRecordServiceImpl.java
  18. 177 0
      snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/wx/WxPayNotifyController.java

+ 45 - 0
snowy-common/src/main/java/vip/xiaonuo/common/wx/HttpServletUtils.java

@@ -0,0 +1,45 @@
+package vip.xiaonuo.common.wx;
+
+import jakarta.servlet.ServletInputStream;
+import jakarta.servlet.http.HttpServletRequest;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * @author wulei
+ * @date 2024/11/1
+ * @Version 1.0
+ * @Description TODO
+ */
+public class HttpServletUtils {
+
+    /**
+     * 获取请求体
+     *
+     * @param request
+     * @return
+     * @throws IOException
+     */
+    public static String getRequestBody(HttpServletRequest request) throws IOException {
+        ServletInputStream stream = null;
+        BufferedReader reader = null;
+        StringBuffer sb = new StringBuffer();
+        try {
+            stream = request.getInputStream();
+            // 获取响应
+            reader = new BufferedReader(new InputStreamReader(stream, StandardCharsets.UTF_8));
+            String line;
+            while ((line = reader.readLine()) != null) {
+                sb.append(line);
+            }
+        } catch (IOException e) {
+            throw new IOException("读取返回支付接口数据流出现异常!");
+        } finally {
+            reader.close();
+        }
+        return sb.toString();
+    }
+}

+ 2 - 2
snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/rechargerecord/controller/BizRechargeRecordController.java

@@ -26,7 +26,6 @@ import vip.xiaonuo.common.annotation.CommonNoRepeat;
 import vip.xiaonuo.common.pojo.CommonResult;
 import vip.xiaonuo.biz.modular.rechargerecord.entity.BizRechargeRecord;
 import vip.xiaonuo.biz.modular.rechargerecord.param.BizRechargeRecordAddParam;
-import vip.xiaonuo.biz.modular.rechargerecord.param.BizRechargeRecordEditParam;
 import vip.xiaonuo.biz.modular.rechargerecord.param.BizRechargeRecordIdParam;
 import vip.xiaonuo.biz.modular.rechargerecord.param.BizRechargeRecordPageParam;
 import vip.xiaonuo.biz.modular.rechargerecord.service.BizRechargeRecordService;
@@ -35,6 +34,7 @@ import jakarta.annotation.Resource;
 import jakarta.validation.Valid;
 import jakarta.validation.constraints.NotEmpty;
 import java.util.List;
+import java.util.Map;
 
 /**
  * 充值记录表控制器
@@ -73,7 +73,7 @@ public class BizRechargeRecordController {
     @CommonLog("添加充值记录表")
     @PostMapping("/biz/rechargerecord/add")
     @CommonNoRepeat
-    public CommonResult<String> add(@RequestBody @Valid BizRechargeRecordAddParam bizRechargeRecordAddParam) {
+    public CommonResult<Map<String, Object>> add(@RequestBody @Valid BizRechargeRecordAddParam bizRechargeRecordAddParam) {
         bizRechargeRecordService.add(bizRechargeRecordAddParam);
         return CommonResult.ok();
     }

+ 8 - 0
snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/rechargerecord/entity/BizRechargeRecord.java

@@ -45,6 +45,10 @@ public class BizRechargeRecord extends CommonEntity {
     @Schema(description = "充值方案id")
     private String rechargePlanId;
 
+    /** 充值方案中账户需要增加的金额 */
+    @Schema(description = "充值方案中账户需要增加的金额")
+    private BigDecimal planAccountBalance;
+
     /** 充值金额 */
     @Schema(description = "充值金额")
     private BigDecimal rechargeAmount;
@@ -93,6 +97,10 @@ public class BizRechargeRecord extends CommonEntity {
     @Schema(description = "微信订单编号")
     private String wxOrderNo;
 
+    /** 实际支付金额 */
+    @Schema(description = "实际支付金额")
+    private BigDecimal wxPayAmount;
+
     /** 是否支付 0.未支付 1.已支付 */
     @Schema(description = "是否支付 0.未支付 1.已支付")
     private Boolean isPay;

+ 5 - 4
snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/rechargerecord/param/BizRechargeRecordAddParam.java

@@ -30,10 +30,10 @@ import java.util.Date;
 @Getter
 @Setter
 public class BizRechargeRecordAddParam {
-
-    /** 用户id */
-    @Schema(description = "用户id")
-    private String userId;
+    /** 微信Code */
+    @Schema(description = "微信Code")
+    @NotBlank(message = "微信Code不能为空")
+    private String code;
 
     /** 充值方案id */
     @Schema(description = "充值方案id")
@@ -41,6 +41,7 @@ public class BizRechargeRecordAddParam {
 
     /** 充值金额 */
     @Schema(description = "充值金额")
+    @NotNull(message = "充值金额不能为空")
     private BigDecimal rechargeAmount;
 
 }

+ 2 - 1
snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/rechargerecord/service/BizRechargeRecordService.java

@@ -21,6 +21,7 @@ import vip.xiaonuo.biz.modular.rechargerecord.param.BizRechargeRecordIdParam;
 import vip.xiaonuo.biz.modular.rechargerecord.param.BizRechargeRecordPageParam;
 
 import java.util.List;
+import java.util.Map;
 
 /**
  * 充值记录表Service接口
@@ -44,7 +45,7 @@ public interface BizRechargeRecordService extends IService<BizRechargeRecord> {
      * @author wulei
      * @date  2025/02/06 17:41
      */
-    void add(BizRechargeRecordAddParam bizRechargeRecordAddParam);
+    Map<String, Object> add(BizRechargeRecordAddParam bizRechargeRecordAddParam);
 
 //    /**
 //     * 编辑充值记录表

+ 128 - 7
snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/rechargerecord/service/impl/BizRechargeRecordServiceImpl.java

@@ -14,14 +14,33 @@ package vip.xiaonuo.biz.modular.rechargerecord.service.impl;
 
 import cn.hutool.core.bean.BeanUtil;
 import cn.hutool.core.collection.CollStreamUtil;
+import cn.hutool.core.date.DatePattern;
+import cn.hutool.core.date.DateUtil;
 import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.core.util.RandomUtil;
 import cn.hutool.core.util.StrUtil;
+import com.alibaba.fastjson.JSONObject;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.wechat.pay.java.core.RSAAutoCertificateConfig;
+import com.wechat.pay.java.core.exception.HttpException;
+import com.wechat.pay.java.core.exception.MalformedMessageException;
+import com.wechat.pay.java.core.exception.ServiceException;
+import com.wechat.pay.java.service.payments.jsapi.JsapiServiceExtension;
+import com.wechat.pay.java.service.payments.jsapi.model.Amount;
+import com.wechat.pay.java.service.payments.jsapi.model.Payer;
+import com.wechat.pay.java.service.payments.jsapi.model.PrepayRequest;
+import com.wechat.pay.java.service.payments.jsapi.model.PrepayWithRequestPaymentResponse;
 import jakarta.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
+import vip.xiaonuo.auth.core.util.StpLoginUserUtil;
+import vip.xiaonuo.biz.modular.rechargeplanconfig.entity.BizRechargePlanConfig;
+import vip.xiaonuo.biz.modular.rechargeplanconfig.mapper.BizRechargePlanConfigMapper;
+import vip.xiaonuo.biz.modular.user.entity.BizUser;
+import vip.xiaonuo.biz.modular.user.mapper.BizUserMapper;
 import vip.xiaonuo.common.enums.CommonSortOrderEnum;
 import vip.xiaonuo.common.exception.CommonException;
 import vip.xiaonuo.common.page.CommonPageRequest;
@@ -35,33 +54,44 @@ import vip.xiaonuo.biz.modular.rechargerecord.service.BizRechargeRecordService;
 import vip.xiaonuo.common.util.CommonWxUtil;
 import vip.xiaonuo.common.wx.WxPayConfig;
 
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
 /**
  * 充值记录表Service接口实现类
  *
  * @author wulei
- * @date  2025/02/06 17:41
+ * @date 2025/02/06 17:41
  **/
 @Service
+@Slf4j
 public class BizRechargeRecordServiceImpl extends ServiceImpl<BizRechargeRecordMapper, BizRechargeRecord> implements BizRechargeRecordService {
 
     @Resource
     private CommonWxUtil commonWxUtil;
     @Resource
     private WxPayConfig wxPayConfig;
+    @Resource
+    private RSAAutoCertificateConfig rsaAutoCertificateConfig;
+    @Resource
+    private BizRechargePlanConfigMapper bizRechargePlanConfigMapper;
+    @Resource
+    private BizUserMapper bizUserMapper;
 
 
     @Override
     public Page<BizRechargeRecord> page(BizRechargeRecordPageParam bizRechargeRecordPageParam) {
         QueryWrapper<BizRechargeRecord> queryWrapper = new QueryWrapper<BizRechargeRecord>().checkSqlInjection();
-        if(ObjectUtil.isNotEmpty(bizRechargeRecordPageParam.getUserId())) {
+        if (ObjectUtil.isNotEmpty(bizRechargeRecordPageParam.getUserId())) {
             queryWrapper.lambda().like(BizRechargeRecord::getUserId, bizRechargeRecordPageParam.getUserId());
         }
-        if(ObjectUtil.isNotEmpty(bizRechargeRecordPageParam.getStartRechargeTime()) && ObjectUtil.isNotEmpty(bizRechargeRecordPageParam.getEndRechargeTime())) {
+        if (ObjectUtil.isNotEmpty(bizRechargeRecordPageParam.getStartRechargeTime()) && ObjectUtil.isNotEmpty(bizRechargeRecordPageParam.getEndRechargeTime())) {
             queryWrapper.lambda().between(BizRechargeRecord::getRechargeTime, bizRechargeRecordPageParam.getStartRechargeTime(), bizRechargeRecordPageParam.getEndRechargeTime());
         }
-        if(ObjectUtil.isAllNotEmpty(bizRechargeRecordPageParam.getSortField(), bizRechargeRecordPageParam.getSortOrder())) {
+        if (ObjectUtil.isAllNotEmpty(bizRechargeRecordPageParam.getSortField(), bizRechargeRecordPageParam.getSortOrder())) {
             CommonSortOrderEnum.validate(bizRechargeRecordPageParam.getSortOrder());
             queryWrapper.orderBy(true, bizRechargeRecordPageParam.getSortOrder().equals(CommonSortOrderEnum.ASC.getValue()),
                     StrUtil.toUnderlineCase(bizRechargeRecordPageParam.getSortField()));
@@ -73,9 +103,100 @@ public class BizRechargeRecordServiceImpl extends ServiceImpl<BizRechargeRecordM
 
     @Transactional(rollbackFor = Exception.class)
     @Override
-    public void add(BizRechargeRecordAddParam bizRechargeRecordAddParam) {
-        BizRechargeRecord bizRechargeRecord = BeanUtil.toBean(bizRechargeRecordAddParam, BizRechargeRecord.class);
+    public synchronized Map<String, Object> add(BizRechargeRecordAddParam bizRechargeRecordAddParam) {
+        Map<String, Object> resultMap = new HashMap<>(5);
+        BizRechargeRecord bizRechargeRecord = new BizRechargeRecord();
+        //充值金额不能为0
+        if (bizRechargeRecordAddParam.getRechargeAmount().compareTo(BigDecimal.ZERO) == 0) {
+            throw new CommonException("充值金额不能为0");
+        }
+        // 查询用户信息
+        BizUser bizUser = bizUserMapper.selectById(StpLoginUserUtil.getLoginUser().getId());
+        if (ObjectUtil.isNull(bizUser)) {
+            throw new CommonException("未查询到该用户信息");
+        }
+        if (ObjectUtil.isNotEmpty(bizRechargeRecordAddParam.getRechargePlanId())) {
+            // 校验充值方案
+            BizRechargePlanConfig bizRechargePlanConfig = bizRechargePlanConfigMapper.selectById(bizRechargeRecordAddParam.getRechargePlanId());
+            if (ObjectUtil.isNull(bizRechargePlanConfig)) {
+                throw new CommonException("未查询到该充值方案");
+            }
+            if (bizRechargeRecordAddParam.getRechargeAmount().compareTo(bizRechargePlanConfig.getRechargeAmount()) < 0) {
+                throw new CommonException("当前充值金额不符合该优惠方案");
+            }
+            bizRechargeRecord.setRechargePlanId(bizRechargeRecordAddParam.getRechargePlanId());
+            bizRechargeRecord.setPlanAccountBalance(bizRechargePlanConfig.getAccountBalance());
+            bizRechargeRecord.setCouponAmount(bizRechargePlanConfig.getCouponAmount());
+            bizRechargeRecord.setCouponNum(bizRechargePlanConfig.getCouponNum());
+            bizRechargeRecord.setRebateRatio(bizRechargePlanConfig.getRebateRatio());
+
+            //计算返点金额
+            if (ObjectUtil.isNotEmpty(bizUser.getReferralUser())) {
+                BigDecimal rebate = bizRechargePlanConfig.getRebateRatio().divide(new BigDecimal("100"), 2, RoundingMode.HALF_UP);
+                BigDecimal rebateAmount = bizRechargeRecordAddParam.getRechargeAmount().multiply(rebate);
+                bizRechargeRecord.setRebateAmount(rebateAmount);
+            }
+        }
+        String openId = commonWxUtil.getOpenId(bizRechargeRecordAddParam.getCode(), wxPayConfig.getGetOpenIdUrl(), wxPayConfig.getAppId(), wxPayConfig.getAppSecret());
+        if (ObjectUtil.isEmpty(openId)) {
+            throw new CommonException("openId获取异常,请联系管理员");
+        }
+        log.info("================ 生成基础充值记录内容 =============");
+        bizRechargeRecord.setRechargeAmount(bizRechargeRecordAddParam.getRechargeAmount());
+        // 本系统订单号
+        String orderNumber = "BBT" + DateUtil.format(DateUtil.date(), DatePattern.PURE_DATETIME_MS_PATTERN) + RandomUtil.randomNumbers(3);
+        bizRechargeRecord.setOrderNo(orderNumber);
+        bizRechargeRecord.setRechargeTime(DateUtil.date());
+        bizRechargeRecord.setUserId(bizUser.getId());
         this.save(bizRechargeRecord);
+        //转换金额为分
+        Integer totalFee = bizRechargeRecordAddParam.getRechargeAmount().multiply(new BigDecimal("100")).intValue();
+
+        //请求微信支付相关配置
+        JsapiServiceExtension service =
+                new JsapiServiceExtension.Builder()
+                        .config(rsaAutoCertificateConfig)
+                        .signType("RSA") // 不填默认为RSA
+                        .build();
+        PrepayWithRequestPaymentResponse response;
+        try {
+            PrepayRequest request = new PrepayRequest();
+            request.setAppid(wxPayConfig.getAppId());
+            request.setMchid(wxPayConfig.getMerchantId());
+            request.setDescription("爸爸糖手工吐司会员账户充值");
+            request.setOutTradeNo(orderNumber);
+            request.setNotifyUrl(wxPayConfig.getPayNotifyUrl());
+            Amount amount = new Amount();
+            amount.setTotal(totalFee);
+            request.setAmount(amount);
+            Payer payer = new Payer();
+            payer.setOpenid(openId);
+            request.setPayer(payer);
+            log.info("请求生成订单,请求参数:{}", JSONObject.toJSONString(request));
+            // 调用预下单接口
+            response = service.prepayWithRequestPayment(request);
+            resultMap.put("package", response.getPackageVal());
+            resultMap.put("nonceStr", response.getNonceStr());
+            resultMap.put("signType", response.getSignType());
+            resultMap.put("timeStamp", response.getTimeStamp());
+            resultMap.put("paySign", response.getPaySign());
+            resultMap.put("dataId", bizRechargeRecord.getId());
+            log.info("订单【{}】生成订单成功,返回信息:{}", orderNumber, response);
+        } catch (HttpException e) {
+            // 发送HTTP请求失败
+            log.error("微信下单发送HTTP请求失败,错误信息:{}", e.getMessage());
+            throw new CommonException("微信下单失败,请联系管理员");
+        } catch (ServiceException e) {
+            // 服务返回状态小于200或大于等于300,例如500
+            log.error("微信下单服务状态错误,错误信息:{}", e.getErrorMessage());
+            throw new CommonException("微信下单失败,请联系管理员");
+        } catch (MalformedMessageException e) {
+            // 服务返回成功,返回体类型不合法,或者解析返回体失败
+            log.error("服务返回成功,返回体类型不合法,或者解析返回体失败,错误信息:{}", e.getMessage());
+            throw new CommonException("微信下单失败,请联系管理员");
+        }
+        //3. 返回微信订单参数,方便小程序唤起支付
+        return resultMap;
     }
 
 //    @Transactional(rollbackFor = Exception.class)
@@ -101,7 +222,7 @@ public class BizRechargeRecordServiceImpl extends ServiceImpl<BizRechargeRecordM
     @Override
     public BizRechargeRecord queryEntity(String id) {
         BizRechargeRecord bizRechargeRecord = this.getById(id);
-        if(ObjectUtil.isEmpty(bizRechargeRecord)) {
+        if (ObjectUtil.isEmpty(bizRechargeRecord)) {
             throw new CommonException("充值记录表不存在,id值为:{}", id);
         }
         return bizRechargeRecord;

+ 75 - 0
snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/userfundchangerecord/controller/BizUserFundChangeRecordController.java

@@ -0,0 +1,75 @@
+/*
+ * Copyright [2022] [https://www.xiaonuo.vip]
+ *
+ * Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
+ *
+ * 1.请不要删除和修改根目录下的LICENSE文件。
+ * 2.请不要删除和修改Snowy源码头部的版权声明。
+ * 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
+ * 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
+ * 5.不可二次分发开源参与同类竞品,如有想法可联系团队xiaonuobase@qq.com商议合作。
+ * 6.若您的项目无法满足以上几点,需要更多功能代码,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip
+ */
+package vip.xiaonuo.biz.modular.userfundchangerecord.controller;
+
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import io.swagger.v3.oas.annotations.Operation;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RestController;
+import vip.xiaonuo.common.annotation.CommonLog;
+import vip.xiaonuo.common.pojo.CommonResult;
+import vip.xiaonuo.biz.modular.userfundchangerecord.entity.BizUserFundChangeRecord;
+import vip.xiaonuo.biz.modular.userfundchangerecord.param.BizUserFundChangeRecordAddParam;
+import vip.xiaonuo.biz.modular.userfundchangerecord.param.BizUserFundChangeRecordEditParam;
+import vip.xiaonuo.biz.modular.userfundchangerecord.param.BizUserFundChangeRecordIdParam;
+import vip.xiaonuo.biz.modular.userfundchangerecord.param.BizUserFundChangeRecordPageParam;
+import vip.xiaonuo.biz.modular.userfundchangerecord.service.BizUserFundChangeRecordService;
+
+import jakarta.annotation.Resource;
+import jakarta.validation.Valid;
+import jakarta.validation.constraints.NotEmpty;
+import java.util.List;
+
+/**
+ * 用户账户资金变动记录控制器
+ *
+ * @author wulei
+ * @date  2025/02/07 11:26
+ */
+@Tag(name = "用户账户资金变动记录控制器")
+@RestController
+@Validated
+public class BizUserFundChangeRecordController {
+
+    @Resource
+    private BizUserFundChangeRecordService bizUserFundChangeRecordService;
+
+    /**
+     * 获取用户账户资金变动记录分页
+     *
+     * @author wulei
+     * @date  2025/02/07 11:26
+     */
+    @Operation(summary = "获取用户账户资金变动记录分页")
+    @GetMapping("/biz/userfundchangerecord/page")
+    public CommonResult<Page<BizUserFundChangeRecord>> page(BizUserFundChangeRecordPageParam bizUserFundChangeRecordPageParam) {
+        return CommonResult.data(bizUserFundChangeRecordService.page(bizUserFundChangeRecordPageParam));
+    }
+
+    /**
+     * 获取用户账户资金变动记录详情
+     *
+     * @author wulei
+     * @date  2025/02/07 11:26
+     */
+    @Operation(summary = "获取用户账户资金变动记录详情")
+    @GetMapping("/biz/userfundchangerecord/detail")
+    public CommonResult<BizUserFundChangeRecord> detail(@Valid BizUserFundChangeRecordIdParam bizUserFundChangeRecordIdParam) {
+        return CommonResult.data(bizUserFundChangeRecordService.detail(bizUserFundChangeRecordIdParam));
+    }
+}

+ 87 - 0
snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/userfundchangerecord/entity/BizUserFundChangeRecord.java

@@ -0,0 +1,87 @@
+/*
+ * Copyright [2022] [https://www.xiaonuo.vip]
+ *
+ * Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
+ *
+ * 1.请不要删除和修改根目录下的LICENSE文件。
+ * 2.请不要删除和修改Snowy源码头部的版权声明。
+ * 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
+ * 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
+ * 5.不可二次分发开源参与同类竞品,如有想法可联系团队xiaonuobase@qq.com商议合作。
+ * 6.若您的项目无法满足以上几点,需要更多功能代码,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip
+ */
+package vip.xiaonuo.biz.modular.userfundchangerecord.entity;
+
+import com.baomidou.mybatisplus.annotation.*;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Getter;
+import lombok.Setter;
+import java.math.BigDecimal;
+import java.util.Date;
+
+/**
+ * 用户账户资金变动记录实体
+ *
+ * @author wulei
+ * @date  2025/02/07 11:26
+ **/
+@Getter
+@Setter
+@TableName("biz_user_fund_change_record")
+public class BizUserFundChangeRecord {
+
+    /** 账户金额变动记录id */
+    @TableId
+    @Schema(description = "账户金额变动记录id")
+    private String id;
+
+    /** 用户id */
+    @Schema(description = "用户id")
+    private String userId;
+
+    /** 变动类别: 1. 主动充值 2.主动消费 3.代金券生效 4.赠送 5.扣减 */
+    @Schema(description = "变动类别: 1. 主动充值 2.主动消费 3.代金券生效 4.赠送 5.扣减")
+    private String changeType;
+
+    /** 原账户余额 */
+    @Schema(description = "原账户余额")
+    private BigDecimal oldAccountBalance;
+
+    /** 原代金券金额 */
+    @Schema(description = "原代金券金额")
+    private BigDecimal oldVoucherBalance;
+
+    /** 现账户余额 */
+    @Schema(description = "现账户余额")
+    private BigDecimal newAccountBalance;
+
+    /** 现代金券金额 */
+    @Schema(description = "现代金券金额")
+    private BigDecimal newVoucherBalance;
+
+    /** 删除标志:NOT_DELETE 正常,DELETED 已删除 */
+    @Schema(description = "删除标志:NOT_DELETE 正常,DELETED 已删除")
+    @TableLogic
+    @TableField(fill = FieldFill.INSERT)
+    private String deleteFlag;
+
+    /** 创建人 */
+    @Schema(description = "创建人")
+    @TableField(fill = FieldFill.INSERT)
+    private String createUser;
+
+    /** 创建时间 */
+    @Schema(description = "创建时间")
+    @TableField(fill = FieldFill.INSERT)
+    private Date createTime;
+
+    /** 修改时间 */
+    @Schema(description = "修改时间")
+    @TableField(fill = FieldFill.UPDATE)
+    private Date updateTime;
+
+    /** 修改人 */
+    @Schema(description = "修改人")
+    @TableField(fill = FieldFill.UPDATE)
+    private String updateUser;
+}

+ 34 - 0
snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/userfundchangerecord/enums/BizUserFundChangeRecordEnum.java

@@ -0,0 +1,34 @@
+/*
+ * Copyright [2022] [https://www.xiaonuo.vip]
+ *
+ * Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
+ *
+ * 1.请不要删除和修改根目录下的LICENSE文件。
+ * 2.请不要删除和修改Snowy源码头部的版权声明。
+ * 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
+ * 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
+ * 5.不可二次分发开源参与同类竞品,如有想法可联系团队xiaonuobase@qq.com商议合作。
+ * 6.若您的项目无法满足以上几点,需要更多功能代码,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip
+ */
+package vip.xiaonuo.biz.modular.userfundchangerecord.enums;
+
+import lombok.Getter;
+
+/**
+ * 用户账户资金变动记录枚举
+ *
+ * @author wulei
+ * @date  2025/02/07 11:26
+ **/
+@Getter
+public enum BizUserFundChangeRecordEnum {
+
+    /** 测试 */
+    TEST("TEST");
+
+    private final String value;
+
+    BizUserFundChangeRecordEnum(String value) {
+        this.value = value;
+    }
+}

+ 25 - 0
snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/userfundchangerecord/mapper/BizUserFundChangeRecordMapper.java

@@ -0,0 +1,25 @@
+/*
+ * Copyright [2022] [https://www.xiaonuo.vip]
+ *
+ * Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
+ *
+ * 1.请不要删除和修改根目录下的LICENSE文件。
+ * 2.请不要删除和修改Snowy源码头部的版权声明。
+ * 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
+ * 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
+ * 5.不可二次分发开源参与同类竞品,如有想法可联系团队xiaonuobase@qq.com商议合作。
+ * 6.若您的项目无法满足以上几点,需要更多功能代码,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip
+ */
+package vip.xiaonuo.biz.modular.userfundchangerecord.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import vip.xiaonuo.biz.modular.userfundchangerecord.entity.BizUserFundChangeRecord;
+
+/**
+ * 用户账户资金变动记录Mapper接口
+ *
+ * @author wulei
+ * @date  2025/02/07 11:26
+ **/
+public interface BizUserFundChangeRecordMapper extends BaseMapper<BizUserFundChangeRecord> {
+}

+ 5 - 0
snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/userfundchangerecord/mapper/mapping/BizUserFundChangeRecordMapper.xml

@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="vip.xiaonuo.biz.modular.userfundchangerecord.mapper.BizUserFundChangeRecordMapper">
+
+</mapper>

+ 58 - 0
snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/userfundchangerecord/param/BizUserFundChangeRecordAddParam.java

@@ -0,0 +1,58 @@
+/*
+ * Copyright [2022] [https://www.xiaonuo.vip]
+ *
+ * Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
+ *
+ * 1.请不要删除和修改根目录下的LICENSE文件。
+ * 2.请不要删除和修改Snowy源码头部的版权声明。
+ * 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
+ * 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
+ * 5.不可二次分发开源参与同类竞品,如有想法可联系团队xiaonuobase@qq.com商议合作。
+ * 6.若您的项目无法满足以上几点,需要更多功能代码,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip
+ */
+package vip.xiaonuo.biz.modular.userfundchangerecord.param;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Getter;
+import lombok.Setter;
+
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import java.math.BigDecimal;
+import java.util.Date;
+
+/**
+ * 用户账户资金变动记录添加参数
+ *
+ * @author wulei
+ * @date  2025/02/07 11:26
+ **/
+@Getter
+@Setter
+public class BizUserFundChangeRecordAddParam {
+
+    /** 用户id */
+    @Schema(description = "用户id")
+    private String userId;
+
+    /** 变动类别: 1. 主动充值 2.主动消费 3.代金券生效 4.赠送 5.扣减 */
+    @Schema(description = "变动类别: 1. 主动充值 2.主动消费 3.代金券生效 4.赠送 5.扣减")
+    private String changeType;
+
+    /** 原账户余额 */
+    @Schema(description = "原账户余额")
+    private BigDecimal oldAccountBalance;
+
+    /** 原代金券金额 */
+    @Schema(description = "原代金券金额")
+    private BigDecimal oldVoucherBalance;
+
+    /** 现账户余额 */
+    @Schema(description = "现账户余额")
+    private BigDecimal newAccountBalance;
+
+    /** 现代金券金额 */
+    @Schema(description = "现代金券金额")
+    private BigDecimal newVoucherBalance;
+
+}

+ 63 - 0
snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/userfundchangerecord/param/BizUserFundChangeRecordEditParam.java

@@ -0,0 +1,63 @@
+/*
+ * Copyright [2022] [https://www.xiaonuo.vip]
+ *
+ * Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
+ *
+ * 1.请不要删除和修改根目录下的LICENSE文件。
+ * 2.请不要删除和修改Snowy源码头部的版权声明。
+ * 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
+ * 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
+ * 5.不可二次分发开源参与同类竞品,如有想法可联系团队xiaonuobase@qq.com商议合作。
+ * 6.若您的项目无法满足以上几点,需要更多功能代码,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip
+ */
+package vip.xiaonuo.biz.modular.userfundchangerecord.param;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Getter;
+import lombok.Setter;
+
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import java.math.BigDecimal;
+import java.util.Date;
+
+/**
+ * 用户账户资金变动记录编辑参数
+ *
+ * @author wulei
+ * @date  2025/02/07 11:26
+ **/
+@Getter
+@Setter
+public class BizUserFundChangeRecordEditParam {
+
+    /** 账户金额变动记录id */
+    @Schema(description = "账户金额变动记录id", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotBlank(message = "id不能为空")
+    private String id;
+
+    /** 用户id */
+    @Schema(description = "用户id")
+    private String userId;
+
+    /** 变动类别: 1. 主动充值 2.主动消费 3.代金券生效 4.赠送 5.扣减 */
+    @Schema(description = "变动类别: 1. 主动充值 2.主动消费 3.代金券生效 4.赠送 5.扣减")
+    private String changeType;
+
+    /** 原账户余额 */
+    @Schema(description = "原账户余额")
+    private BigDecimal oldAccountBalance;
+
+    /** 原代金券金额 */
+    @Schema(description = "原代金券金额")
+    private BigDecimal oldVoucherBalance;
+
+    /** 现账户余额 */
+    @Schema(description = "现账户余额")
+    private BigDecimal newAccountBalance;
+
+    /** 现代金券金额 */
+    @Schema(description = "现代金券金额")
+    private BigDecimal newVoucherBalance;
+
+}

+ 35 - 0
snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/userfundchangerecord/param/BizUserFundChangeRecordIdParam.java

@@ -0,0 +1,35 @@
+/*
+ * Copyright [2022] [https://www.xiaonuo.vip]
+ *
+ * Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
+ *
+ * 1.请不要删除和修改根目录下的LICENSE文件。
+ * 2.请不要删除和修改Snowy源码头部的版权声明。
+ * 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
+ * 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
+ * 5.不可二次分发开源参与同类竞品,如有想法可联系团队xiaonuobase@qq.com商议合作。
+ * 6.若您的项目无法满足以上几点,需要更多功能代码,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip
+ */
+package vip.xiaonuo.biz.modular.userfundchangerecord.param;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Getter;
+import lombok.Setter;
+
+import jakarta.validation.constraints.NotBlank;
+
+/**
+ * 用户账户资金变动记录Id参数
+ *
+ * @author wulei
+ * @date  2025/02/07 11:26
+ **/
+@Getter
+@Setter
+public class BizUserFundChangeRecordIdParam {
+
+    /** 账户金额变动记录id */
+    @Schema(description = "账户金额变动记录id", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotBlank(message = "id不能为空")
+    private String id;
+}

+ 51 - 0
snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/userfundchangerecord/param/BizUserFundChangeRecordPageParam.java

@@ -0,0 +1,51 @@
+/*
+ * Copyright [2022] [https://www.xiaonuo.vip]
+ *
+ * Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
+ *
+ * 1.请不要删除和修改根目录下的LICENSE文件。
+ * 2.请不要删除和修改Snowy源码头部的版权声明。
+ * 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
+ * 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
+ * 5.不可二次分发开源参与同类竞品,如有想法可联系团队xiaonuobase@qq.com商议合作。
+ * 6.若您的项目无法满足以上几点,需要更多功能代码,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip
+ */
+package vip.xiaonuo.biz.modular.userfundchangerecord.param;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Getter;
+import lombok.Setter;
+import java.math.BigDecimal;
+import java.util.Date;
+
+/**
+ * 用户账户资金变动记录查询参数
+ *
+ * @author wulei
+ * @date  2025/02/07 11:26
+ **/
+@Getter
+@Setter
+public class BizUserFundChangeRecordPageParam {
+
+    /** 当前页 */
+    @Schema(description = "当前页码")
+    private Integer current;
+
+    /** 每页条数 */
+    @Schema(description = "每页条数")
+    private Integer size;
+
+    /** 排序字段 */
+    @Schema(description = "排序字段,字段驼峰名称,如:userName")
+    private String sortField;
+
+    /** 排序方式 */
+    @Schema(description = "排序方式,升序:ASCEND;降序:DESCEND")
+    private String sortOrder;
+
+    /** 关键词 */
+    @Schema(description = "关键词")
+    private String searchKey;
+
+}

+ 80 - 0
snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/userfundchangerecord/service/BizUserFundChangeRecordService.java

@@ -0,0 +1,80 @@
+/*
+ * Copyright [2022] [https://www.xiaonuo.vip]
+ *
+ * Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
+ *
+ * 1.请不要删除和修改根目录下的LICENSE文件。
+ * 2.请不要删除和修改Snowy源码头部的版权声明。
+ * 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
+ * 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
+ * 5.不可二次分发开源参与同类竞品,如有想法可联系团队xiaonuobase@qq.com商议合作。
+ * 6.若您的项目无法满足以上几点,需要更多功能代码,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip
+ */
+package vip.xiaonuo.biz.modular.userfundchangerecord.service;
+
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.IService;
+import vip.xiaonuo.biz.modular.userfundchangerecord.entity.BizUserFundChangeRecord;
+import vip.xiaonuo.biz.modular.userfundchangerecord.param.BizUserFundChangeRecordAddParam;
+import vip.xiaonuo.biz.modular.userfundchangerecord.param.BizUserFundChangeRecordEditParam;
+import vip.xiaonuo.biz.modular.userfundchangerecord.param.BizUserFundChangeRecordIdParam;
+import vip.xiaonuo.biz.modular.userfundchangerecord.param.BizUserFundChangeRecordPageParam;
+
+import java.util.List;
+
+/**
+ * 用户账户资金变动记录Service接口
+ *
+ * @author wulei
+ * @date  2025/02/07 11:26
+ **/
+public interface BizUserFundChangeRecordService extends IService<BizUserFundChangeRecord> {
+
+    /**
+     * 获取用户账户资金变动记录分页
+     *
+     * @author wulei
+     * @date  2025/02/07 11:26
+     */
+    Page<BizUserFundChangeRecord> page(BizUserFundChangeRecordPageParam bizUserFundChangeRecordPageParam);
+
+    /**
+     * 添加用户账户资金变动记录
+     *
+     * @author wulei
+     * @date  2025/02/07 11:26
+     */
+    void add(BizUserFundChangeRecordAddParam bizUserFundChangeRecordAddParam);
+
+    /**
+     * 编辑用户账户资金变动记录
+     *
+     * @author wulei
+     * @date  2025/02/07 11:26
+     */
+    void edit(BizUserFundChangeRecordEditParam bizUserFundChangeRecordEditParam);
+
+    /**
+     * 删除用户账户资金变动记录
+     *
+     * @author wulei
+     * @date  2025/02/07 11:26
+     */
+    void delete(List<BizUserFundChangeRecordIdParam> bizUserFundChangeRecordIdParamList);
+
+    /**
+     * 获取用户账户资金变动记录详情
+     *
+     * @author wulei
+     * @date  2025/02/07 11:26
+     */
+    BizUserFundChangeRecord detail(BizUserFundChangeRecordIdParam bizUserFundChangeRecordIdParam);
+
+    /**
+     * 获取用户账户资金变动记录详情
+     *
+     * @author wulei
+     * @date  2025/02/07 11:26
+     **/
+    BizUserFundChangeRecord queryEntity(String id);
+}

+ 94 - 0
snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/userfundchangerecord/service/impl/BizUserFundChangeRecordServiceImpl.java

@@ -0,0 +1,94 @@
+/*
+ * Copyright [2022] [https://www.xiaonuo.vip]
+ *
+ * Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
+ *
+ * 1.请不要删除和修改根目录下的LICENSE文件。
+ * 2.请不要删除和修改Snowy源码头部的版权声明。
+ * 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
+ * 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
+ * 5.不可二次分发开源参与同类竞品,如有想法可联系团队xiaonuobase@qq.com商议合作。
+ * 6.若您的项目无法满足以上几点,需要更多功能代码,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip
+ */
+package vip.xiaonuo.biz.modular.userfundchangerecord.service.impl;
+
+import cn.hutool.core.bean.BeanUtil;
+import cn.hutool.core.collection.CollStreamUtil;
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.core.util.StrUtil;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import vip.xiaonuo.common.enums.CommonSortOrderEnum;
+import vip.xiaonuo.common.exception.CommonException;
+import vip.xiaonuo.common.page.CommonPageRequest;
+import vip.xiaonuo.biz.modular.userfundchangerecord.entity.BizUserFundChangeRecord;
+import vip.xiaonuo.biz.modular.userfundchangerecord.mapper.BizUserFundChangeRecordMapper;
+import vip.xiaonuo.biz.modular.userfundchangerecord.param.BizUserFundChangeRecordAddParam;
+import vip.xiaonuo.biz.modular.userfundchangerecord.param.BizUserFundChangeRecordEditParam;
+import vip.xiaonuo.biz.modular.userfundchangerecord.param.BizUserFundChangeRecordIdParam;
+import vip.xiaonuo.biz.modular.userfundchangerecord.param.BizUserFundChangeRecordPageParam;
+import vip.xiaonuo.biz.modular.userfundchangerecord.service.BizUserFundChangeRecordService;
+
+import java.util.List;
+
+/**
+ * 用户账户资金变动记录Service接口实现类
+ *
+ * @author wulei
+ * @date  2025/02/07 11:26
+ **/
+@Service
+public class BizUserFundChangeRecordServiceImpl extends ServiceImpl<BizUserFundChangeRecordMapper, BizUserFundChangeRecord> implements BizUserFundChangeRecordService {
+
+    @Override
+    public Page<BizUserFundChangeRecord> page(BizUserFundChangeRecordPageParam bizUserFundChangeRecordPageParam) {
+        QueryWrapper<BizUserFundChangeRecord> queryWrapper = new QueryWrapper<BizUserFundChangeRecord>().checkSqlInjection();
+        if(ObjectUtil.isAllNotEmpty(bizUserFundChangeRecordPageParam.getSortField(), bizUserFundChangeRecordPageParam.getSortOrder())) {
+            CommonSortOrderEnum.validate(bizUserFundChangeRecordPageParam.getSortOrder());
+            queryWrapper.orderBy(true, bizUserFundChangeRecordPageParam.getSortOrder().equals(CommonSortOrderEnum.ASC.getValue()),
+                    StrUtil.toUnderlineCase(bizUserFundChangeRecordPageParam.getSortField()));
+        } else {
+            queryWrapper.lambda().orderByAsc(BizUserFundChangeRecord::getId);
+        }
+        return this.page(CommonPageRequest.defaultPage(), queryWrapper);
+    }
+
+    @Transactional(rollbackFor = Exception.class)
+    @Override
+    public void add(BizUserFundChangeRecordAddParam bizUserFundChangeRecordAddParam) {
+        BizUserFundChangeRecord bizUserFundChangeRecord = BeanUtil.toBean(bizUserFundChangeRecordAddParam, BizUserFundChangeRecord.class);
+        this.save(bizUserFundChangeRecord);
+    }
+
+    @Transactional(rollbackFor = Exception.class)
+    @Override
+    public void edit(BizUserFundChangeRecordEditParam bizUserFundChangeRecordEditParam) {
+        BizUserFundChangeRecord bizUserFundChangeRecord = this.queryEntity(bizUserFundChangeRecordEditParam.getId());
+        BeanUtil.copyProperties(bizUserFundChangeRecordEditParam, bizUserFundChangeRecord);
+        this.updateById(bizUserFundChangeRecord);
+    }
+
+    @Transactional(rollbackFor = Exception.class)
+    @Override
+    public void delete(List<BizUserFundChangeRecordIdParam> bizUserFundChangeRecordIdParamList) {
+        // 执行删除
+        this.removeByIds(CollStreamUtil.toList(bizUserFundChangeRecordIdParamList, BizUserFundChangeRecordIdParam::getId));
+    }
+
+    @Override
+    public BizUserFundChangeRecord detail(BizUserFundChangeRecordIdParam bizUserFundChangeRecordIdParam) {
+        return this.queryEntity(bizUserFundChangeRecordIdParam.getId());
+    }
+
+    @Override
+    public BizUserFundChangeRecord queryEntity(String id) {
+        BizUserFundChangeRecord bizUserFundChangeRecord = this.getById(id);
+        if(ObjectUtil.isEmpty(bizUserFundChangeRecord)) {
+            throw new CommonException("用户账户资金变动记录不存在,id值为:{}", id);
+        }
+        return bizUserFundChangeRecord;
+    }
+}

+ 177 - 0
snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/wx/WxPayNotifyController.java

@@ -0,0 +1,177 @@
+package vip.xiaonuo.biz.modular.wx;
+
+import cn.hutool.core.date.DateTime;
+import cn.hutool.core.date.DateUtil;
+import cn.hutool.core.lang.ObjectId;
+import cn.hutool.core.util.IdUtil;
+import cn.hutool.core.util.ObjectUtil;
+import com.alibaba.fastjson.JSONObject;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.wechat.pay.java.core.RSAAutoCertificateConfig;
+import com.wechat.pay.java.core.notification.NotificationParser;
+import com.wechat.pay.java.core.notification.RequestParam;
+import com.wechat.pay.java.service.partnerpayments.app.model.Transaction;
+import jakarta.annotation.Resource;
+import jakarta.servlet.http.HttpServletRequest;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RestController;
+import vip.xiaonuo.biz.api.BizUserApi;
+import vip.xiaonuo.biz.modular.couponrecord.entity.BizCouponRecord;
+import vip.xiaonuo.biz.modular.couponrecord.service.BizCouponRecordService;
+import vip.xiaonuo.biz.modular.rebaterecord.entity.BizRebateRecord;
+import vip.xiaonuo.biz.modular.rebaterecord.service.BizRebateRecordService;
+import vip.xiaonuo.biz.modular.rechargerecord.entity.BizRechargeRecord;
+import vip.xiaonuo.biz.modular.rechargerecord.service.BizRechargeRecordService;
+import vip.xiaonuo.biz.modular.user.entity.BizUser;
+import vip.xiaonuo.biz.modular.user.mapper.BizUserMapper;
+import vip.xiaonuo.biz.modular.user.service.BizUserService;
+import vip.xiaonuo.common.wx.HttpServletUtils;
+import vip.xiaonuo.sys.api.SysUserApi;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author wulei
+ * @date 2024/11/1
+ * @Version 1.0
+ * @Description 微信支付回调
+ */
+@RestController
+@Slf4j
+public class WxPayNotifyController {
+
+    @Resource
+    private RSAAutoCertificateConfig rsaAutoCertificateConfig;
+    @Resource
+    private BizRechargeRecordService bizRechargeRecordService;
+    @Resource
+    private BizUserMapper bizUserMapper;
+    @Resource
+    private BizCouponRecordService bizCouponRecordService;
+    @Resource
+    private BizRebateRecordService bizRebateRecordService;
+
+    /**
+     * 微信支付回调通知
+     *
+     * @param request
+     * @return
+     * @throws Exception
+     */
+    @PostMapping("/biz/wx/notify")
+    @Transactional(rollbackFor = Exception.class)
+    public synchronized String payNotify(HttpServletRequest request) throws Exception {
+        log.info("------收到支付通知------");
+        // 请求头Wechatpay-Signature
+        String signature = request.getHeader("Wechatpay-Signature");
+        // 请求头Wechatpay-nonce
+        String nonce = request.getHeader("Wechatpay-Nonce");
+        // 请求头Wechatpay-Timestamp
+        String timestamp = request.getHeader("Wechatpay-Timestamp");
+        // 微信支付证书序列号
+        String serial = request.getHeader("Wechatpay-Serial");
+        // 签名方式
+        String signType = request.getHeader("Wechatpay-Signature-Type");
+        Map<String, String> returnMap = new HashMap<>(2);
+        try {
+            RequestParam requestParam = new RequestParam.Builder()
+                    .serialNumber(serial)
+                    .nonce(nonce)
+                    .signature(signature)
+                    .timestamp(timestamp)
+                    .signType(signType)
+                    .body(HttpServletUtils.getRequestBody(request))
+                    .build();
+
+            returnMap.put("code", "FAIL");
+            returnMap.put("message", "失败");
+            // 初始化 NotificationParser
+            NotificationParser parser = new NotificationParser(rsaAutoCertificateConfig);
+            // 以支付通知回调为例,验签、解密并转换成 Transaction
+            log.info("验签参数:{}", requestParam);
+            Transaction transaction = parser.parse(requestParam, Transaction.class);
+            log.info("验签成功!-支付回调结果:{}", transaction.toString());
+            //实际支付金额
+            BigDecimal realPayAmount = new BigDecimal(transaction.getAmount().getPayerTotal()).divide(new BigDecimal("100"), 2, RoundingMode.HALF_UP);
+            //修改订单前,建议主动请求微信查询订单是否支付成功,防止恶意post
+            BizRechargeRecord bizRechargeRecord = bizRechargeRecordService.getOne(new LambdaQueryWrapper<BizRechargeRecord>().eq(BizRechargeRecord::getOrderNo, transaction.getOutTradeNo()));
+            if (ObjectUtil.isNull(bizRechargeRecord)) {
+                log.info("未查询到系统订单编号所关联的充值记录:{}========", transaction.getOutTradeNo());
+                return JSONObject.toJSONString(returnMap);
+            }
+            //如果已经成功无需再次更新
+            if (bizRechargeRecord.getIsPay()) {
+                returnMap.put("code", "SUCCESS");
+                returnMap.put("message", "成功");
+                return JSONObject.toJSONString(returnMap);
+            }
+            // 查询用户信息
+            log.info("=========== 更新用户账户余额 ========");
+            BizUser bizUser = bizUserMapper.selectById(bizRechargeRecord.getUserId());
+            if (ObjectUtil.isNull(bizUser)) {
+                log.info("未查询到用户信息:{}========", bizRechargeRecord.getUserId());
+                return JSONObject.toJSONString(returnMap);
+            }
+            bizRechargeRecord.setOldAccountBalance(bizUser.getAccountBalance());
+            bizRechargeRecord.setOldVoucherBalance(bizUser.getVoucherBalance());
+            //计算新金额
+            if (ObjectUtil.isNotEmpty(bizRechargeRecord.getRechargePlanId())) {
+                //有充值计划
+                bizUser.setAccountBalance( bizUser.getAccountBalance().add(bizRechargeRecord.getPlanAccountBalance()));
+            } else {
+                bizUser.setAccountBalance(bizUser.getAccountBalance().add(realPayAmount));
+            }
+            bizUserMapper.updateById(bizUser);
+            //更新充值记录
+            log.info("=========== 更新充值记录 ========");
+            bizRechargeRecord.setNewAccountBalance(bizUser.getAccountBalance());
+            bizRechargeRecord.setPayTime(DateUtil.parse(transaction.getSuccessTime()));
+            bizRechargeRecord.setIsPay(Boolean.TRUE);
+            bizRechargeRecord.setWxOrderNo(transaction.getTransactionId());
+            bizRechargeRecord.setWxPayAmount(realPayAmount);
+            bizRechargeRecordService.updateById(bizRechargeRecord);
+            //生成蛋糕券
+            if (bizRechargeRecord.getCouponNum() != null && bizRechargeRecord.getCouponNum() > 0) {
+                log.info("=========== 生成优惠券 ========");
+                DateTime date = DateUtil.date();
+                for (int i = 0; i < bizRechargeRecord.getCouponNum(); i++) {
+                    BizCouponRecord bizCouponRecord = new BizCouponRecord();
+                    bizCouponRecord.setCouponNo(IdUtil.objectId());
+                    bizCouponRecord.setTime(date);
+                    bizCouponRecord.setRechargePlanId(bizRechargeRecord.getRechargePlanId());
+                    bizCouponRecord.setRechargeRecordId(bizRechargeRecord.getId());
+                    bizCouponRecordService.save(bizCouponRecord);
+                }
+            }
+            // 生成推荐人返点
+            if (ObjectUtil.isNotEmpty(bizUser.getReferralUser())) {
+                log.info("=========== 生成推荐人返点 ========");
+                BizRebateRecord bizRebateRecord = new BizRebateRecord();
+                bizRebateRecord.setRebateAmout(bizRechargeRecord.getRebateAmount());
+                bizRebateRecord.setRechargeUserId(bizRechargeRecord.getUserId());
+                bizRebateRecord.setRecommendUserId(bizUser.getReferralUser());
+                bizRebateRecordService.save(bizRebateRecord);
+            }
+            // 记录用户账户资金变动记录
+            log.info("=========== 记录用户账户资金变动记录 ========");
+
+
+
+
+            log.info("微信订单回调成功------------------");
+        } catch (Exception e) {
+            log.error("wx pay error , 支付回调异常:{}", e.getMessage());
+            returnMap.put("code", "FAIL");
+            returnMap.put("message", "失败");
+            return JSONObject.toJSONString(returnMap);
+        }
+        returnMap.put("code", "SUCCESS");
+        returnMap.put("message", "成功");
+        return JSONObject.toJSONString(returnMap);
+    }
+}