Jelajahi Sumber

trade: 分销业务 - 提现申请

owen 1 tahun lalu
induk
melakukan
a7de955926
20 mengubah file dengan 216 tambahan dan 46 penghapusan
  1. 24 19
      sql/mysql/brokerage.sql
  2. 2 0
      yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/ErrorCodeConstants.java
  3. 1 0
      yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/brokerage/BrokerageRecordBizTypeEnum.java
  4. 1 1
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/brokerage/AppBrokerageUserController.java
  5. 6 2
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/brokerage/AppBrokerageWithdrawController.java
  6. 1 1
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/brokerage/vo/user/AppBrokerageUserRespVO.java
  7. 4 2
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/brokerage/vo/withdraw/AppBrokerageWithdrawCreateReqVO.java
  8. 2 1
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/brokerage/BrokerageRecordConvert.java
  9. 2 2
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/brokerage/BrokerageWithdrawConvert.java
  10. 1 1
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/brokerage/BrokerageWithdrawDO.java
  11. 4 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/config/TradeConfigDO.java
  12. 6 5
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/brokerage/BrokerageUserMapper.java
  13. 11 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageRecordService.java
  14. 28 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageRecordServiceImpl.java
  15. 2 1
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageUserService.java
  16. 3 2
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageUserServiceImpl.java
  17. 11 1
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageWithdrawService.java
  18. 76 7
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageWithdrawServiceImpl.java
  19. 30 0
      yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageWithdrawServiceImplTest.java
  20. 1 1
      yudao-module-mall/yudao-module-trade-biz/src/test/resources/sql/create_tables.sql

+ 24 - 19
sql/mysql/brokerage.sql

@@ -1,25 +1,29 @@
 -- 增加配置表
 create table trade_config
 (
-    id                           bigint auto_increment comment '自增主键' primary key,
-    brokerage_enabled            bit           default 1                 not null comment '是否启用分佣',
-    brokerage_enabled_condition  tinyint       default 0                 not null comment '分佣模式:1-人人分销 2-指定分销',
-    brokerage_bind_mode          tinyint       default 0                 not null comment '分销关系绑定模式: 1-没有推广人,2-新用户, 3-扫码覆盖',
-    brokerage_post_urls          varchar(2000) default ''                null comment '分销海报图地址数组',
-    brokerage_first_percent      int           default 0                 not null comment '一级返佣比例',
-    brokerage_second_percent     int           default 0                 not null comment '二级返佣比例',
-    brokerage_withdraw_min_price int           default 0                 not null comment '用户提现最低金额',
-    brokerage_bank_names         varchar(200)  default ''                not null comment '提现银行(字典类型=brokerage_bank_name)',
-    brokerage_frozen_days        int           default 7                 not null comment '佣金冻结时间(天)',
-    brokerage_withdraw_type      varchar(32)   default '1,2,3,4'         not null comment '提现方式:1-钱包;2-银行卡;3-微信;4-支付宝',
-    creator                      varchar(64)   default ''                null comment '创建者',
-    create_time                  datetime      default CURRENT_TIMESTAMP not null comment '创建时间',
-    updater                      varchar(64)   default ''                null comment '更新者',
-    update_time                  datetime      default CURRENT_TIMESTAMP not null on update CURRENT_TIMESTAMP comment '更新时间',
-    deleted                      bit           default b'0'              not null comment '是否删除',
-    tenant_id                    bigint        default 0                 not null comment '租户编号'
+    id                             bigint auto_increment comment '自增主键' primary key,
+    brokerage_enabled              bit           default 1                 not null comment '是否启用分佣',
+    brokerage_enabled_condition    tinyint       default 0                 not null comment '分佣模式:1-人人分销 2-指定分销',
+    brokerage_bind_mode            tinyint       default 0                 not null comment '分销关系绑定模式: 1-没有推广人,2-新用户, 3-扫码覆盖',
+    brokerage_post_urls            varchar(2000) default ''                null comment '分销海报图地址数组',
+    brokerage_first_percent        int           default 0                 not null comment '一级返佣比例',
+    brokerage_second_percent       int           default 0                 not null comment '二级返佣比例',
+    brokerage_withdraw_min_price   int           default 0                 not null comment '用户提现最低金额',
+    brokerage_withdraw_fee_percent int           default 0                 not null comment '提现手续费百分比',
+    brokerage_bank_names           varchar(200)  default ''                not null comment '提现银行(字典类型=brokerage_bank_name)',
+    brokerage_frozen_days          int           default 7                 not null comment '佣金冻结时间(天)',
+    brokerage_withdraw_type        varchar(32)   default '1,2,3,4'         not null comment '提现方式:1-钱包;2-银行卡;3-微信;4-支付宝',
+    creator                        varchar(64)   default ''                null comment '创建者',
+    create_time                    datetime      default CURRENT_TIMESTAMP not null comment '创建时间',
+    updater                        varchar(64)   default ''                null comment '更新者',
+    update_time                    datetime      default CURRENT_TIMESTAMP not null on update CURRENT_TIMESTAMP comment '更新时间',
+    deleted                        bit           default b'0'              not null comment '是否删除',
+    tenant_id                      bigint        default 0                 not null comment '租户编号'
 ) comment '交易中心配置';
 
+# alter table trade_config
+#     add brokerage_withdraw_fee_percent int default 0 not null comment '提现手续费百分比' after brokerage_withdraw_min_price;
+
 # alter table trade_brokerage_user
 #     add level int not null default 1 comment '等级' after frozen_price;
 # alter table trade_brokerage_user
@@ -83,7 +87,7 @@ create index idx_status on trade_brokerage_record (status) comment '状态';
 
 create table trade_brokerage_withdraw
 (
-    id                  int auto_increment comment '编号'
+    id                  bigint auto_increment comment '编号'
         primary key,
     user_id             bigint                                not null comment '用户编号',
     price               int         default 0                 not null comment '提现金额',
@@ -137,7 +141,8 @@ insert into system_dict_type(type, name)
 values ('brokerage_record_biz_type', '佣金记录业务类型');
 insert into system_dict_data(dict_type, label, value, sort)
 values ('brokerage_record_biz_type', '订单返佣', 1, 1),
-       ('brokerage_record_biz_type', '申请提现', 2, 2);
+       ('brokerage_record_biz_type', '申请提现', 2, 2),
+       ('brokerage_record_biz_type', '申请提现驳回', 3, 3);
 
 insert into system_dict_type(type, name)
 values ('brokerage_record_status', '佣金记录状态');

+ 2 - 0
yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/ErrorCodeConstants.java

@@ -93,5 +93,7 @@ public interface ErrorCodeConstants {
     // ========== 分销提现 模块 1011008000 ==========
     ErrorCode BROKERAGE_WITHDRAW_NOT_EXISTS = new ErrorCode(1011008000, "佣金提现记录不存在");
     ErrorCode BROKERAGE_WITHDRAW_STATUS_NOT_AUDITING = new ErrorCode(1011008001, "佣金提现记录状态不是审核中");
+    ErrorCode BROKERAGE_WITHDRAW_MIN_PRICE = new ErrorCode(1011008002, "提现金额不能低于{}元");
+    ErrorCode BROKERAGE_WITHDRAW_USER_BALANCE_NOT_ENOUGH = new ErrorCode(1011008003, "您当前最多可提现{}元");
 
 }

+ 1 - 0
yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/brokerage/BrokerageRecordBizTypeEnum.java

@@ -17,6 +17,7 @@ public enum BrokerageRecordBizTypeEnum implements IntArrayValuable {
 
     ORDER(1, "获得推广佣金", "获得推广佣金 {}", true),
     WITHDRAW(2, "提现申请", "提现申请扣除佣金 {}", false),
+    WITHDRAW_REJECT(3, "提现申请驳回", "提现申请驳回返还佣金 {}", true),
     ;
 
     public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BrokerageRecordBizTypeEnum::getType).toArray();

+ 1 - 1
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/brokerage/AppBrokerageUserController.java

@@ -38,7 +38,7 @@ public class AppBrokerageUserController {
     public CommonResult<AppBrokerageUserRespVO> getBrokerageUser() {
         AppBrokerageUserRespVO respVO = new AppBrokerageUserRespVO()
                 .setBrokerageEnabled(true)
-                .setPrice(2000)
+                .setBrokeragePrice(2000)
                 .setFrozenPrice(3000);
         return success(respVO);
     }

+ 6 - 2
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/brokerage/AppBrokerageWithdrawController.java

@@ -5,16 +5,19 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.security.core.annotations.PreAuthenticated;
 import cn.iocoder.yudao.module.trade.controller.app.brokerage.vo.withdraw.AppBrokerageWithdrawCreateReqVO;
 import cn.iocoder.yudao.module.trade.controller.app.brokerage.vo.withdraw.AppBrokerageWithdrawRespVO;
+import cn.iocoder.yudao.module.trade.service.brokerage.BrokerageWithdrawService;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.tags.Tag;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.*;
 
+import javax.annotation.Resource;
 import javax.validation.Valid;
 import java.time.LocalDateTime;
 
 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.getLoginUserId;
 import static java.util.Arrays.asList;
 
 @Tag(name = "用户 APP - 分销提现")
@@ -23,6 +26,8 @@ import static java.util.Arrays.asList;
 @Validated
 @Slf4j
 public class AppBrokerageWithdrawController {
+    @Resource
+    private BrokerageWithdrawService brokerageWithdrawService;
 
     // TODO 芋艿:临时 mock =>
     @GetMapping("/page")
@@ -36,12 +41,11 @@ public class AppBrokerageWithdrawController {
         return success(new PageResult<>(asList(vo1, vo2), 10L));
     }
 
-    // TODO 芋艿:临时 mock =>
     @PostMapping("/create")
     @Operation(summary = "创建分销提现")
     @PreAuthenticated
     public CommonResult<Long> createBrokerageWithdraw(@RequestBody @Valid AppBrokerageWithdrawCreateReqVO createReqVO) {
-        return success(1L);
+        return success(brokerageWithdrawService.createBrokerageWithdraw(createReqVO, getLoginUserId()));
     }
 
 }

+ 1 - 1
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/brokerage/vo/user/AppBrokerageUserRespVO.java

@@ -11,7 +11,7 @@ public class AppBrokerageUserRespVO {
     private Boolean brokerageEnabled;
 
     @Schema(description = "可用的佣金,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "2408")
-    private Integer price;
+    private Integer brokeragePrice;
 
     @Schema(description = "冻结的佣金,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "234")
     private Integer frozenPrice;

+ 4 - 2
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/brokerage/vo/withdraw/AppBrokerageWithdrawCreateReqVO.java

@@ -8,8 +8,9 @@ import lombok.Data;
 import org.hibernate.validator.constraints.URL;
 
 import javax.validation.Validator;
-import javax.validation.constraints.Min;
 import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+import javax.validation.constraints.PositiveOrZero;
 
 @Schema(description = "用户 App - 分销提现创建 Request VO")
 @Data
@@ -20,7 +21,8 @@ public class AppBrokerageWithdrawCreateReqVO {
     private Integer type;
 
     @Schema(description = "提现金额,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "1000")
-    @Min(value = 1, message = "提现金额不能小于 1")
+    @PositiveOrZero(message = "提现金额不能小于 0")
+    @NotNull(message = "提现金额不能为空")
     private Integer price;
 
     // ========== 银行卡、微信、支付宝 提现相关字段 ==========

+ 2 - 1
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/brokerage/BrokerageRecordConvert.java

@@ -1,5 +1,6 @@
 package cn.iocoder.yudao.module.trade.convert.brokerage;
 
+import cn.hutool.core.math.Money;
 import cn.hutool.core.util.ObjectUtil;
 import cn.hutool.core.util.StrUtil;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
@@ -45,7 +46,7 @@ public interface BrokerageRecordConvert {
                 .setBizType(bizType.getType()).setBizId(bizId)
                 .setPrice(brokeragePrice).setTotalPrice(user.getBrokeragePrice())
                 .setTitle(title)
-                .setDescription(StrUtil.format(bizType.getDescription(), String.format("¥%.2f", brokeragePrice / 100d)))
+                .setDescription(StrUtil.format(bizType.getDescription(), new Money(0, Math.abs(brokeragePrice))))
                 .setStatus(status).setFrozenDays(brokerageFrozenDays).setUnfreezeTime(unfreezeTime)
                 .setSourceUserLevel(sourceUserLevel).setSourceUserId(sourceUserId);
     }

+ 2 - 2
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/brokerage/BrokerageWithdrawConvert.java

@@ -2,8 +2,8 @@ package cn.iocoder.yudao.module.trade.convert.brokerage;
 
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
-import cn.iocoder.yudao.module.trade.controller.admin.brokerage.vo.withdraw.BrokerageWithdrawRejectReqVO;
 import cn.iocoder.yudao.module.trade.controller.admin.brokerage.vo.withdraw.BrokerageWithdrawRespVO;
+import cn.iocoder.yudao.module.trade.controller.app.brokerage.vo.withdraw.AppBrokerageWithdrawCreateReqVO;
 import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.BrokerageWithdrawDO;
 import org.mapstruct.Mapper;
 import org.mapstruct.factory.Mappers;
@@ -22,7 +22,7 @@ public interface BrokerageWithdrawConvert {
 
     BrokerageWithdrawConvert INSTANCE = Mappers.getMapper(BrokerageWithdrawConvert.class);
 
-    BrokerageWithdrawDO convert(BrokerageWithdrawRejectReqVO bean);
+    BrokerageWithdrawDO convert(AppBrokerageWithdrawCreateReqVO createReqVO, Long userId, Integer feePrice);
 
     BrokerageWithdrawRespVO convert(BrokerageWithdrawDO bean);
 

+ 1 - 1
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/brokerage/BrokerageWithdrawDO.java

@@ -29,7 +29,7 @@ public class BrokerageWithdrawDO extends BaseDO {
      * 编号
      */
     @TableId
-    private Integer id;
+    private Long id;
     /**
      * 用户编号
      *

+ 4 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/config/TradeConfigDO.java

@@ -81,6 +81,10 @@ public class TradeConfigDO extends BaseDO {
      * 用户提现最低金额
      */
     private Integer brokerageWithdrawMinPrice;
+    /**
+     * 用户提现手续费百分比
+     */
+    private Integer brokerageWithdrawFeePercent;
     /**
      * 提现银行
      */

+ 6 - 5
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/brokerage/BrokerageUserMapper.java

@@ -38,7 +38,7 @@ public interface BrokerageUserMapper extends BaseMapperX<BrokerageUserDO> {
     default void updatePriceIncr(Long id, Integer incrCount) {
         Assert.isTrue(incrCount > 0);
         LambdaUpdateWrapper<BrokerageUserDO> lambdaUpdateWrapper = new LambdaUpdateWrapper<BrokerageUserDO>()
-                .setSql(" price = price + " + incrCount)
+                .setSql(" brokerage_price = brokerage_price + " + incrCount)
                 .eq(BrokerageUserDO::getId, id);
         update(null, lambdaUpdateWrapper);
     }
@@ -49,13 +49,14 @@ public interface BrokerageUserMapper extends BaseMapperX<BrokerageUserDO> {
      *
      * @param id        用户编号
      * @param incrCount 增加佣金(负数)
+     * @return 更新行数
      */
-    default void updatePriceDecr(Long id, Integer incrCount) {
+    default int updatePriceDecr(Long id, Integer incrCount) {
         Assert.isTrue(incrCount < 0);
         LambdaUpdateWrapper<BrokerageUserDO> lambdaUpdateWrapper = new LambdaUpdateWrapper<BrokerageUserDO>()
-                .setSql(" price = price + " + incrCount) // 负数,所以使用 + 号
+                .setSql(" brokerage_price = brokerage_price + " + incrCount) // 负数,所以使用 + 号
                 .eq(BrokerageUserDO::getId, id);
-        update(null, lambdaUpdateWrapper);
+        return update(null, lambdaUpdateWrapper);
     }
 
     /**
@@ -98,7 +99,7 @@ public interface BrokerageUserMapper extends BaseMapperX<BrokerageUserDO> {
         Assert.isTrue(incrCount < 0);
         LambdaUpdateWrapper<BrokerageUserDO> lambdaUpdateWrapper = new LambdaUpdateWrapper<BrokerageUserDO>()
                 .setSql(" frozen_price = frozen_price + " + incrCount + // 负数,所以使用 + 号
-                        ", price = price + " + -incrCount) // 负数,所以使用 - 号
+                        ", brokerage_price = brokerage_price + " + -incrCount) // 负数,所以使用 - 号
                 .eq(BrokerageUserDO::getId, id)
                 .ge(BrokerageUserDO::getFrozenPrice, -incrCount); // cas 逻辑
         return update(null, lambdaUpdateWrapper);

+ 11 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageRecordService.java

@@ -42,6 +42,17 @@ public interface BrokerageRecordService {
      */
     void addBrokerage(Long userId, BrokerageRecordBizTypeEnum bizType, @Valid List<BrokerageAddReqBO> list);
 
+    /**
+     * 增加佣金
+     *
+     * @param userId         会员编号
+     * @param bizType        业务类型
+     * @param bizId          业务编号
+     * @param brokeragePrice 佣金
+     * @param title          标题
+     */
+    void addBrokerage(Long userId, BrokerageRecordBizTypeEnum bizType, String bizId, int brokeragePrice, String title);
+
     /**
      * 取消佣金:将佣金记录,状态修改为已失效
      *

+ 28 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageRecordServiceImpl.java

@@ -1,6 +1,7 @@
 package cn.iocoder.yudao.module.trade.service.brokerage;
 
 import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.math.Money;
 import cn.hutool.core.util.BooleanUtil;
 import cn.hutool.core.util.ObjectUtil;
 import cn.hutool.core.util.StrUtil;
@@ -28,6 +29,10 @@ import java.time.LocalDateTime;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Objects;
+import java.util.Optional;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.BROKERAGE_WITHDRAW_USER_BALANCE_NOT_ENOUGH;
 
 /**
  * 佣金记录 Service 实现类
@@ -217,6 +222,29 @@ public class BrokerageRecordServiceImpl implements BrokerageRecordService {
         return summaryBO != null ? summaryBO : new UserBrokerageSummaryBO(0, 0);
     }
 
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void addBrokerage(Long userId, BrokerageRecordBizTypeEnum bizType, String bizId, int brokeragePrice, String title) {
+        // 校验佣金余额
+        BrokerageUserDO user = brokerageUserService.getBrokerageUser(userId);
+        int balance = Optional.of(user)
+                .map(BrokerageUserDO::getBrokeragePrice).orElse(0);
+        if (balance < brokeragePrice) {
+            throw exception(BROKERAGE_WITHDRAW_USER_BALANCE_NOT_ENOUGH, new Money(0, balance));
+        }
+
+        // 扣减佣金余额
+        boolean success = brokerageUserService.updateUserPrice(userId, brokeragePrice);
+        if (!success) {
+            throw exception(BROKERAGE_WITHDRAW_USER_BALANCE_NOT_ENOUGH, new Money(0, balance));
+        }
+
+        // 新增记录
+        BrokerageRecordDO record = BrokerageRecordConvert.INSTANCE.convert(user, bizType, bizId, 0, brokeragePrice,
+                null, title, null, null);
+        brokerageRecordMapper.insert(record);
+    }
+
     @Transactional(rollbackFor = Exception.class)
     public boolean unfreezeRecord(BrokerageRecordDO record) {
         // 更新记录状态

+ 2 - 1
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageUserService.java

@@ -70,8 +70,9 @@ public interface BrokerageUserService {
      *
      * @param id    用户编号
      * @param price 用户可用佣金
+     * @return 更新结果
      */
-    void updateUserPrice(Long id, Integer price);
+    boolean updateUserPrice(Long id, Integer price);
 
     /**
      * 更新用户冻结佣金

+ 3 - 2
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageUserServiceImpl.java

@@ -110,12 +110,13 @@ public class BrokerageUserServiceImpl implements BrokerageUserService {
     }
 
     @Override
-    public void updateUserPrice(Long id, Integer price) {
+    public boolean updateUserPrice(Long id, Integer price) {
         if (price > 0) {
             brokerageUserMapper.updatePriceIncr(id, price);
         } else if (price < 0) {
-            brokerageUserMapper.updatePriceDecr(id, price);
+            return brokerageUserMapper.updatePriceDecr(id, price) > 0;
         }
+        return true;
     }
 
     @Override

+ 11 - 1
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageWithdrawService.java

@@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.trade.service.brokerage;
 
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.module.trade.controller.admin.brokerage.vo.withdraw.BrokerageWithdrawPageReqVO;
+import cn.iocoder.yudao.module.trade.controller.app.brokerage.vo.withdraw.AppBrokerageWithdrawCreateReqVO;
 import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.BrokerageWithdrawDO;
 import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageWithdrawStatusEnum;
 
@@ -13,7 +14,7 @@ import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageWithdrawStatusEnum
 public interface BrokerageWithdrawService {
 
     /**
-     * 审核佣金提现
+     * 【管理员】审核佣金提现
      *
      * @param id          佣金编号
      * @param status      审核状态
@@ -37,4 +38,13 @@ public interface BrokerageWithdrawService {
      * @return 佣金提现分页
      */
     PageResult<BrokerageWithdrawDO> getBrokerageWithdrawPage(BrokerageWithdrawPageReqVO pageReqVO);
+
+    /**
+     * 【会员】创建佣金提现
+     *
+     * @param createReqVO 创建信息
+     * @param userId      会员用户编号
+     * @return 佣金提现编号
+     */
+    Long createBrokerageWithdraw(AppBrokerageWithdrawCreateReqVO createReqVO, Long userId);
 }

+ 76 - 7
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageWithdrawServiceImpl.java

@@ -2,26 +2,34 @@ package cn.iocoder.yudao.module.trade.service.brokerage;
 
 import cn.hutool.core.date.LocalDateTimeUtil;
 import cn.hutool.core.map.MapUtil;
+import cn.hutool.core.math.Money;
 import cn.hutool.core.util.ObjectUtil;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.number.MoneyUtils;
 import cn.iocoder.yudao.module.system.api.notify.NotifyMessageSendApi;
 import cn.iocoder.yudao.module.system.api.notify.dto.NotifySendSingleToUserReqDTO;
 import cn.iocoder.yudao.module.trade.controller.admin.brokerage.vo.withdraw.BrokerageWithdrawPageReqVO;
+import cn.iocoder.yudao.module.trade.controller.app.brokerage.vo.withdraw.AppBrokerageWithdrawCreateReqVO;
+import cn.iocoder.yudao.module.trade.convert.brokerage.BrokerageWithdrawConvert;
 import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.BrokerageWithdrawDO;
+import cn.iocoder.yudao.module.trade.dal.dataobject.config.TradeConfigDO;
 import cn.iocoder.yudao.module.trade.dal.mysql.brokerage.BrokerageWithdrawMapper;
 import cn.iocoder.yudao.module.trade.enums.MessageTemplateConstants;
+import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageRecordBizTypeEnum;
 import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageWithdrawStatusEnum;
+import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageWithdrawTypeEnum;
+import cn.iocoder.yudao.module.trade.service.config.TradeConfigService;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.validation.annotation.Validated;
 
 import javax.annotation.Resource;
+import javax.validation.Validator;
 import java.time.LocalDateTime;
 import java.util.Map;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
-import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.BROKERAGE_WITHDRAW_NOT_EXISTS;
-import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.BROKERAGE_WITHDRAW_STATUS_NOT_AUDITING;
+import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.*;
 
 /**
  * 佣金提现 Service 实现类
@@ -37,10 +45,15 @@ public class BrokerageWithdrawServiceImpl implements BrokerageWithdrawService {
 
     @Resource
     private BrokerageRecordService brokerageRecordService;
+    @Resource
+    private TradeConfigService tradeConfigService;
 
     @Resource
     private NotifyMessageSendApi notifyMessageSendApi;
 
+    @Resource
+    private Validator validator;
+
     @Override
     @Transactional(rollbackFor = Exception.class)
     public void auditBrokerageWithdraw(Integer id, BrokerageWithdrawStatusEnum status, String auditReason) {
@@ -61,19 +74,26 @@ public class BrokerageWithdrawServiceImpl implements BrokerageWithdrawService {
             throw exception(BROKERAGE_WITHDRAW_STATUS_NOT_AUDITING);
         }
 
-        // 3. 驳回时需要退还用户佣金
         String templateCode = MessageTemplateConstants.BROKERAGE_WITHDRAW_AUDIT_APPROVE;
-        if (BrokerageWithdrawStatusEnum.AUDIT_FAIL.equals(status)) {
+        if (BrokerageWithdrawStatusEnum.AUDIT_SUCCESS.equals(status)) {
+            // 3.1 通过时佣金转余额
+            if (BrokerageWithdrawTypeEnum.WALLET.getType().equals(withdraw.getType())) {
+                // todo
+            }
+        } else if (BrokerageWithdrawStatusEnum.AUDIT_FAIL.equals(status)) {
             templateCode = MessageTemplateConstants.BROKERAGE_WITHDRAW_AUDIT_REJECT;
 
-            // todo @owen
-//            brokerageRecordService.addBrokerage(withdraw.getUserId(), BrokerageRecordBizTypeEnum.WITHDRAW, withdraw.getPrice(), "");
+            // 3.2 驳回时需要退还用户佣金
+            brokerageRecordService.addBrokerage(withdraw.getUserId(), BrokerageRecordBizTypeEnum.WITHDRAW_REJECT,
+                    String.valueOf(withdraw.getId()), withdraw.getPrice(), BrokerageRecordBizTypeEnum.WITHDRAW_REJECT.getTitle());
+        } else {
+            throw new IllegalArgumentException("不支持的提现状态");
         }
 
         // 4. 通知用户
         Map<String, Object> templateParams = MapUtil.<String, Object>builder()
                 .put("createTime", LocalDateTimeUtil.formatNormal(withdraw.getCreateTime()))
-                .put("price", String.format("%.2f", withdraw.getPrice() / 100d))
+                .put("price", new Money(0, withdraw.getPrice()).toString())
                 .put("reason", withdraw.getAuditReason())
                 .build();
         NotifySendSingleToUserReqDTO reqDTO = new NotifySendSingleToUserReqDTO()
@@ -100,4 +120,53 @@ public class BrokerageWithdrawServiceImpl implements BrokerageWithdrawService {
         return brokerageWithdrawMapper.selectPage(pageReqVO);
     }
 
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Long createBrokerageWithdraw(AppBrokerageWithdrawCreateReqVO createReqVO, Long userId) {
+        // 校验提现金额
+        TradeConfigDO tradeConfig = validateWithdrawPrice(createReqVO.getPrice());
+        // 校验提现参数
+        createReqVO.validate(validator);
+
+        // 计算手续费
+        Integer feePrice = calculateFeePrice(createReqVO.getPrice(), tradeConfig.getBrokerageWithdrawFeePercent());
+        // 创建佣金提现记录
+        BrokerageWithdrawDO withdraw = BrokerageWithdrawConvert.INSTANCE.convert(createReqVO, userId, feePrice);
+        brokerageWithdrawMapper.insert(withdraw);
+
+        // 创建用户佣金记录
+        brokerageRecordService.addBrokerage(userId, BrokerageRecordBizTypeEnum.WITHDRAW, String.valueOf(withdraw.getId()),
+                -createReqVO.getPrice(), BrokerageRecordBizTypeEnum.WITHDRAW.getTitle());
+
+        return withdraw.getId();
+    }
+
+    /**
+     * 计算提现手续费
+     *
+     * @param withdrawPrice 提现金额
+     * @param percent       手续费百分比
+     * @return 提现手续费
+     */
+    Integer calculateFeePrice(Integer withdrawPrice, Integer percent) {
+        Integer feePrice = 0;
+        if (percent != null && percent > 0) {
+            feePrice = MoneyUtils.calculateRatePrice(withdrawPrice, Double.valueOf(percent));
+        }
+        return feePrice;
+    }
+
+    /**
+     * 校验提现金额要求
+     *
+     * @param withdrawPrice 提现金额
+     * @return 分销配置
+     */
+    TradeConfigDO validateWithdrawPrice(Integer withdrawPrice) {
+        TradeConfigDO tradeConfig = tradeConfigService.getTradeConfig();
+        if (tradeConfig.getBrokerageWithdrawMinPrice() != null && withdrawPrice < tradeConfig.getBrokerageWithdrawMinPrice()) {
+            throw exception(BROKERAGE_WITHDRAW_MIN_PRICE, new Money(0, tradeConfig.getBrokerageWithdrawMinPrice()));
+        }
+        return tradeConfig;
+    }
 }

+ 30 - 0
yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageWithdrawServiceImplTest.java

@@ -2,14 +2,18 @@ package cn.iocoder.yudao.module.trade.service.brokerage;
 
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
+import cn.iocoder.yudao.module.system.api.notify.NotifyMessageSendApi;
 import cn.iocoder.yudao.module.trade.controller.admin.brokerage.vo.withdraw.BrokerageWithdrawPageReqVO;
 import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.BrokerageWithdrawDO;
 import cn.iocoder.yudao.module.trade.dal.mysql.brokerage.BrokerageWithdrawMapper;
+import cn.iocoder.yudao.module.trade.service.config.TradeConfigService;
 import org.junit.jupiter.api.Disabled;
 import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.mock.mockito.MockBean;
 import org.springframework.context.annotation.Import;
 
 import javax.annotation.Resource;
+import javax.validation.Validator;
 
 import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildBetweenTime;
 import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId;
@@ -32,6 +36,19 @@ public class BrokerageWithdrawServiceImplTest extends BaseDbUnitTest {
     @Resource
     private BrokerageWithdrawMapper brokerageWithdrawMapper;
 
+    @MockBean
+    private BrokerageRecordService brokerageRecordService;
+    @MockBean
+    private BrokerageUserService brokerageUserService;
+    @MockBean
+    private TradeConfigService tradeConfigService;
+
+    @MockBean
+    private NotifyMessageSendApi notifyMessageSendApi;
+
+    @Resource
+    private Validator validator;
+
     @Test
     @Disabled  // TODO 请修改 null 为需要的值,然后删除 @Disabled 注解
     public void testGetBrokerageWithdrawPage() {
@@ -84,4 +101,17 @@ public class BrokerageWithdrawServiceImplTest extends BaseDbUnitTest {
         assertPojoEquals(dbBrokerageWithdraw, pageResult.getList().get(0));
     }
 
+    @Test
+    public void testCalculateFeePrice() {
+        Integer withdrawPrice = 100;
+        // 测试手续费比例未设置
+        Integer percent = null;
+        assertEquals(brokerageWithdrawService.calculateFeePrice(withdrawPrice, percent), 0);
+        // 测试手续费给为0
+        percent = 0;
+        assertEquals(brokerageWithdrawService.calculateFeePrice(withdrawPrice, percent), 0);
+        // 测试手续费
+        percent = 1;
+        assertEquals(brokerageWithdrawService.calculateFeePrice(withdrawPrice, percent), 1);
+    }
 }

+ 1 - 1
yudao-module-mall/yudao-module-trade-biz/src/test/resources/sql/create_tables.sql

@@ -186,6 +186,6 @@ CREATE TABLE IF NOT EXISTS "trade_brokerage_withdraw"
     "updater"             varchar           DEFAULT '',
     "update_time"         datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
     "deleted"             bit      NOT NULL DEFAULT FALSE,
-    "tenant_id"      bigint   not null default '0',
+    "tenant_id"      bigint   not null default '0',
     PRIMARY KEY ("id")
 ) COMMENT '佣金提现';