Bladeren bron

feat(income): add merchant income management features

- Implemented APIs for payment cycle query with income type filtering
- Added account balance inquiry functionality with detailed breakdown
- Created cash-out application endpoint with password validation
- Built cash-out record search with date range and status filters
- Developed group income query with refund and commission handling
- Added fast cash-out feature bypassing manual review
- Included DTOs and service layer implementations for all features
- Integrated logging and error handling across all operations
- Supported pagination and data formatting in all list responses
- Ensured transaction safety for financial operations like withdrawals
wxd 3 weken geleden
bovenliggende
commit
323507f6f9

+ 176 - 0
alien-store-platform/src/main/java/shop/alien/storeplatform/controller/IncomeManageController.java

@@ -0,0 +1,176 @@
+package shop.alien.storeplatform.controller;
+
+import com.alibaba.fastjson2.JSONObject;
+import io.swagger.annotations.*;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.*;
+import shop.alien.entity.result.R;
+import shop.alien.storeplatform.service.IncomeManageService;
+import shop.alien.storeplatform.util.LoginUserUtil;
+
+import java.util.Map;
+
+/**
+ * web端商户收入管理 前端控制器
+ *
+ * @author ssk
+ * @since 2025-11-14
+ */
+@Slf4j
+@Api(tags = {"web端商户收入管理"})
+@CrossOrigin
+@RestController
+@RequestMapping("/incomeManage")
+@RequiredArgsConstructor
+public class IncomeManageController {
+
+    private final IncomeManageService incomeManageService;
+
+    @ApiOperation("账期查询")
+    @ApiOperationSupport(order = 1)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "storeId", value = "门店ID", dataType = "Integer", paramType = "query", required = true),
+            @ApiImplicitParam(name = "incomeType", value = "收入类型, 0:主页, 1:优惠券, 2:代金券, 3:套餐, 4:联名卡", dataType = "Integer", paramType = "query"),
+            @ApiImplicitParam(name = "paymentType", value = "账期类型, 0:未到, 1:已到", dataType = "Integer", paramType = "query"),
+            @ApiImplicitParam(name = "startTime", value = "开始时间(格式:yyyy-MM-dd)", dataType = "String", paramType = "query", required = true),
+            @ApiImplicitParam(name = "endTime", value = "结束时间(格式:yyyy-MM-dd)", dataType = "String", paramType = "query", required = true),
+            @ApiImplicitParam(name = "page", value = "页码", dataType = "Integer", paramType = "query"),
+            @ApiImplicitParam(name = "size", value = "每页条数", dataType = "Integer", paramType = "query")
+    })
+    @GetMapping("/getPaymentCycle")
+    public R<JSONObject> getPaymentCycle(
+            @RequestParam(value = "incomeType", required = false) Integer incomeType,
+            @RequestParam(value = "paymentType", required = false) Integer paymentType,
+            @RequestParam(value = "startTime", required = false) String startTime,
+            @RequestParam(value = "endTime", required = false) String endTime,
+            @RequestParam(value = "page", defaultValue = "1") int page,
+            @RequestParam(value = "size", defaultValue = "10") int size) {
+        log.info("IncomeManageController.getPaymentCycle? incomeType={}, paymentType={}, startTime={}, endTime={}, page={}, size={}",
+                incomeType, paymentType, startTime, endTime, page, size);
+        try {
+            Integer storeId = LoginUserUtil.getCurrentStoreId();
+            JSONObject result = incomeManageService.getPaymentCycle(storeId, incomeType, paymentType, startTime, endTime, page, size);
+            return R.data(result);
+        } catch (Exception e) {
+            log.error("IncomeManageController.getPaymentCycle ERROR: {}", e.getMessage(), e);
+            return R.fail("查询失败:" + e.getMessage());
+        }
+    }
+
+    @ApiOperation("查询账户余额")
+    @ApiOperationSupport(order = 2)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "storeId", value = "门店ID", dataType = "Integer", paramType = "query", required = true)
+    })
+    @GetMapping("/getAccountBalance")
+    public R<Map<String, Object>> getAccountBalance() {
+        try {
+            Integer storeId = LoginUserUtil.getCurrentStoreId();
+            Map<String, Object> result = incomeManageService.getAccountBalance(storeId);
+            return R.data(result);
+        } catch (Exception e) {
+            log.error("IncomeManageController.getAccountBalance ERROR: {}", e.getMessage(), e);
+            return R.fail("查询失败:" + e.getMessage());
+        }
+    }
+
+    @ApiOperation("提现申请")
+    @ApiOperationSupport(order = 3)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "storeId", value = "门店ID", dataType = "Integer", paramType = "query", required = true),
+            @ApiImplicitParam(name = "payPassword", value = "支付密码", dataType = "String", paramType = "query", required = true),
+            @ApiImplicitParam(name = "withdrawalMoney", value = "提现金额(单位:分)", dataType = "Integer", paramType = "query", required = true)
+    })
+    @PostMapping("/cashOut")
+    public R<?> cashOut(
+            @RequestParam("payPassword") String payPassword,
+            @RequestParam("withdrawalMoney") Integer withdrawalMoney) {
+        log.info("IncomeManageController.cashOut? payPassword=***, withdrawalMoney={}",
+                 withdrawalMoney);
+        try {
+            Integer storeId = LoginUserUtil.getCurrentStoreId();
+            return incomeManageService.cashOut(storeId, payPassword, withdrawalMoney);
+        } catch (Exception e) {
+            log.error("IncomeManageController.cashOut ERROR: {}", e.getMessage(), e);
+            return R.fail("提现失败:" + e.getMessage());
+        }
+    }
+
+    @ApiOperation("提现记录查询")
+    @ApiOperationSupport(order = 4)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "storeId", value = "门店ID", dataType = "Integer", paramType = "query", required = true),
+            @ApiImplicitParam(name = "cashOutStartTime", value = "开始时间(格式:yyyy-MM-dd)", dataType = "String", paramType = "query"),
+            @ApiImplicitParam(name = "cashOutEndTime", value = "结束时间(格式:yyyy-MM-dd)", dataType = "String", paramType = "query"),
+            @ApiImplicitParam(name = "paymentStatus", value = "提现状态(1-待审核, 2-审核不通过, 3-已通过, 4-已打款, 5-打款失败)", dataType = "String", paramType = "query"),
+            @ApiImplicitParam(name = "page", value = "页码", dataType = "Integer", paramType = "query"),
+            @ApiImplicitParam(name = "size", value = "每页条数", dataType = "Integer", paramType = "query")
+    })
+    @GetMapping("/getCashOutRecordList")
+    public R<?> getCashOutRecordList(
+            @RequestParam(value = "cashOutStartTime", required = false) String cashOutStartTime,
+            @RequestParam(value = "cashOutEndTime", required = false) String cashOutEndTime,
+            @RequestParam(value = "paymentStatus", required = false) String paymentStatus,
+            @RequestParam(value = "page", defaultValue = "1") Integer page,
+            @RequestParam(value = "size", defaultValue = "10") Integer size) {
+        log.info("IncomeManageController.getCashOutRecordList? cashOutStartTime={}, cashOutEndTime={}, paymentStatus={}, page={}, size={}",
+                 cashOutStartTime, cashOutEndTime, paymentStatus, page, size);
+        try {
+            Integer storeId = LoginUserUtil.getCurrentStoreId();
+            Object result = incomeManageService.getCashOutRecordList(storeId, cashOutStartTime, cashOutEndTime, paymentStatus, page, size);
+            return R.data(result);
+        } catch (Exception e) {
+            log.error("IncomeManageController.getCashOutRecordList ERROR: {}", e.getMessage(), e);
+            return R.fail("查询失败:" + e.getMessage());
+        }
+    }
+
+    @ApiOperation("团购收益查询")
+    @ApiOperationSupport(order = 5)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "date", value = "日期(格式:yyyy-MM-dd)", dataType = "String", paramType = "query", required = true),
+            @ApiImplicitParam(name = "incomeType", value = "收入类型, 0:主页, 1:优惠券, 2:代金券, 3:套餐, 4:联名卡", dataType = "Integer", paramType = "query"),
+            @ApiImplicitParam(name = "page", value = "页码", dataType = "Integer", paramType = "query"),
+            @ApiImplicitParam(name = "size", value = "每页条数", dataType = "Integer", paramType = "query")
+    })
+    @GetMapping("/getGroupIncome")
+    public R<?> getGroupIncome(
+            @RequestParam("date") String date,
+            @RequestParam(value = "incomeType", required = false) Integer incomeType,
+            @RequestParam(value = "page", defaultValue = "1") Integer page,
+            @RequestParam(value = "size", defaultValue = "10") Integer size) {
+        log.info("IncomeManageController.getGroupIncome?date={}, incomeType={}, page={}, size={}",
+                date, incomeType, page, size);
+        try {
+            Integer storeId = LoginUserUtil.getCurrentStoreId();
+            Object result = incomeManageService.getGroupIncome(storeId, date, incomeType, page, size);
+            return R.data(result);
+        } catch (Exception e) {
+            log.error("IncomeManageController.getGroupIncome ERROR: {}", e.getMessage(), e);
+            return R.fail("查询失败:" + e.getMessage());
+        }
+    }
+
+    @ApiOperation("快速提现申请(免审核)")
+    @ApiOperationSupport(order = 6)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "payPassword", value = "支付密码", dataType = "String", paramType = "query", required = true),
+            @ApiImplicitParam(name = "withdrawalMoney", value = "提现金额(单位:分)", dataType = "Integer", paramType = "query", required = true)
+    })
+    @PostMapping("/applyFastCashOut")
+    public R<?> applyFastCashOut(
+            @RequestParam("payPassword") String payPassword,
+            @RequestParam("withdrawalMoney") Integer withdrawalMoney) {
+        log.info("IncomeManageController.applyFastCashOut? payPassword=***, withdrawalMoney={}",
+                withdrawalMoney);
+        try {
+            Integer storeId = LoginUserUtil.getCurrentStoreId();
+            return incomeManageService.applyFastCashOut(storeId, payPassword, withdrawalMoney);
+        } catch (Exception e) {
+            log.error("IncomeManageController.applyFastCashOut ERROR: {}", e.getMessage(), e);
+            return R.fail("快速提现失败:" + e.getMessage());
+        }
+    }
+}
+

+ 59 - 0
alien-store-platform/src/main/java/shop/alien/storeplatform/controller/MerchantUserController.java

@@ -7,6 +7,8 @@ import org.springframework.web.bind.annotation.*;
 import shop.alien.entity.result.R;
 import shop.alien.entity.store.StoreUser;
 import shop.alien.entity.store.vo.StoreUserVo;
+import shop.alien.storeplatform.dto.AddAlipayAccountDTO;
+import shop.alien.storeplatform.dto.SetPayPasswordDTO;
 import shop.alien.storeplatform.service.MerchantUserService;
 
 import java.util.Map;
@@ -85,6 +87,63 @@ public class MerchantUserController {
             return R.fail("查询失败:" + e.getMessage());
         }
     }
+
+    @ApiOperation("设置/修改支付密码")
+    @ApiOperationSupport(order = 4)
+    @PostMapping("/setPayPassword")
+    public R<Boolean> setPayPassword(@RequestBody SetPayPasswordDTO dto) {
+        log.info("MerchantUserController.setPayPassword - 设置支付密码");
+        try {
+            // 从token中获取当前登录用户的ID
+            Integer userId = shop.alien.storeplatform.util.LoginUserUtil.getCurrentUserId();
+            
+            boolean success = merchantUserService.setPayPassword(userId, dto.getPayPassword());
+            if (success) {
+                return R.success("设置成功");
+            }
+            return R.fail("设置失败");
+        } catch (Exception e) {
+            log.error("MerchantUserController.setPayPassword ERROR: {}", e.getMessage(), e);
+            return R.fail("设置失败:" + e.getMessage());
+        }
+    }
+
+    @ApiOperation("添加/修改支付宝账号")
+    @ApiOperationSupport(order = 5)
+    @PostMapping("/addAlipayAccount")
+    public R<Integer> addAlipayAccount(@RequestBody AddAlipayAccountDTO dto) {
+        log.info("MerchantUserController.addAlipayAccount - 添加支付宝账号");
+        try {
+            // 从token中获取当前登录用户的ID
+            Integer userId = shop.alien.storeplatform.util.LoginUserUtil.getCurrentUserId();
+            
+            Integer result = merchantUserService.addAlipayAccount(userId, dto.getAlipayAccount());
+            if (result > 0) {
+                return R.data(result, "添加成功");
+            }
+            return R.fail("添加失败");
+        } catch (Exception e) {
+            log.error("MerchantUserController.addAlipayAccount ERROR: {}", e.getMessage(), e);
+            return R.fail("添加失败:" + e.getMessage());
+        }
+    }
+
+    @ApiOperation("重置商户到刚注册状态")
+    @ApiOperationSupport(order = 6)
+    @PostMapping("/resetToInitialStatus")
+    public R<Boolean> resetToInitialStatus() {
+        log.info("MerchantUserController.resetToInitialStatus - 重置商户状态");
+        try {
+            boolean success = merchantUserService.resetToInitialStatus();
+            if (success) {
+                return R.data(true, "重置成功,已退回到刚注册状态");
+            }
+            return R.fail("重置失败");
+        } catch (Exception e) {
+            log.error("MerchantUserController.resetToInitialStatus ERROR: {}", e.getMessage(), e);
+            return R.fail("重置失败:" + e.getMessage());
+        }
+    }
 }
 
 

+ 1 - 1
alien-store-platform/src/main/java/shop/alien/storeplatform/controller/NoticeController.java

@@ -74,7 +74,7 @@ public class NoticeController {
     @ApiImplicitParams({
             @ApiImplicitParam(name = "id", value = "通知ID", dataType = "Integer", paramType = "query", required = true)
     })
-    @PostMapping("/markNoticeAsRead")
+    @GetMapping("/markNoticeAsRead")
     public R<Boolean> markNoticeAsRead(@RequestParam("id") Integer id) {
         log.info("NoticeController.markNoticeAsRead?id={}", id);
         try {

+ 24 - 0
alien-store-platform/src/main/java/shop/alien/storeplatform/dto/AddAlipayAccountDTO.java

@@ -0,0 +1,24 @@
+package shop.alien.storeplatform.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 添加支付宝账号DTO
+ *
+ * @author ssk
+ * @since 2025-11-20
+ */
+@Data
+@ApiModel("添加支付宝账号DTO")
+public class AddAlipayAccountDTO implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty(value = "支付宝账号(邮箱或手机号)", required = true, example = "alipay@example.com")
+    private String alipayAccount;
+}
+

+ 2 - 0
alien-store-platform/src/main/java/shop/alien/storeplatform/dto/MarkAllNoticesReadDTO.java

@@ -27,3 +27,5 @@ public class MarkAllNoticesReadDTO implements Serializable {
 
 
 
+
+

+ 24 - 0
alien-store-platform/src/main/java/shop/alien/storeplatform/dto/SetPayPasswordDTO.java

@@ -0,0 +1,24 @@
+package shop.alien.storeplatform.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 设置支付密码DTO
+ *
+ * @author ssk
+ * @since 2025-11-20
+ */
+@Data
+@ApiModel("设置支付密码DTO")
+public class SetPayPasswordDTO implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty(value = "支付密码(6位数字)", required = true, example = "123456")
+    private String payPassword;
+}
+

+ 2 - 0
alien-store-platform/src/main/java/shop/alien/storeplatform/example/LoginUserUtilExample.java

@@ -255,3 +255,5 @@ public class LoginUserUtilExample {
 
 
 
+
+

+ 86 - 0
alien-store-platform/src/main/java/shop/alien/storeplatform/service/IncomeManageService.java

@@ -0,0 +1,86 @@
+package shop.alien.storeplatform.service;
+
+import com.alibaba.fastjson2.JSONObject;
+import shop.alien.entity.result.R;
+
+import java.util.Map;
+
+/**
+ * web端商户收入管理服务接口
+ *
+ * @author ssk
+ * @since 2025-11-14
+ */
+public interface IncomeManageService {
+
+    /**
+     * 账期查询
+     *
+     * @param storeId     门店ID
+     * @param incomeType  收入类型, 0:主页, 1:优惠券, 2:代金券, 3:套餐, 4:联名卡
+     * @param paymentType 账期类型, 0:未到, 1:已到
+     * @param startTime   开始时间
+     * @param endTime     结束时间
+     * @param page        页码
+     * @param size        每页条数
+     * @return 账期数据
+     */
+    JSONObject getPaymentCycle(Integer storeId, Integer incomeType, Integer paymentType,
+                               String startTime, String endTime, int page, int size);
+
+    /**
+     * 查询账户余额
+     *
+     * @param storeId 门店ID
+     * @return 账户余额信息(balance: 总余额, cashOutMoney: 可提现金额)
+     */
+    Map<String, Object> getAccountBalance(Integer storeId);
+
+    /**
+     * 提现申请
+     *
+     * @param storeId         门店ID
+     * @param payPassword     支付密码
+     * @param withdrawalMoney 提现金额(单位:分)
+     * @return 提现结果
+     */
+    R<?> cashOut(Integer storeId, String payPassword, Integer withdrawalMoney);
+
+    /**
+     * 提现记录查询
+     *
+     * @param storeId          门店ID
+     * @param cashOutStartTime 开始时间(格式:yyyy-MM-dd)
+     * @param cashOutEndTime   结束时间(格式:yyyy-MM-dd)
+     * @param paymentStatus    提现状态
+     * @param page             页码
+     * @param size             每页条数
+     * @return 提现记录列表
+     */
+    Object getCashOutRecordList(Integer storeId, String cashOutStartTime, String cashOutEndTime,
+                                String paymentStatus, Integer page, Integer size);
+
+    /**
+     * 团购收益查询
+     *
+     * @param storeId    门店ID
+     * @param date       日期(格式:yyyy-MM-dd)
+     * @param incomeType 收入类型, 0:主页, 1:优惠券, 2:代金券, 3:套餐, 4:联名卡
+     * @param page       页码
+     * @param size       每页条数
+     * @return 团购收益信息
+     */
+    Object getGroupIncome(Integer storeId, String date, Integer incomeType, Integer page, Integer size);
+
+    /**
+     * 快速提现申请(免审核,直接通过)
+     *
+     * @param storeId         门店ID
+     * @param payPassword     支付密码
+     * @param withdrawalMoney 提现金额(单位:分)
+     * @return 提现结果
+     */
+    R<?> applyFastCashOut(Integer storeId, String payPassword, Integer withdrawalMoney);
+}
+
+

+ 26 - 0
alien-store-platform/src/main/java/shop/alien/storeplatform/service/MerchantUserService.java

@@ -37,6 +37,32 @@ public interface MerchantUserService {
      * @return 包含验证结果的Map
      */
     Map<String, Object> checkPayPassword(String storeUserId, String password);
+
+    /**
+     * 设置/修改支付密码
+     *
+     * @param id          用户ID
+     * @param payPassword 支付密码
+     * @return 是否设置成功
+     */
+    boolean setPayPassword(Integer id, String payPassword);
+
+    /**
+     * 添加/修改支付宝账号
+     *
+     * @param userId         用户ID
+     * @param alipayAccount  支付宝账号
+     * @return 影响的行数
+     */
+    Integer addAlipayAccount(Integer userId, String alipayAccount);
+
+    /**
+     * 重置商户到刚注册状态
+     * (一键退回:删除店铺申请、解绑店铺、清理相关数据)
+     *
+     * @return 是否重置成功
+     */
+    boolean resetToInitialStatus();
 }
 
 

+ 640 - 0
alien-store-platform/src/main/java/shop/alien/storeplatform/service/impl/IncomeManageServiceImpl.java

@@ -0,0 +1,640 @@
+package shop.alien.storeplatform.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.BeanUtils;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import shop.alien.entity.result.R;
+import shop.alien.entity.store.StoreCashOutRecord;
+import shop.alien.entity.store.StoreIncomeDetailsRecord;
+import shop.alien.entity.store.StoreInfo;
+import shop.alien.entity.store.StoreUser;
+import shop.alien.entity.store.vo.StoreCashOutRecordVo;
+import shop.alien.entity.store.vo.StoreIncomeDetailsRecordVo;
+import shop.alien.mapper.StoreCashOutRecordMapper;
+import shop.alien.mapper.StoreIncomeDetailsRecordMapper;
+import shop.alien.mapper.StoreInfoMapper;
+import shop.alien.mapper.StoreUserMapper;
+import shop.alien.storeplatform.service.IncomeManageService;
+import shop.alien.util.common.ListToPage;
+import shop.alien.util.common.constant.CouponTypeEnum;
+import shop.alien.util.date.DateUtils;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.text.SimpleDateFormat;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * web端商户收入管理服务实现
+ *
+ * @author ssk
+ * @since 2025-11-14
+ */
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class IncomeManageServiceImpl extends ServiceImpl<StoreIncomeDetailsRecordMapper, StoreIncomeDetailsRecord> implements IncomeManageService {
+
+    private final StoreIncomeDetailsRecordMapper storeIncomeDetailsRecordMapper;
+    private final StoreInfoMapper storeInfoMapper;
+    private final StoreUserMapper storeUserMapper;
+    private final StoreCashOutRecordMapper storeCashOutRecordMapper;
+
+    @Override
+    public com.alibaba.fastjson2.JSONObject getPaymentCycle(Integer storeId, Integer incomeType, Integer paymentType,
+                                      String startTime, String endTime, int page, int size) {
+        log.info("IncomeManageServiceImpl.getPaymentCycle - 开始查询账期: storeId={}, incomeType={}, paymentType={}, startTime={}, endTime={}, page={}, size={}",
+                storeId, incomeType, paymentType, startTime, endTime, page, size);
+
+        QueryWrapper<StoreIncomeDetailsRecord> wrapper = new QueryWrapper<>();
+        com.alibaba.fastjson2.JSONObject jsonObject = new com.alibaba.fastjson2.JSONObject();
+        Date now = new Date();
+        SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd");
+
+        // 1. 根据账期类型设置查询条件
+        if (paymentType != null && paymentType == 0) {
+            // 未到账期: 未绑定提现记录 & 当前时间-3天大于创建时间
+            wrapper.isNull("sidr.cash_out_id");
+            wrapper.gt("sidr.created_time", DateUtils.calcDays(new Date(), -3));
+            Date startDate = DateUtils.calcDays(now, -3);
+            jsonObject.put("date", df.format(startDate) + " ~ " + df.format(now));
+            log.debug("IncomeManageServiceImpl.getPaymentCycle - 未到账期: date={}", jsonObject.get("date"));
+        } else if (paymentType != null && paymentType == 1) {
+            // 已到账期: 已绑定提现记录 & 当前时间-4~27天大于创建时间
+            wrapper.isNotNull("sidr.cash_out_id");
+            wrapper.between("sidr.created_time", DateUtils.calcDays(new Date(), -27), DateUtils.calcDays(new Date(), -4));
+            Date startDate = DateUtils.calcDays(now, -27);
+            Date endDate = DateUtils.calcDays(now, -4);
+            jsonObject.put("date", df.format(startDate) + " ~ " + df.format(endDate));
+            log.debug("IncomeManageServiceImpl.getPaymentCycle - 已到账期: date={}", jsonObject.get("date"));
+        }
+
+        // 2. 门店ID条件
+        if (storeId != null) {
+            wrapper.eq("sidr.store_id", storeId);
+        }
+
+        // 3. 时间范围条件(用户选择的时间范围)
+        if (StringUtils.isNotBlank(startTime) && StringUtils.isNotBlank(endTime)) {
+            LocalDate startDate = LocalDate.parse(startTime);
+            LocalDate endDate = LocalDate.parse(endTime);
+            LocalDateTime startOfDay = startDate.atStartOfDay();
+            LocalDateTime endOfDay = endDate.atTime(LocalTime.MAX);
+            wrapper.between("sidr.created_time", startOfDay, endOfDay)
+                    .orderByDesc("sidr.created_time");
+        }
+
+        // 4. 收入类型条件
+        if (null != incomeType) {
+            if (0 == incomeType) {
+                // 主页: 包含优惠券和代金券
+                wrapper.in("sidr.income_type", CouponTypeEnum.COUPON.getCode(), CouponTypeEnum.GROUP_BUY.getCode());
+                log.debug("IncomeManageServiceImpl.getPaymentCycle - 收入类型: 主页(优惠券+代金券)");
+            } else {
+                wrapper.eq("sidr.income_type", incomeType);
+                log.debug("IncomeManageServiceImpl.getPaymentCycle - 收入类型: {}", incomeType);
+            }
+        }
+
+        // 5. 查询收入明细记录列表
+        List<StoreIncomeDetailsRecordVo> list = storeIncomeDetailsRecordMapper.selectRecordList(wrapper);
+        log.info("IncomeManageServiceImpl.getPaymentCycle - 查询到收入记录数: {}", list.size());
+
+        // 6. 查询门店信息(用于获取抽成比例)
+        StoreInfo storeInfo = storeInfoMapper.selectById(storeId);
+        if (storeInfo == null) {
+            log.warn("IncomeManageServiceImpl.getPaymentCycle - 门店不存在: storeId={}", storeId);
+            throw new RuntimeException("门店不存在");
+        }
+
+        // 7. 处理列表数据:格式化金额、日期
+        for (StoreIncomeDetailsRecordVo storeIncomeDetailsRecord : list) {
+            if (storeIncomeDetailsRecord == null) {
+                continue;
+            }
+            
+            StoreIncomeDetailsRecordVo vo = new StoreIncomeDetailsRecordVo();
+            BeanUtils.copyProperties(storeIncomeDetailsRecord, vo);
+            
+            // 将金额从分转换为元(保留2位小数)
+            storeIncomeDetailsRecord.setMoneyStr(
+                    new BigDecimal(vo.getMoney())
+                            .divide(new BigDecimal(100), 2, RoundingMode.HALF_UP)
+                            .toString()
+            );
+            
+            // 格式化日期
+            String format = df.format(storeIncomeDetailsRecord.getCreatedTime());
+            storeIncomeDetailsRecord.setDate(format);
+            
+            // 设置抽成比例
+            vo.setCommissionRate(storeInfo.getCommissionRate());
+        }
+
+        // 8. 手动分页
+        jsonObject.put("data", ListToPage.setPage(list, page, size));
+
+        // 9. 计算总金额(元)
+        int totalMoney = list.stream().mapToInt(StoreIncomeDetailsRecord::getMoney).sum();
+        jsonObject.put("money",
+                new BigDecimal(totalMoney)
+                        .divide(new BigDecimal(100), 2, RoundingMode.HALF_UP)
+                        .toString()
+        );
+
+        log.info("IncomeManageServiceImpl.getPaymentCycle - 查询完成: 总记录数={}, 总金额={}元",
+                list.size(), jsonObject.get("money"));
+
+        return jsonObject;
+    }
+
+    @Override
+    public Map<String, Object> getAccountBalance(Integer storeId) {
+        log.info("IncomeManageServiceImpl.getAccountBalance - 开始查询账户余额: storeId={}", storeId);
+
+        Map<String, Object> map = new HashMap<>();
+
+        // 1. 查询店铺用户信息,获取账户总余额
+        LambdaQueryWrapper<StoreUser> storeUserWrapper = new LambdaQueryWrapper<>();
+        storeUserWrapper.eq(StoreUser::getStoreId, storeId);
+        StoreUser storeUser = storeUserMapper.selectOne(storeUserWrapper);
+
+        if (storeUser == null) {
+            log.warn("IncomeManageServiceImpl.getAccountBalance - 店铺用户不存在: storeId={}", storeId);
+            throw new RuntimeException("店铺用户不存在");
+        }
+
+        // 2. 账户总余额(单位:元,保留2位小数)
+        String balance = new BigDecimal(storeUser.getMoney())
+                .divide(new BigDecimal(100), 2, RoundingMode.HALF_UP)
+                .toString();
+        map.put("balance", balance);
+        log.debug("IncomeManageServiceImpl.getAccountBalance - 账户总余额: {}元", balance);
+
+        // 3. 查询可提现金额(4~27天内的收入,且未绑定提现记录)
+        LambdaQueryWrapper<StoreIncomeDetailsRecord> wrapper = new LambdaQueryWrapper<>();
+        wrapper.between(StoreIncomeDetailsRecord::getCreatedTime,
+                        DateUtils.calcDays(new Date(), -27),
+                        DateUtils.calcDays(new Date(), -4))
+                .isNull(StoreIncomeDetailsRecord::getCashOutId)  // 未绑定提现记录
+                .eq(StoreIncomeDetailsRecord::getStoreId, storeId);
+
+        List<StoreIncomeDetailsRecord> incomeList = storeIncomeDetailsRecordMapper.selectList(wrapper);
+        
+        // 计算可提现收入总额(分)
+        int cashOutMoney = incomeList.stream()
+                .mapToInt(StoreIncomeDetailsRecord::getMoney)
+                .sum();
+        
+        log.debug("IncomeManageServiceImpl.getAccountBalance - 4~27天内收入总额: {}分", cashOutMoney);
+
+        // 4. 查询已提现和待审核的金额(需要扣除)
+        // payment_status: 1-待审核, 3-已提现
+        QueryWrapper<StoreCashOutRecord> cashOutWrapper = new QueryWrapper<>();
+        cashOutWrapper.eq("store_id", storeId)
+                .in("payment_status", "1", "3")
+                .eq("delete_flag", "0");
+        
+        List<StoreCashOutRecord> storeCashOutRecords = storeCashOutRecordMapper.selectList(cashOutWrapper);
+        
+        // 计算已提现和待审核的总金额(分)
+        int totalCashOutAmount = storeCashOutRecords.stream()
+                .collect(Collectors.summingInt(StoreCashOutRecord::getMoney));
+        
+        log.debug("IncomeManageServiceImpl.getAccountBalance - 已提现+待审核金额: {}分", totalCashOutAmount);
+
+        // 5. 计算最终可提现金额 = 可提现收入 - (已提现 + 待审核)
+        BigDecimal finalCashOutMoney = new BigDecimal(cashOutMoney)
+                .subtract(BigDecimal.valueOf(totalCashOutAmount))
+                .divide(new BigDecimal(100), 2, RoundingMode.HALF_UP);
+        
+        map.put("cashOutMoney", finalCashOutMoney.toString());
+
+        log.info("IncomeManageServiceImpl.getAccountBalance - 查询完成: 总余额={}元, 可提现金额={}元",
+                balance, finalCashOutMoney);
+
+        return map;
+    }
+
+    /**
+     * 提现申请
+     * 
+     * @param storeId         门店ID
+     * @param payPassword     支付密码
+     * @param withdrawalMoney 提现金额(单位:分)
+     * @return 提现结果
+     */
+    @Transactional
+    @Override
+    public R<?> cashOut(Integer storeId, String payPassword, Integer withdrawalMoney) {
+        log.info("IncomeManageServiceImpl.cashOut - 开始处理提现: storeId={}, withdrawalMoney={}分",
+                storeId, withdrawalMoney);
+
+        // 1. 验证支付密码并查询用户信息
+        LambdaQueryWrapper<StoreUser> userWrapper = new LambdaQueryWrapper<>();
+        userWrapper.eq(StoreUser::getStoreId, storeId)
+                .eq(StoreUser::getPayPassword, payPassword);
+        StoreUser storeUser = storeUserMapper.selectOne(userWrapper);
+
+        if (storeUser == null) {
+            log.warn("IncomeManageServiceImpl.cashOut - 支付密码错误: storeId={}", storeId);
+            return R.fail("支付密码错误");
+        }
+
+        // 2. 验证账户余额是否充足
+        if (storeUser.getMoney() < withdrawalMoney) {
+            log.warn("IncomeManageServiceImpl.cashOut - 余额不足: 账户余额={}分, 提现金额={}分",
+                    storeUser.getMoney(), withdrawalMoney);
+            return R.fail("余额不足");
+        }
+
+        // 3. 验证提现金额是否满足最低要求(0.1元 = 10分)
+        BigDecimal amountInYuan = new BigDecimal(withdrawalMoney)
+                .divide(new BigDecimal(100), 2, RoundingMode.DOWN);
+        if (amountInYuan.compareTo(new BigDecimal("0.1")) < 0) {
+            log.warn("IncomeManageServiceImpl.cashOut - 提现金额过小: {}元", amountInYuan);
+            return R.fail("金额不能小于0.1元");
+        }
+
+        log.info("IncomeManageServiceImpl.cashOut - 验证通过: 账户余额={}分, 提现金额={}元",
+                storeUser.getMoney(), amountInYuan);
+
+        // 4. 调用支付宝转账
+        // 注意:这里需要集成支付宝API,目前先创建提现申请记录
+        // 实际生产环境需要调用 AliApi.pay() 或 AliApi.payAccount()
+        
+        try {
+            // TODO: 调用支付宝转账API
+            // StoreAliPayLog pay;
+            // if (StringUtils.isNotBlank(storeUser.getAlipayAccount())) {
+            //     pay = aliApi.pay(storeUser.getName(), storeUser.getIdCard(), 
+            //                      storeUser.getAlipayAccount(), amountInYuan.toString());
+            // } else {
+            //     pay = aliApi.payAccount(storeUser.getName(), storeUser.getIdCard(), 
+            //                            null, amountInYuan.toString(), storeUser.getPhone());
+            // }
+            
+            // 5. 创建提现记录(待审核状态)
+            // Web端采用人工审核模式,不直接调用支付宝接口
+            StoreCashOutRecord storeCashOutRecord = new StoreCashOutRecord();
+            storeCashOutRecord.setStoreId(storeId);
+            storeCashOutRecord.setMoney(withdrawalMoney);
+            storeCashOutRecord.setCashOutType(0);
+            storeCashOutRecord.setPaymentStatus(1);  // 1-待审核
+            storeCashOutRecord.setDeleteFlag(0);
+            storeCashOutRecord.setStoreUserId(storeUser.getId());
+            storeCashOutRecord.setPayDate(new Date());
+            
+            // 插入提现记录
+            int insertResult = storeCashOutRecordMapper.insert(storeCashOutRecord);
+            if (insertResult <= 0) {
+                log.error("IncomeManageServiceImpl.cashOut - 创建提现记录失败");
+                throw new RuntimeException("创建提现记录失败");
+            }
+
+            // 6. 扣减账户余额
+            StoreUser updateUser = new StoreUser();
+            updateUser.setId(storeUser.getId());
+            updateUser.setMoney(storeUser.getMoney() - withdrawalMoney);
+            int updateResult = storeUserMapper.updateById(updateUser);
+            if (updateResult <= 0) {
+                log.error("IncomeManageServiceImpl.cashOut - 更新账户余额失败");
+                throw new RuntimeException("更新账户余额失败");
+            }
+
+            log.info("IncomeManageServiceImpl.cashOut - 提现申请成功: 提现记录ID={}, 提现金额={}元",
+                    storeCashOutRecord.getId(), amountInYuan);
+
+            // 7. 返回结果
+            Map<String, Object> result = new HashMap<>();
+            result.put("cashOutRecordId", storeCashOutRecord.getId());
+            result.put("withdrawalMoney", amountInYuan.toString());
+            result.put("status", "pending");  // pending-待审核
+            result.put("message", "提现申请已提交,等待审核");
+            
+            return R.data(result);
+
+        } catch (Exception e) {
+            log.error("IncomeManageServiceImpl.cashOut - 提现失败: {}", e.getMessage(), e);
+            
+            // 如果是已知的运行时异常,直接抛出让事务回滚
+            if (e instanceof RuntimeException) {
+                throw (RuntimeException) e;
+            }
+            
+            // 其他异常也需要回滚
+            throw new RuntimeException("提现失败:" + e.getMessage(), e);
+        }
+    }
+
+    /**
+     * 提现记录查询
+     *
+     * @param storeId          门店ID
+     * @param cashOutStartTime 开始时间
+     * @param cashOutEndTime   结束时间
+     * @param paymentStatus    提现状态
+     * @param page             页码
+     * @param size             每页条数
+     * @return 提现记录列表
+     */
+    @Override
+    public Object getCashOutRecordList(Integer storeId, String cashOutStartTime, String cashOutEndTime,
+                                       String paymentStatus, Integer page, Integer size) {
+        log.info("IncomeManageServiceImpl.getCashOutRecordList - 开始查询提现记录: storeId={}, cashOutStartTime={}, cashOutEndTime={}, paymentStatus={}, page={}, size={}",
+                storeId, cashOutStartTime, cashOutEndTime, paymentStatus, page, size);
+
+        // 1. 构建查询条件(使用QueryWrapper和表别名scor,以配合selectCashoutRecordList的LEFT JOIN查询)
+        QueryWrapper<StoreCashOutRecord> wrapper = new QueryWrapper<>();
+        
+        // 必填条件:门店ID
+        wrapper.eq("scor.store_id", storeId);
+        
+        // 可选条件:提现状态
+        if (StringUtils.isNotBlank(paymentStatus)) {
+            wrapper.eq("scor.payment_status", paymentStatus);
+            log.debug("IncomeManageServiceImpl.getCashOutRecordList - 筛选提现状态: {}", paymentStatus);
+        }
+        
+        // 可选条件:结束时间
+        if (StringUtils.isNotBlank(cashOutEndTime)) {
+            wrapper.le("scor.created_time", cashOutEndTime + " 23:59:59");
+            log.debug("IncomeManageServiceImpl.getCashOutRecordList - 结束时间: {}", cashOutEndTime);
+        }
+        
+        // 可选条件:开始时间
+        if (StringUtils.isNotBlank(cashOutStartTime)) {
+            wrapper.ge("scor.created_time", cashOutStartTime + " 00:00:00");
+            log.debug("IncomeManageServiceImpl.getCashOutRecordList - 开始时间: {}", cashOutStartTime);
+        }
+        
+        // 删除标识(软删除)
+        wrapper.eq("scor.delete_flag", 0);
+        
+        // 按创建时间倒序排序
+        wrapper.orderByDesc("scor.created_time");
+
+        // 2. 查询所有符合条件的记录(LEFT JOIN store_user,获取结算账户信息)
+        List<StoreCashOutRecord> recordList = storeCashOutRecordMapper.selectCashoutRecordList(wrapper);
+        log.info("IncomeManageServiceImpl.getCashOutRecordList - 查询到提现记录数: {}", recordList.size());
+
+        // 3. 手动分页
+        IPage<StoreCashOutRecord> storeCashOutRecordIPage = ListToPage.setPage(recordList, page, size);
+
+        // 4. 构建返回VO
+        StoreCashOutRecordVo vo = new StoreCashOutRecordVo();
+        
+        // 分页后的记录列表
+        vo.setCashOutRecordList(storeCashOutRecordIPage.getRecords());
+        
+        // 提现总金额(仅统计状态为1-待审核的记录)
+        int totalMoney = recordList.stream()
+                .filter(item -> "1".equals(item.getPaymentStatus().toString()))
+                .mapToInt(StoreCashOutRecord::getMoney)
+                .sum();
+        BigDecimal cashOutAllMoney = new BigDecimal(totalMoney)
+                .divide(new BigDecimal(100), 2, RoundingMode.DOWN);
+        vo.setCashOutAllMoney(cashOutAllMoney);
+        
+        // 提现记录总数
+        vo.setCashOutNum(recordList.size());
+
+        log.info("IncomeManageServiceImpl.getCashOutRecordList - 查询完成: 总记录数={}, 总金额={}元",
+                recordList.size(), cashOutAllMoney);
+
+        return vo;
+    }
+
+    /**
+     * 团购收益查询
+     *
+     * @param storeId    门店ID
+     * @param date       日期
+     * @param incomeType 收入类型
+     * @param page       页码
+     * @param size       每页条数
+     * @return 团购收益信息
+     */
+    @Override
+    public Object getGroupIncome(Integer storeId, String date, Integer incomeType, Integer page, Integer size) {
+        log.info("IncomeManageServiceImpl.getGroupIncome - 开始查询团购收益: storeId={}, date={}, incomeType={}, page={}, size={}",
+                storeId, date, incomeType, page, size);
+
+        // 1. 构建查询条件:指定日期内、未绑定提现记录
+        LambdaQueryWrapper<StoreIncomeDetailsRecord> wrapper = new LambdaQueryWrapper<>();
+        wrapper.between(StoreIncomeDetailsRecord::getCreatedTime, date + " 00:00:00", date + " 23:59:59")
+                .isNull(StoreIncomeDetailsRecord::getCashOutId)  // 未绑定提现记录
+                .eq(StoreIncomeDetailsRecord::getStoreId, storeId);
+
+        // 2. 收入类型筛选
+        // 2025-11-17 前端展示逻辑将团购券去掉,仅展示优惠券(前端不传0)
+        if (null != incomeType && 0 == incomeType) {
+            wrapper.in(StoreIncomeDetailsRecord::getIncomeType,
+                    CouponTypeEnum.COUPON.getCode(), CouponTypeEnum.GROUP_BUY.getCode());
+            log.debug("IncomeManageServiceImpl.getGroupIncome - 收入类型: 主页(优惠券+团购券)");
+        } else if (null != incomeType) {
+            wrapper.eq(StoreIncomeDetailsRecord::getIncomeType, incomeType);
+            log.debug("IncomeManageServiceImpl.getGroupIncome - 收入类型: {}", incomeType);
+        }
+
+        // 3. 查询店铺信息
+        StoreInfo storeInfo = storeInfoMapper.selectById(storeId);
+        if (storeInfo == null) {
+            log.warn("IncomeManageServiceImpl.getGroupIncome - 门店不存在: storeId={}", storeId);
+            throw new RuntimeException("门店不存在");
+        }
+
+        // 4. 查询收入明细记录
+        List<StoreIncomeDetailsRecord> list = this.list(wrapper);
+        log.info("IncomeManageServiceImpl.getGroupIncome - 查询到收入记录数: {}", list.size());
+
+        // 5. 计算总收入和手续费
+        Integer allIncome = 0;
+        Integer commission = 0;
+        for (StoreIncomeDetailsRecord storeIncomeDetailsRecord : list) {
+            allIncome += storeIncomeDetailsRecord.getMoney();
+            commission += storeIncomeDetailsRecord.getCommission();
+        }
+
+        // 6. 构建返回VO
+        StoreIncomeDetailsRecordVo vo = new StoreIncomeDetailsRecordVo();
+        vo.setCommissionRate(storeInfo.getCommissionRate());
+        vo.setDate(date);
+        vo.setStoreName(storeInfo.getStoreName());
+        vo.setStoreId(storeId);
+
+        // 7. 格式化金额信息
+        // 售价(收入+手续费)
+        vo.setIncomeMoney(
+                new BigDecimal(allIncome)
+                        .add(new BigDecimal(commission))
+                        .divide(new BigDecimal(100), 2, RoundingMode.HALF_UP)
+                        .toString()
+        );
+
+        // 手续费
+        vo.setCommissionStr(
+                new BigDecimal(commission)
+                        .divide(new BigDecimal(100), 2, RoundingMode.HALF_UP)
+                        .toString()
+        );
+
+        // 收益(售价-手续费,存入时已经减完了)
+        vo.setNoYetPaymentMoney(
+                new BigDecimal(allIncome)
+                        .divide(new BigDecimal(100), 2, RoundingMode.HALF_UP)
+                        .toString()
+        );
+
+        // 8. 查询明细记录列表(含退款标识)
+        QueryWrapper<StoreIncomeDetailsRecordVo> incomeWrapper = new QueryWrapper<>();
+        incomeWrapper.eq("income.delete_flag", 0)
+                .eq("income.store_id", storeId)
+                .between("income.created_time", date + " 00:00:00", date + " 23:59:59")
+                .orderByDesc("income.created_time");
+
+        if (null != incomeType) {
+            if (0 == incomeType) {
+                incomeWrapper.in("income_type",
+                        CouponTypeEnum.COUPON.getCode(), CouponTypeEnum.GROUP_BUY.getCode());
+            } else {
+                incomeWrapper.eq("income_type", incomeType);
+            }
+        }
+
+        List<StoreIncomeDetailsRecordVo> incomeDetailsRecordVoList =
+                storeIncomeDetailsRecordMapper.getIncomeList(incomeWrapper);
+
+        // 9. 按退款类型分组
+        Map<String, List<StoreIncomeDetailsRecordVo>> collect =
+                incomeDetailsRecordVoList.stream()
+                        .collect(Collectors.groupingBy(x -> x.getRefundType()));
+
+        // 10. 设置明细列表(不含退款)和统计信息
+        vo.setIncomeDetailsRecordVoList(new ArrayList<>());
+        vo.setRefundMoney(new BigDecimal(0));
+        vo.setCouponCount(0);
+
+        // 正常收入记录(未退款)
+        if (collect.containsKey("false")) {
+            vo.setIncomeDetailsRecordVoList(
+                    ListToPage.setPage(incomeDetailsRecordVoList, page, size).getRecords()
+            );
+            // 计算券数量(正常订单 - 退款订单)
+            vo.setCouponCount(
+                    collect.get("false").size() -
+                            (collect.containsKey("true") ? collect.get("true").size() : 0)
+            );
+        }
+
+        // 退款金额统计
+        if (collect.containsKey("true")) {
+            vo.setRefundMoney(
+                    new BigDecimal(
+                            collect.get("true").stream()
+                                    .mapToInt(StoreIncomeDetailsRecordVo::getMoney)
+                                    .sum()
+                    ).divide(new BigDecimal(100), 2, RoundingMode.HALF_UP)
+            );
+        }
+
+        log.info("IncomeManageServiceImpl.getGroupIncome - 查询完成: 总收入={}元, 手续费={}元, 券数量={}",
+                vo.getNoYetPaymentMoney(), vo.getCommissionStr(), vo.getCouponCount());
+
+        return vo;
+    }
+
+    /**
+     * 快速提现申请(免审核,直接通过状态)
+     * 与app端applyCashOut逻辑保持一致
+     *
+     * @param storeId         门店ID
+     * @param payPassword     支付密码
+     * @param withdrawalMoney 提现金额(单位:分)
+     * @return 提现结果
+     */
+    @Transactional
+    @Override
+    public R<?> applyFastCashOut(Integer storeId, String payPassword, Integer withdrawalMoney) {
+        log.info("IncomeManageServiceImpl.applyFastCashOut - 开始处理快速提现: storeId={}, withdrawalMoney={}分",
+                storeId, withdrawalMoney);
+
+        // 1. 验证支付密码并查询用户信息
+        LambdaQueryWrapper<StoreUser> userWrapper = new LambdaQueryWrapper<>();
+        userWrapper.eq(StoreUser::getStoreId, storeId)
+                .eq(StoreUser::getPayPassword, payPassword);
+        StoreUser storeUser = storeUserMapper.selectOne(userWrapper);
+
+        if (storeUser == null) {
+            log.warn("IncomeManageServiceImpl.applyFastCashOut - 支付密码错误: storeId={}", storeId);
+            return R.fail("支付密码错误");
+        }
+
+        // 2. 验证账户余额是否充足
+        if (storeUser.getMoney() <= withdrawalMoney) {
+            log.warn("IncomeManageServiceImpl.applyFastCashOut - 余额不足: 账户余额={}分, 提现金额={}分",
+                    storeUser.getMoney(), withdrawalMoney);
+            return R.fail("余额不足");
+        }
+
+        // 3. 验证提现金额是否满足最低要求(0.1元 = 10分)
+        BigDecimal amountInYuan = new BigDecimal(withdrawalMoney)
+                .divide(new BigDecimal(100), 2, RoundingMode.DOWN);
+        if (amountInYuan.compareTo(new BigDecimal("0.1")) < 0) {
+            log.warn("IncomeManageServiceImpl.applyFastCashOut - 提现金额过小: {}元", amountInYuan);
+            return R.fail("金额不能小于0.1元");
+        }
+
+        log.info("IncomeManageServiceImpl.applyFastCashOut - 验证通过: 账户余额={}分, 提现金额={}元",
+                storeUser.getMoney(), amountInYuan);
+
+        try {
+            // 4. 创建提现记录(已通过状态,与app端保持一致)
+            StoreCashOutRecord storeCashOutRecord = new StoreCashOutRecord();
+            storeCashOutRecord.setStoreId(storeId);
+            storeCashOutRecord.setMoney(withdrawalMoney);
+            storeCashOutRecord.setCashOutType(0);  // 0-全部提现
+            storeCashOutRecord.setPaymentStatus(3);  // 3-已通过(与app端保持一致)
+            storeCashOutRecord.setDeleteFlag(0);
+            storeCashOutRecord.setIncomeStartTime(new Date());
+            storeCashOutRecord.setIncomeEndTime(new Date());
+            storeCashOutRecord.setStoreUserId(storeUser.getId());
+
+            // 5. 插入提现记录
+            int insertResult = storeCashOutRecordMapper.insert(storeCashOutRecord);
+            if (insertResult <= 0) {
+                log.error("IncomeManageServiceImpl.applyFastCashOut - 创建提现记录失败");
+                throw new RuntimeException("创建提现记录失败");
+            }
+
+            log.info("IncomeManageServiceImpl.applyFastCashOut - 快速提现申请成功: 提现记录ID={}, 提现金额={}元",
+                    storeCashOutRecord.getId(), amountInYuan);
+
+            // 6. 返回结果(返回StoreCashOutRecord对象,与app端保持一致)
+            return R.data(storeCashOutRecord);
+
+        } catch (Exception e) {
+            log.error("IncomeManageServiceImpl.applyFastCashOut - 快速提现失败: {}", e.getMessage(), e);
+
+            // 如果是已知的运行时异常,直接抛出让事务回滚
+            if (e instanceof RuntimeException) {
+                throw (RuntimeException) e;
+            }
+
+            // 其他异常也需要回滚
+            throw new RuntimeException("快速提现失败:" + e.getMessage(), e);
+        }
+    }
+}
+

+ 219 - 2
alien-store-platform/src/main/java/shop/alien/storeplatform/service/impl/MerchantUserServiceImpl.java

@@ -1,15 +1,18 @@
 package shop.alien.storeplatform.service.impl;
 
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.lang3.StringUtils;
 import org.springframework.beans.BeanUtils;
 import org.springframework.stereotype.Service;
-import shop.alien.entity.store.StoreUser;
+import org.springframework.transaction.annotation.Transactional;
+import shop.alien.entity.store.*;
 import shop.alien.entity.store.vo.StoreUserVo;
-import shop.alien.mapper.StoreUserMapper;
+import shop.alien.mapper.*;
 import shop.alien.storeplatform.service.MerchantUserService;
+import shop.alien.storeplatform.util.LoginUserUtil;
 
 import java.math.BigDecimal;
 import java.math.RoundingMode;
@@ -31,6 +34,15 @@ import java.util.Map;
 public class MerchantUserServiceImpl implements MerchantUserService {
 
     private final StoreUserMapper storeUserMapper;
+    private final StoreInfoMapper storeInfoMapper;
+    private final StoreImgMapper storeImgMapper;
+    private final TagStoreRelationMapper tagStoreRelationMapper;
+    private final LifeNoticeMapper lifeNoticeMapper;
+    private final LifeUserOrderMapper lifeUserOrderMapper;
+    private final OrderCouponMiddleMapper orderCouponMiddleMapper;
+    private final LifeCouponMapper lifeCouponMapper;
+    private final LifeDiscountCouponMapper lifeDiscountCouponMapper;
+    private final LifeDiscountCouponUserMapper lifeDiscountCouponUserMapper;
 
     @Override
     public StoreUserVo getMerchantByPhone(String phone) {
@@ -193,6 +205,211 @@ public class MerchantUserServiceImpl implements MerchantUserService {
         log.info("MerchantUserServiceImpl.checkPayPassword - 检查完成: storeUserId={}, result=true", storeUserId);
         return returnMap;
     }
+
+    /**
+     * 设置/修改支付密码
+     * 
+     * @param id          用户ID
+     * @param payPassword 支付密码
+     * @return 是否设置成功
+     */
+    @Override
+    public boolean setPayPassword(Integer id, String payPassword) {
+        log.info("MerchantUserServiceImpl.setPayPassword - 开始设置支付密码: id={}", id);
+
+        // 1. 验证参数
+        if (id == null) {
+            log.warn("MerchantUserServiceImpl.setPayPassword - 用户ID为空");
+            throw new RuntimeException("用户ID不能为空");
+        }
+
+        if (StringUtils.isEmpty(payPassword)) {
+            log.warn("MerchantUserServiceImpl.setPayPassword - 支付密码为空");
+            throw new RuntimeException("支付密码不能为空");
+        }
+
+        // 2. 构建更新对象
+        StoreUser storeUser = new StoreUser();
+        storeUser.setId(id);
+        storeUser.setPayPassword(payPassword);
+
+        // 3. 执行更新
+        int updateResult = storeUserMapper.updateById(storeUser);
+        boolean success = updateResult > 0;
+
+        log.info("MerchantUserServiceImpl.setPayPassword - 设置完成: id={}, success={}", id, success);
+        return success;
+    }
+
+    /**
+     * 添加/修改支付宝账号
+     *
+     * @param userId        用户ID
+     * @param alipayAccount 支付宝账号
+     * @return 影响的行数
+     */
+    @Override
+    public Integer addAlipayAccount(Integer userId, String alipayAccount) {
+        log.info("MerchantUserServiceImpl.addAlipayAccount - 开始添加支付宝账号: userId={}, alipayAccount={}", 
+                userId, alipayAccount);
+
+        // 1. 验证参数
+        if (userId == null) {
+            log.warn("MerchantUserServiceImpl.addAlipayAccount - 用户ID为空");
+            throw new RuntimeException("用户ID不能为空");
+        }
+
+        if (StringUtils.isEmpty(alipayAccount)) {
+            log.warn("MerchantUserServiceImpl.addAlipayAccount - 支付宝账号为空");
+            throw new RuntimeException("支付宝账号不能为空");
+        }
+
+        // 2. 构建更新条件
+        LambdaQueryWrapper<StoreUser> updateWrapper = new LambdaQueryWrapper<>();
+        updateWrapper.eq(StoreUser::getId, userId);
+
+        // 3. 构建更新对象
+        StoreUser storeUser = new StoreUser();
+        storeUser.setAlipayAccount(alipayAccount);
+
+        // 4. 执行更新
+        int updateResult = storeUserMapper.update(storeUser, updateWrapper);
+
+        log.info("MerchantUserServiceImpl.addAlipayAccount - 添加完成: userId={}, updateResult={}", 
+                userId, updateResult);
+
+        return updateResult;
+    }
+
+    /**
+     * 重置商户到刚注册状态
+     * 一键退回:删除店铺申请、解绑店铺、清理相关数据
+     *
+     * @return 是否重置成功
+     */
+    @Transactional(rollbackFor = Exception.class)
+    @Override
+    public boolean resetToInitialStatus() {
+        try {
+            // 1. 获取当前登录用户
+            Integer userId = LoginUserUtil.getCurrentUserId();
+            StoreUser storeUser = storeUserMapper.selectById(userId);
+            
+            if (storeUser == null) {
+                log.warn("MerchantUserServiceImpl.resetToInitialStatus - 用户不存在: userId={}", userId);
+                return false;
+            }
+            
+            Integer storeId = storeUser.getStoreId();
+            log.info("MerchantUserServiceImpl.resetToInitialStatus - 开始重置: userId={}, storeId={}", 
+                    userId, storeId);
+            
+            // 2. 如果没有关联店铺,无需重置
+            if (storeId == null) {
+                log.info("MerchantUserServiceImpl.resetToInitialStatus - 用户未关联店铺,无需重置");
+                return true;
+            }
+            
+            // 3. 删除店铺图片(营业执照、合同、经营许可证)
+            LambdaQueryWrapper<StoreImg> imgWrapper = new LambdaQueryWrapper<>();
+            imgWrapper.eq(StoreImg::getStoreId, storeId)
+                    .in(StoreImg::getImgType, 14, 15, 25);  // 14-营业执照, 15-合同, 25-经营许可证
+            int deletedImgCount = storeImgMapper.delete(imgWrapper);
+            log.info("MerchantUserServiceImpl.resetToInitialStatus - 删除店铺图片: count={}", deletedImgCount);
+            
+            // 4. 删除店铺标签关系
+            LambdaQueryWrapper<TagStoreRelation> tagWrapper = new LambdaQueryWrapper<>();
+            tagWrapper.eq(TagStoreRelation::getStoreId, storeId);
+            int deletedTagCount = tagStoreRelationMapper.delete(tagWrapper);
+            log.info("MerchantUserServiceImpl.resetToInitialStatus - 删除店铺标签: count={}", deletedTagCount);
+            
+            // 5. 删除店铺信息
+            int deletedStoreCount = storeInfoMapper.deleteById(storeId);
+            log.info("MerchantUserServiceImpl.resetToInitialStatus - 删除店铺信息: success={}", deletedStoreCount > 0);
+            
+            // 6. 清理用户关联的通知消息(入住申请相关)
+            if (StringUtils.isNotEmpty(storeUser.getPhone())) {
+                String receiverId = "store_" + storeUser.getPhone();
+                LambdaQueryWrapper<LifeNotice> noticeWrapper = new LambdaQueryWrapper<>();
+                noticeWrapper.eq(LifeNotice::getReceiverId, receiverId)
+                        .eq(LifeNotice::getTitle, "入住店铺申请");
+                int deletedNoticeCount = lifeNoticeMapper.delete(noticeWrapper);
+                log.info("MerchantUserServiceImpl.resetToInitialStatus - 删除入住申请通知: count={}", deletedNoticeCount);
+            }
+            
+            // 7. 查询该店铺的所有订单ID(用于后续删除验券记录)
+            LambdaQueryWrapper<LifeUserOrder> orderQueryWrapper = new LambdaQueryWrapper<>();
+            orderQueryWrapper.eq(LifeUserOrder::getStoreId, storeId)
+                    .select(LifeUserOrder::getId);
+            java.util.List<LifeUserOrder> orderList = lifeUserOrderMapper.selectList(orderQueryWrapper);
+            java.util.List<String> orderIds = orderList.stream()
+                    .map(order -> order.getId().toString())
+                    .collect(java.util.stream.Collectors.toList());
+            log.info("MerchantUserServiceImpl.resetToInitialStatus - 查询到订单数量: count={}", orderIds.size());
+            
+            // 8. 删除订单-优惠券中间表数据(验券记录)
+            if (!orderIds.isEmpty()) {
+                LambdaQueryWrapper<OrderCouponMiddle> orderCouponWrapper = new LambdaQueryWrapper<>();
+                orderCouponWrapper.in(OrderCouponMiddle::getOrderId, orderIds);
+                int deletedOrderCouponCount = orderCouponMiddleMapper.delete(orderCouponWrapper);
+                log.info("MerchantUserServiceImpl.resetToInitialStatus - 删除验券记录: count={}", deletedOrderCouponCount);
+            }
+            
+            // 9. 删除该店铺的所有订单记录
+            LambdaQueryWrapper<LifeUserOrder> orderWrapper = new LambdaQueryWrapper<>();
+            orderWrapper.eq(LifeUserOrder::getStoreId, storeId);
+            int deletedOrderCount = lifeUserOrderMapper.delete(orderWrapper);
+            log.info("MerchantUserServiceImpl.resetToInitialStatus - 删除订单记录: count={}", deletedOrderCount);
+            
+            // 10. 删除该店铺发布的团购券
+            LambdaQueryWrapper<LifeCoupon> couponWrapper = new LambdaQueryWrapper<>();
+            couponWrapper.eq(LifeCoupon::getStoreId, storeId);
+            int deletedCouponCount = lifeCouponMapper.delete(couponWrapper);
+            log.info("MerchantUserServiceImpl.resetToInitialStatus - 删除团购券: count={}", deletedCouponCount);
+            
+            // 10. 删除该店铺发布的代金券
+            LambdaQueryWrapper<LifeDiscountCoupon> discountCouponWrapper = new LambdaQueryWrapper<>();
+            discountCouponWrapper.eq(LifeDiscountCoupon::getStoreId, storeId);
+            int deletedDiscountCouponCount = lifeDiscountCouponMapper.delete(discountCouponWrapper);
+            log.info("MerchantUserServiceImpl.resetToInitialStatus - 删除代金券: count={}", deletedDiscountCouponCount);
+            
+            // 11. 删除用户领取的优惠券(用户是商户本人)
+            LambdaQueryWrapper<LifeDiscountCouponUser> couponUserWrapper = new LambdaQueryWrapper<>();
+            couponUserWrapper.eq(LifeDiscountCouponUser::getUserId, userId);
+            int deletedCouponUserCount = lifeDiscountCouponUserMapper.delete(couponUserWrapper);
+            log.info("MerchantUserServiceImpl.resetToInitialStatus - 删除用户优惠券: count={}", deletedCouponUserCount);
+            
+            // 12. 清理所有通知消息(包括订单、优惠券相关)
+            if (StringUtils.isNotEmpty(storeUser.getPhone())) {
+                String receiverId = "store_" + storeUser.getPhone();
+                LambdaQueryWrapper<LifeNotice> allNoticeWrapper = new LambdaQueryWrapper<>();
+                allNoticeWrapper.eq(LifeNotice::getReceiverId, receiverId);
+                int deletedAllNoticeCount = lifeNoticeMapper.delete(allNoticeWrapper);
+                log.info("MerchantUserServiceImpl.resetToInitialStatus - 删除所有通知消息: count={}", deletedAllNoticeCount);
+            }
+            
+            // 13. 重置用户的店铺关联和个人信息(使用 LambdaUpdateWrapper 显式设置为 null)
+            LambdaUpdateWrapper<StoreUser> updateWrapper = new LambdaUpdateWrapper<>();
+            updateWrapper.eq(StoreUser::getId, userId)
+                    .set(StoreUser::getStoreId, null)       // 解绑店铺
+                    .set(StoreUser::getName, null)          // 清空姓名(入住时填写的联系人)
+                    .set(StoreUser::getIdCard, null)        // 清空身份证(入住时填写的)
+                    .set(StoreUser::getPayPassword, null)   // 清空支付密码
+                    .set(StoreUser::getMoney, 0)    // 重置用户余额为0
+                    .set(StoreUser::getAlipayAccount, null); // 清空支付宝账号(入住时填写的)
+            
+            int updateResult = storeUserMapper.update(null, updateWrapper);
+            
+            log.info("MerchantUserServiceImpl.resetToInitialStatus - 重置用户信息: success={}", updateResult > 0);
+            log.info("MerchantUserServiceImpl.resetToInitialStatus - 重置完成: userId={}, storeId={}", userId, storeId);
+            
+            return updateResult > 0;
+            
+        } catch (Exception e) {
+            log.error("MerchantUserServiceImpl.resetToInitialStatus - 重置失败: {}", e.getMessage(), e);
+            throw new RuntimeException("重置失败:" + e.getMessage(), e);
+        }
+    }
 }
 
 

+ 2 - 0
alien-store-platform/src/main/java/shop/alien/storeplatform/util/LoginUserUtil.java

@@ -247,3 +247,5 @@ public class LoginUserUtil {
 
 
 
+
+

+ 4 - 0
alien-store-platform/接口文档/19-查询账户余额接口.md

@@ -717,3 +717,7 @@ ADD INDEX idx_store_status (store_id, payment_status, delete_flag);
 **维护人员**: ssk
 
 
+
+
+
+

+ 4 - 0
alien-store-platform/接口文档/20-修改商户用户信息接口.md

@@ -792,3 +792,7 @@ if (StringUtils.isNotEmpty(storeUser.getIdCard())) {
 **维护人员**: ssk
 
 
+
+
+
+