فهرست منبع

Merge remote-tracking branch 'origin/feature/mall_product' into feature/mall_product

# Conflicts:
#	yudao-module-member/yudao-module-member-biz/src/test/resources/sql/create_tables.sql
YunaiV 1 سال پیش
والد
کامیت
eb342d5abf
70فایلهای تغییر یافته به همراه1710 افزوده شده و 784 حذف شده
  1. 89 61
      sql/mysql/brokerage.sql
  2. 0 63
      yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/delegate/DelegatePayClient.java
  3. 3 2
      yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/mock/MockPayClient.java
  4. 0 28
      yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/mock/MockPayClientConfig.java
  5. 1 2
      yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/enums/channel/PayChannelEnum.java
  6. 4 0
      yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/ErrorCodeConstants.java
  7. 1 1
      yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/brokerage/BrokerageBindModeEnum.java
  8. 1 1
      yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/brokerage/BrokerageEnabledConditionEnum.java
  9. 1 1
      yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/brokerage/BrokerageRecordBizTypeEnum.java
  10. 1 1
      yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/brokerage/BrokerageRecordStatusEnum.java
  11. 1 1
      yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/brokerage/BrokerageWithdrawStatusEnum.java
  12. 1 1
      yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/brokerage/BrokerageWithdrawTypeEnum.java
  13. 4 0
      yudao-module-mall/yudao-module-trade-biz/pom.xml
  14. 51 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/record/TradeBrokerageRecordController.java
  15. 2 2
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/record/vo/TradeBrokerageRecordBaseVO.java
  16. 2 2
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/record/vo/TradeBrokerageRecordPageReqVO.java
  17. 2 2
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/record/vo/TradeBrokerageRecordRespVO.java
  18. 71 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/user/TradeBrokerageUserController.java
  19. 43 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/user/vo/TradeBrokerageUserBaseVO.java
  20. 18 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/user/vo/TradeBrokerageUserClearBrokerageUserReqVO.java
  21. 30 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/user/vo/TradeBrokerageUserPageReqVO.java
  22. 22 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/user/vo/TradeBrokerageUserRespVO.java
  23. 22 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/user/vo/TradeBrokerageUserUpdateBrokerageEnabledReqVO.java
  24. 22 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/user/vo/TradeBrokerageUserUpdateBrokerageUserReqVO.java
  25. 45 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/config/TradeConfigController.java
  26. 71 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/config/vo/TradeConfigBaseVO.java
  27. 17 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/config/vo/TradeConfigRespVO.java
  28. 14 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/config/vo/TradeConfigSaveReqVO.java
  29. 13 13
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/brokerage/record/TradeBrokerageRecordConvert.java
  30. 27 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/brokerage/user/TradeBrokerageUserConvert.java
  31. 23 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/config/TradeConfigConvert.java
  32. 3 3
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/order/TradeOrderConvert.java
  33. 6 6
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/brokerage/record/TradeBrokerageRecordDO.java
  34. 56 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/brokerage/user/TradeBrokerageUserDO.java
  35. 90 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/config/TradeConfigDO.java
  36. 47 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/brokerage/record/TradeBrokerageRecordMapper.java
  37. 103 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/brokerage/user/TradeBrokerageUserMapper.java
  38. 15 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/config/TradeConfigMapper.java
  39. 29 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/job/brokerage/TradeBrokerageRecordUnfreezeJob.java
  40. 54 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/record/TradeBrokerageRecordService.java
  41. 232 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/record/TradeBrokerageRecordServiceImpl.java
  42. 3 3
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/record/bo/BrokerageAddReqBO.java
  43. 88 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/user/TradeBrokerageUserService.java
  44. 106 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/user/TradeBrokerageUserServiceImpl.java
  45. 30 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/config/TradeConfigService.java
  46. 44 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/config/TradeConfigServiceImpl.java
  47. 12 7
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java
  48. 26 26
      yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/brokerage/record/TradeBrokerageRecordServiceImplTest.java
  49. 64 0
      yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/brokerage/user/TradeBrokerageUserServiceImplTest.java
  50. 2 0
      yudao-module-mall/yudao-module-trade-biz/src/test/resources/sql/clean.sql
  51. 39 0
      yudao-module-mall/yudao-module-trade-biz/src/test/resources/sql/create_tables.sql
  52. 0 30
      yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/api/brokerage/BrokerageApi.java
  53. 1 0
      yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/enums/ErrorCodeConstants.java
  54. 0 32
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/api/brokerage/BrokerageApiImpl.java
  55. 0 51
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/brokerage/record/MemberBrokerageRecordController.java
  56. 0 57
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/point/vo/config/MemberPointConfigBaseVO.java
  57. 0 60
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/dataobject/point/MemberPointConfigDO.java
  58. 0 27
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/mysql/brokerage/record/MemberBrokerageRecordMapper.java
  59. 0 31
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/mysql/user/MemberUserMapper.java
  60. 0 40
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/brokerage/record/MemberBrokerageRecordService.java
  61. 0 151
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/brokerage/record/MemberBrokerageRecordServiceImpl.java
  62. 0 24
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/user/MemberUserService.java
  63. 0 22
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/user/MemberUserServiceImpl.java
  64. 1 2
      yudao-module-member/yudao-module-member-biz/src/test/resources/sql/clean.sql
  65. 0 1
      yudao-module-member/yudao-module-member-biz/src/test/resources/sql/create_tables.sql
  66. 1 0
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/PayOrderController.java
  67. 4 0
      yudao-ui-admin/src/utils/constants.js
  68. 27 26
      yudao-ui-admin/src/views/pay/app/components/noneConfigChannelForm.vue
  69. 21 4
      yudao-ui-admin/src/views/pay/app/index.vue
  70. 4 0
      yudao-ui-admin/src/views/pay/cashier/index.vue

+ 89 - 61
sql/mysql/brokerage.sql

@@ -1,44 +1,48 @@
--- 增加配置
-alter table member_point_config
-    add column brokerage_enabled bit default 1 not null comment '是否启用分佣';
-alter table member_point_config
-    add column brokerage_enabled_condition tinyint default 0 not null comment '分佣模式:0-人人分销 1-指定分销';
-alter table member_point_config
-    add column brokerage_bind_mode tinyint default 0 not null comment '分销关系绑定模式: 0-没有推广人,1-新用户';
-alter table member_point_config
-    add column brokerage_post_urls varchar(2000) null comment '分销海报图地址数组';
-alter table member_point_config
-    add column brokerage_first_percent int not null comment '一级返佣比例';
-alter table member_point_config
-    add column brokerage_second_percent int not null comment '二级返佣比例';
-alter table member_point_config
-    add column brokerage_withdraw_min_price int not null comment '用户提现最低金额';
-alter table member_point_config
-    add column brokerage_bank_names varchar(200) not null comment '提现银行(字典类型=brokerage_bank_name)';
-alter table member_point_config
-    add column brokerage_frozen_days int default 7 not null comment '佣金冻结时间(天)';
-alter table member_point_config
-    add column brokerage_withdraw_type varchar(32) default '1,2,3,4' not null comment '提现方式:1-钱包;2-银行卡;3-微信;4-支付宝';
-
--- 用户表增加分销相关字段
-alter table member_user
-    add column brokerage_user_id bigint not null comment '推广员编号';
-alter table member_user
-    add column brokerage_bind_time datetime null comment '推广员绑定时间';
-alter table member_user
-    add column brokerage_enabled bit default 1 not null comment '是否成为推广员';
-alter table member_user
-    add column brokerage_time datetime null comment '成为分销员时间';
-alter table member_user
-    add column brokerage_price int default 0 not null comment '可用佣金';
-alter table member_user
-    add column frozen_brokerage_price int default 0 not null comment '冻结佣金';
-
-create index idx_invite_user_id on member_user (brokerage_user_id) comment '推广员编号';
-create index idx_agent on member_user (brokerage_enabled) comment '是否成为推广员';
-
-
-create table member_brokerage_record
+-- 增加配置表
+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 '分佣模式:0-人人分销 1-指定分销',
+    brokerage_bind_mode          tinyint                                default 0                 not null comment '分销关系绑定模式: 0-没有推广人,1-新用户',
+    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) collate utf8mb4_unicode_ci default ''                null comment '创建者',
+    create_time                  datetime                               default CURRENT_TIMESTAMP not null comment '创建时间',
+    updater                      varchar(64) collate utf8mb4_unicode_ci 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 '交易中心配置';
+
+-- 增加分销用户扩展表
+create table trade_brokerage_user
+(
+    id                     bigint auto_increment comment '用户编号' primary key,
+    brokerage_user_id      bigint                                                           not null comment '推广员编号',
+    brokerage_bind_time    datetime                                                         null comment '推广员绑定时间',
+    brokerage_enabled      bit                                    default 1                 not null comment '是否成为推广员',
+    brokerage_time         datetime                                                         null comment '成为分销员时间',
+    brokerage_price        int                                    default 0                 not null comment '可用佣金',
+    frozen_brokerage_price int                                    default 0                 not null comment '冻结佣金',
+    creator                varchar(64) collate utf8mb4_unicode_ci default ''                null comment '创建者',
+    create_time            datetime                               default CURRENT_TIMESTAMP not null comment '创建时间',
+    updater                varchar(64) collate utf8mb4_unicode_ci 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 '分销用户';
+
+create index idx_invite_user_id on trade_brokerage_user (brokerage_user_id) comment '推广员编号';
+create index idx_agent on trade_brokerage_user (brokerage_enabled) comment '是否成为推广员';
+
+
+create table trade_brokerage_record
 (
     id            int auto_increment comment '编号'
         primary key,
@@ -61,12 +65,12 @@ create table member_brokerage_record
 )
     comment '佣金记录';
 
-create index idx_user_id on member_brokerage_record (user_id) comment '用户编号';
-create index idx_biz on member_brokerage_record (biz_type, biz_id) comment '业务';
-create index idx_status on member_brokerage_record (status) comment '状态';
+create index idx_user_id on trade_brokerage_record (user_id) comment '用户编号';
+create index idx_biz on trade_brokerage_record (biz_type, biz_id) comment '业务';
+create index idx_status on trade_brokerage_record (status) comment '状态';
 
 
-create table member_brokerage_withdraw
+create table trade_brokerage_withdraw
 (
     id                  int auto_increment comment '编号'
         primary key,
@@ -93,8 +97,8 @@ create table member_brokerage_withdraw
 )
     comment '佣金提现';
 
-create index idx_user_id on member_brokerage_withdraw (user_id) comment '用户编号';
-create index idx_audit_status on member_brokerage_withdraw (status) comment '状态';
+create index idx_user_id on trade_brokerage_withdraw (user_id) comment '用户编号';
+create index idx_audit_status on trade_brokerage_withdraw (status) comment '状态';
 
 -- 增加字典
 insert into system_dict_type(type, name)
@@ -149,41 +153,65 @@ values ('brokerage_bank_name', '工商银行', 0, 0),
        ('brokerage_bank_name', '交通银行', 4, 4),
        ('brokerage_bank_name', '招商银行', 5, 5);
 
--- 增加菜单:分销员管理
+
+-- 交易中心配置:菜单 SQL
+INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status, component_name)
+VALUES ('交易中心配置', '', 2, 0, 2072, 'config', 'ep:setting', 'trade/config/index', 0, 'TradeConfig');
+-- 按钮父菜单ID
+-- 暂时只支持 MySQL。如果你是 Oracle、PostgreSQL、SQLServer 的话,需要手动修改 @parentId 的部分的代码
+SELECT @parentId := LAST_INSERT_ID();
+-- 按钮 SQL
+INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status)
+VALUES ('交易中心配置查询', 'trade:config:query', 3, 1, @parentId, '', '', '', 0);
+INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status)
+VALUES ('交易中心配置保存', 'trade:config:save', 3, 2, @parentId, '', '', '', 0);
+
+
+-- 增加菜单:分销
+INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status, component_name)
+VALUES ('分销', '', 1, 5, 2072, 'brokerage', 'fa-solid:project-diagram', '', 0, '');
+-- 按钮父菜单ID
+SELECT @brokerageMenuId := LAST_INSERT_ID();
+
+-- 增加菜单:分销员
+-- 菜单 SQL
 INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status, component_name)
-VALUES ('分销员', '', 2, 7, 2262, 'brokerage', 'user', 'member/brokerage/user/index', 0, 'MemberBrokerageUser');
+VALUES ('分销用户', '', 2, 0, @brokerageMenuId, 'brokerage-user', 'fa-solid:user-tie', 'trade/brokerage/user/index', 0,
+        'TradeBrokerageUser');
 -- 按钮父菜单ID
+-- 暂时只支持 MySQL。如果你是 Oracle、PostgreSQL、SQLServer 的话,需要手动修改 @parentId 的部分的代码
 SELECT @parentId := LAST_INSERT_ID();
 -- 按钮 SQL
 INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status)
-VALUES ('分销员查询', 'member:brokerage-user:query', 3, 1, @parentId, '', '', '', 0);
+VALUES ('分销用户查询', 'trade:brokerage-user:query', 3, 1, @parentId, '', '', '', 0);
 INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status)
-VALUES ('分销员创建', 'member:brokerage-user:create', 3, 2, @parentId, '', '', '', 0);
+VALUES ('分销用户修改推广员', 'trade:brokerage-user:update-brokerage-user', 3, 2, @parentId, '', '', '', 0);
 INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status)
-VALUES ('分销员更新', 'member:brokerage-user:update', 3, 3, @parentId, '', '', '', 0);
+VALUES ('分销用户清除推广员', 'trade:brokerage-user:clear-brokerage-user', 3, 3, @parentId, '', '', '', 0);
 INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status)
-VALUES ('分销员删除', 'member:brokerage-user:delete', 3, 4, @parentId, '', '', '', 0);
+VALUES ('分销用户修改推广资格', 'trade:brokerage-user:update-brokerage-enable', 3, 4, @parentId, '', '', '', 0);
+
 
 -- 增加菜单:佣金记录
 INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status, component_name)
-VALUES ('佣金记录', '', 2, 8, 2262, 'brokerage-record', 'list', 'member/brokerage/record/index', 0,
-        'MemberBrokerageRecord');
+VALUES ('佣金记录', '', 2, 1, @brokerageMenuId, 'brokerage-record', 'fa:money', 'trade/brokerage/record/index', 0,
+        'TradeBrokerageRecord');
 -- 按钮父菜单ID
 -- 暂时只支持 MySQL。如果你是 Oracle、PostgreSQL、SQLServer 的话,需要手动修改 @parentId 的部分的代码
 SELECT @parentId := LAST_INSERT_ID();
 -- 按钮 SQL
 INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status)
-VALUES ('佣金记录查询', 'member:member-brokerage-record:query', 3, 1, @parentId, '', 'table', '', 0);
+VALUES ('佣金记录查询', 'trade:brokerage-record:query', 3, 1, @parentId, '', '', '', 0);
 
 -- 增加菜单:佣金提现
 INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status, component_name)
-VALUES ('佣金提现', '', 2, 9, 2262, 'brokerage-withdraw', '', 'member/brokerage/withdraw/index', 0,
-        'MemberBrokerageWithdraw');
-
+VALUES ('佣金提现', '', 2, 2, @brokerageMenuId, 'brokerage-withdraw', 'fa:credit-card',
+        'trade/brokerage/withdraw/index', 0, 'TradeBrokerageWithdraw');
 -- 按钮父菜单ID
 -- 暂时只支持 MySQL。如果你是 Oracle、PostgreSQL、SQLServer 的话,需要手动修改 @parentId 的部分的代码
 SELECT @parentId := LAST_INSERT_ID();
-
 -- 按钮 SQL
 INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status)
-VALUES ('佣金提现查询', 'member:brokerage-withdraw:query', 3, 1, @parentId, '', '', '', 0);
+VALUES ('佣金提现查询', 'trade:brokerage-withdraw:query', 3, 1, @parentId, '', '', '', 0);
+INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status)
+VALUES ('佣金提现审核', 'trade:brokerage-withdraw:audit', 3, 2, @parentId, '', '', '', 0);

+ 0 - 63
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/delegate/DelegatePayClient.java

@@ -1,63 +0,0 @@
-package cn.iocoder.yudao.framework.pay.core.client.impl.delegate;
-
-import cn.iocoder.yudao.framework.pay.core.client.PayClientConfig;
-import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderRespDTO;
-import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO;
-import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundRespDTO;
-import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedReqDTO;
-import cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient;
-
-import java.util.Map;
-
-// TODO @jason:其它模块,主要是无法 pay client 初始化存在问题,所以我感觉,是不是可以搞个 PayClientInitializer 接口。这样,PayClientFactory 去 get 这个支付模式对应的 PayClientInitializer,通过它来创建。具体注入的地方,可以在 PayChannel init 方法那;
-/**
- * 代理支付 Client 的抽象类。
- *
- * 用于支付 Client 由其它模块实现,例如钱包支付
- *
- * @author jason
- */
-public abstract class DelegatePayClient<Config extends PayClientConfig> extends AbstractPayClient<PayClientConfig> {
-
-    private final DelegatePayClient<Config> delegate;
-
-    public DelegatePayClient(Long channelId, String channelCode, PayClientConfig config) {
-        super(channelId, channelCode, config);
-        delegate = this;
-    }
-
-    @Override
-    protected void doInit() {
-        delegate.doInit();
-    }
-
-    @Override
-    protected PayOrderRespDTO doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO)  {
-        return delegate.doUnifiedOrder(reqDTO);
-    }
-
-    @Override
-    protected PayOrderRespDTO doGetOrder(String outTradeNo)  {
-        return delegate.doGetOrder(outTradeNo);
-    }
-
-    @Override
-    protected PayRefundRespDTO doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO)  {
-        return delegate.doUnifiedRefund(reqDTO);
-    }
-
-    @Override
-    protected PayRefundRespDTO doGetRefund(String outTradeNo, String outRefundNo) {
-        return delegate.doGetRefund(outTradeNo, outRefundNo);
-    }
-
-    @Override
-    protected PayRefundRespDTO doParseRefundNotify(Map<String,String> params, String body)  {
-        return delegate.doParseRefundNotify(params, body);
-    }
-
-    @Override
-    protected PayOrderRespDTO doParseOrderNotify(Map<String,String> params, String body)  {
-        return delegate.doParseOrderNotify(params, body);
-    }
-}

+ 3 - 2
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/mock/MockPayClient.java

@@ -5,6 +5,7 @@ import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDT
 import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundRespDTO;
 import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedReqDTO;
 import cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient;
+import cn.iocoder.yudao.framework.pay.core.client.impl.NonePayClientConfig;
 import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum;
 
 import java.time.LocalDateTime;
@@ -17,11 +18,11 @@ import java.util.Map;
  *
  * @author jason
  */
-public class MockPayClient extends AbstractPayClient<MockPayClientConfig> {
+public class MockPayClient extends AbstractPayClient<NonePayClientConfig> {
 
     private static final String MOCK_RESP_SUCCESS_DATA = "MOCK_SUCCESS";
 
-    public MockPayClient(Long channelId, MockPayClientConfig config) {
+    public MockPayClient(Long channelId, NonePayClientConfig config) {
         super(channelId, PayChannelEnum.MOCK.getCode(), config);
     }
 

+ 0 - 28
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/mock/MockPayClientConfig.java

@@ -1,28 +0,0 @@
-package cn.iocoder.yudao.framework.pay.core.client.impl.mock;
-
-import cn.iocoder.yudao.framework.pay.core.client.PayClientConfig;
-import lombok.Data;
-
-import javax.validation.Validator;
-
-/**
- * 模拟支付的 PayClientConfig 实现类
- *
- * @author jason
- */
-@Data
-public class MockPayClientConfig implements PayClientConfig {
-
-    /**
-     * 配置名称
-     *
-     * 如果不加任何属性,JsonUtils.parseObject2 解析会报错,所以暂时加个名称
-     */
-    private String name;
-
-    @Override
-    public void validate(Validator validator) {
-        // 模拟支付配置无需校验
-    }
-
-}

+ 1 - 2
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/enums/channel/PayChannelEnum.java

@@ -4,7 +4,6 @@ import cn.hutool.core.util.ArrayUtil;
 import cn.iocoder.yudao.framework.pay.core.client.impl.NonePayClientConfig;
 import cn.iocoder.yudao.framework.pay.core.client.PayClientConfig;
 import cn.iocoder.yudao.framework.pay.core.client.impl.alipay.AlipayPayClientConfig;
-import cn.iocoder.yudao.framework.pay.core.client.impl.mock.MockPayClientConfig;
 import cn.iocoder.yudao.framework.pay.core.client.impl.weixin.WxPayClientConfig;
 import lombok.AllArgsConstructor;
 import lombok.Getter;
@@ -30,7 +29,7 @@ public enum PayChannelEnum {
     ALIPAY_QR("alipay_qr", "支付宝扫码支付", AlipayPayClientConfig.class),
     ALIPAY_BAR("alipay_bar", "支付宝条码支付", AlipayPayClientConfig.class),
 
-    MOCK("mock", "模拟支付", MockPayClientConfig.class),
+    MOCK("mock", "模拟支付", NonePayClientConfig.class),
 
     WALLET("wallet", "钱包支付", NonePayClientConfig.class);
 

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

@@ -74,4 +74,8 @@ public interface ErrorCodeConstants {
     // ==========  物流 PICK_UP 模块 1011006000 ==========
     ErrorCode PICK_UP_STORE_NOT_EXISTS = new ErrorCode(1011006000, "自提门店不存在");
 
+
+    // ==========  分销 分销用户 模块 1011007000 ==========
+    ErrorCode BROKERAGE_USER_NOT_EXISTS = new ErrorCode(1011007000, "分销用户不存在");
+
 }

+ 1 - 1
yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/enums/brokerage/BrokerageBindModeEnum.java → yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/brokerage/BrokerageBindModeEnum.java

@@ -1,4 +1,4 @@
-package cn.iocoder.yudao.module.member.enums.brokerage;
+package cn.iocoder.yudao.module.trade.enums.brokerage;
 
 import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
 import lombok.AllArgsConstructor;

+ 1 - 1
yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/enums/brokerage/BrokerageEnabledConditionEnum.java → yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/brokerage/BrokerageEnabledConditionEnum.java

@@ -1,4 +1,4 @@
-package cn.iocoder.yudao.module.member.enums.brokerage;
+package cn.iocoder.yudao.module.trade.enums.brokerage;
 
 import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
 import lombok.AllArgsConstructor;

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

@@ -1,4 +1,4 @@
-package cn.iocoder.yudao.module.member.enums.brokerage;
+package cn.iocoder.yudao.module.trade.enums.brokerage;
 
 import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
 import lombok.AllArgsConstructor;

+ 1 - 1
yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/enums/brokerage/BrokerageRecordStatusEnum.java → yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/brokerage/BrokerageRecordStatusEnum.java

@@ -1,4 +1,4 @@
-package cn.iocoder.yudao.module.member.enums.brokerage;
+package cn.iocoder.yudao.module.trade.enums.brokerage;
 
 import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
 import lombok.AllArgsConstructor;

+ 1 - 1
yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/enums/brokerage/BrokerageWithdrawStatusEnum.java → yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/brokerage/BrokerageWithdrawStatusEnum.java

@@ -1,4 +1,4 @@
-package cn.iocoder.yudao.module.member.enums.brokerage;
+package cn.iocoder.yudao.module.trade.enums.brokerage;
 
 import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
 import lombok.AllArgsConstructor;

+ 1 - 1
yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/enums/brokerage/BrokerageWithdrawTypeEnum.java → yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/brokerage/BrokerageWithdrawTypeEnum.java

@@ -1,4 +1,4 @@
-package cn.iocoder.yudao.module.member.enums.brokerage;
+package cn.iocoder.yudao.module.trade.enums.brokerage;
 
 import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
 import lombok.AllArgsConstructor;

+ 4 - 0
yudao-module-mall/yudao-module-trade-biz/pom.xml

@@ -54,6 +54,10 @@
             <groupId>cn.iocoder.boot</groupId>
             <artifactId>yudao-spring-boot-starter-biz-operatelog</artifactId>
         </dependency>
+        <dependency>
+            <groupId>cn.iocoder.boot</groupId>
+            <artifactId>yudao-spring-boot-starter-biz-tenant</artifactId>
+        </dependency>
         <dependency>
             <groupId>cn.iocoder.boot</groupId>
             <artifactId>yudao-spring-boot-starter-biz-ip</artifactId>

+ 51 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/record/TradeBrokerageRecordController.java

@@ -0,0 +1,51 @@
+package cn.iocoder.yudao.module.trade.controller.admin.brokerage.record;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.trade.controller.admin.brokerage.record.vo.TradeBrokerageRecordPageReqVO;
+import cn.iocoder.yudao.module.trade.controller.admin.brokerage.record.vo.TradeBrokerageRecordRespVO;
+import cn.iocoder.yudao.module.trade.convert.brokerage.record.TradeBrokerageRecordConvert;
+import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.record.TradeBrokerageRecordDO;
+import cn.iocoder.yudao.module.trade.service.brokerage.record.TradeBrokerageRecordService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+import javax.validation.Valid;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+@Tag(name = "管理后台 - 佣金记录")
+@RestController
+@RequestMapping("/trade/brokerage-record")
+@Validated
+public class TradeBrokerageRecordController {
+
+    @Resource
+    private TradeBrokerageRecordService tradeBrokerageRecordService;
+
+    @GetMapping("/get")
+    @Operation(summary = "获得佣金记录")
+    @Parameter(name = "id", description = "编号", required = true, example = "1024")
+    @PreAuthorize("@ss.hasPermission('trade:brokerage-record:query')")
+    public CommonResult<TradeBrokerageRecordRespVO> getBrokerageRecord(@RequestParam("id") Integer id) {
+        TradeBrokerageRecordDO tradeBrokerageRecord = tradeBrokerageRecordService.getBrokerageRecord(id);
+        return success(TradeBrokerageRecordConvert.INSTANCE.convert(tradeBrokerageRecord));
+    }
+
+    @GetMapping("/page")
+    @Operation(summary = "获得佣金记录分页")
+    @PreAuthorize("@ss.hasPermission('trade:brokerage-record:query')")
+    public CommonResult<PageResult<TradeBrokerageRecordRespVO>> getBrokerageRecordPage(@Valid TradeBrokerageRecordPageReqVO pageVO) {
+        PageResult<TradeBrokerageRecordDO> pageResult = tradeBrokerageRecordService.getBrokerageRecordPage(pageVO);
+        return success(TradeBrokerageRecordConvert.INSTANCE.convertPage(pageResult));
+    }
+
+}

+ 2 - 2
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/brokerage/record/vo/MemberBrokerageRecordBaseVO.java → yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/record/vo/TradeBrokerageRecordBaseVO.java

@@ -1,4 +1,4 @@
-package cn.iocoder.yudao.module.member.controller.admin.brokerage.record.vo;
+package cn.iocoder.yudao.module.trade.controller.admin.brokerage.record.vo;
 
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
@@ -14,7 +14,7 @@ import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_
  * 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
  */
 @Data
-public class MemberBrokerageRecordBaseVO {
+public class TradeBrokerageRecordBaseVO {
 
     @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "25973")
     @NotNull(message = "用户编号不能为空")

+ 2 - 2
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/brokerage/record/vo/MemberBrokerageRecordPageReqVO.java → yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/record/vo/TradeBrokerageRecordPageReqVO.java

@@ -1,4 +1,4 @@
-package cn.iocoder.yudao.module.member.controller.admin.brokerage.record.vo;
+package cn.iocoder.yudao.module.trade.controller.admin.brokerage.record.vo;
 
 import cn.iocoder.yudao.framework.common.pojo.PageParam;
 import io.swagger.v3.oas.annotations.media.Schema;
@@ -15,7 +15,7 @@ import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_
 @Data
 @EqualsAndHashCode(callSuper = true)
 @ToString(callSuper = true)
-public class MemberBrokerageRecordPageReqVO extends PageParam {
+public class TradeBrokerageRecordPageReqVO extends PageParam {
 
     @Schema(description = "用户编号", example = "25973")
     private Long userId;

+ 2 - 2
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/brokerage/record/vo/MemberBrokerageRecordRespVO.java → yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/record/vo/TradeBrokerageRecordRespVO.java

@@ -1,4 +1,4 @@
-package cn.iocoder.yudao.module.member.controller.admin.brokerage.record.vo;
+package cn.iocoder.yudao.module.trade.controller.admin.brokerage.record.vo;
 
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
@@ -11,7 +11,7 @@ import java.time.LocalDateTime;
 @Data
 @EqualsAndHashCode(callSuper = true)
 @ToString(callSuper = true)
-public class MemberBrokerageRecordRespVO extends MemberBrokerageRecordBaseVO {
+public class TradeBrokerageRecordRespVO extends TradeBrokerageRecordBaseVO {
 
     @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "28896")
     private Integer id;

+ 71 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/user/TradeBrokerageUserController.java

@@ -0,0 +1,71 @@
+package cn.iocoder.yudao.module.trade.controller.admin.brokerage.user;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.trade.controller.admin.brokerage.user.vo.*;
+import cn.iocoder.yudao.module.trade.convert.brokerage.user.TradeBrokerageUserConvert;
+import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.user.TradeBrokerageUserDO;
+import cn.iocoder.yudao.module.trade.service.brokerage.user.TradeBrokerageUserService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+import javax.validation.Valid;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+@Tag(name = "管理后台 - 分销用户")
+@RestController
+@RequestMapping("/trade/brokerage-user")
+@Validated
+public class TradeBrokerageUserController {
+
+    @Resource
+    private TradeBrokerageUserService brokerageUserService;
+
+    @PutMapping("/update-brokerage-user")
+    @Operation(summary = "修改推广员")
+    @PreAuthorize("@ss.hasPermission('trade:brokerage-user:update-brokerage-user')")
+    public CommonResult<Boolean> updateBrokerageUser(@Valid @RequestBody TradeBrokerageUserUpdateBrokerageUserReqVO updateReqVO) {
+        brokerageUserService.updateBrokerageUserId(updateReqVO.getId(), updateReqVO.getBrokerageUserId());
+        return success(true);
+    }
+
+    @PutMapping("/clear-brokerage-user")
+    @Operation(summary = "清除推广员")
+    @PreAuthorize("@ss.hasPermission('trade:brokerage-user:clear-brokerage-user')")
+    public CommonResult<Boolean> clearBrokerageUser(@Valid @RequestBody TradeBrokerageUserClearBrokerageUserReqVO updateReqVO) {
+        brokerageUserService.updateBrokerageUserId(updateReqVO.getId(), null);
+        return success(true);
+    }
+
+    @PutMapping("/update-brokerage-enable")
+    @Operation(summary = "修改推广资格")
+    @PreAuthorize("@ss.hasPermission('trade:brokerage-user:update-brokerage-enable')")
+    public CommonResult<Boolean> updateBrokerageEnabled(@Valid @RequestBody TradeBrokerageUserUpdateBrokerageEnabledReqVO updateReqVO) {
+        brokerageUserService.updateBrokerageEnabled(updateReqVO.getId(), updateReqVO.getBrokerageEnabled());
+        return success(true);
+    }
+
+    @GetMapping("/get")
+    @Operation(summary = "获得分销用户")
+    @Parameter(name = "id", description = "编号", required = true, example = "1024")
+    @PreAuthorize("@ss.hasPermission('trade:brokerage-user:query')")
+    public CommonResult<TradeBrokerageUserRespVO> getBrokerageUser(@RequestParam("id") Long id) {
+        TradeBrokerageUserDO brokerageUser = brokerageUserService.getBrokerageUser(id);
+        return success(TradeBrokerageUserConvert.INSTANCE.convert(brokerageUser));
+    }
+
+    @GetMapping("/page")
+    @Operation(summary = "获得分销用户分页")
+    @PreAuthorize("@ss.hasPermission('trade:brokerage-user:query')")
+    public CommonResult<PageResult<TradeBrokerageUserRespVO>> getBrokerageUserPage(@Valid TradeBrokerageUserPageReqVO pageVO) {
+        PageResult<TradeBrokerageUserDO> pageResult = brokerageUserService.getBrokerageUserPage(pageVO);
+        return success(TradeBrokerageUserConvert.INSTANCE.convertPage(pageResult));
+    }
+
+}

+ 43 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/user/vo/TradeBrokerageUserBaseVO.java

@@ -0,0 +1,43 @@
+package cn.iocoder.yudao.module.trade.controller.admin.brokerage.user.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import javax.validation.constraints.NotNull;
+import java.time.LocalDateTime;
+
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
+/**
+ * 分销用户 Base VO,提供给添加、修改、详细的子 VO 使用
+ * 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
+ */
+@Data
+public class TradeBrokerageUserBaseVO {
+
+    @Schema(description = "推广员编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "4587")
+    @NotNull(message = "推广员编号不能为空")
+    private Long brokerageUserId;
+
+    @Schema(description = "推广员绑定时间")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    private LocalDateTime brokerageBindTime;
+
+    @Schema(description = "推广资格", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotNull(message = "推广资格不能为空")
+    private Boolean brokerageEnabled;
+
+    @Schema(description = "成为分销员时间")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    private LocalDateTime brokerageTime;
+
+    @Schema(description = "可用佣金", requiredMode = Schema.RequiredMode.REQUIRED, example = "11089")
+    @NotNull(message = "可用佣金不能为空")
+    private Integer brokeragePrice;
+
+    @Schema(description = "冻结佣金", requiredMode = Schema.RequiredMode.REQUIRED, example = "30916")
+    @NotNull(message = "冻结佣金不能为空")
+    private Integer frozenBrokeragePrice;
+
+}

+ 18 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/user/vo/TradeBrokerageUserClearBrokerageUserReqVO.java

@@ -0,0 +1,18 @@
+package cn.iocoder.yudao.module.trade.controller.admin.brokerage.user.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.ToString;
+
+import javax.validation.constraints.NotNull;
+
+@Schema(description = "管理后台 - 分销用户 - 清除推广员 Request VO")
+@Data
+@ToString(callSuper = true)
+public class TradeBrokerageUserClearBrokerageUserReqVO {
+
+    @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "20019")
+    @NotNull(message = "用户编号不能为空")
+    private Long id;
+
+}

+ 30 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/user/vo/TradeBrokerageUserPageReqVO.java

@@ -0,0 +1,30 @@
+package cn.iocoder.yudao.module.trade.controller.admin.brokerage.user.vo;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.time.LocalDateTime;
+
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
+@Schema(description = "管理后台 - 分销用户分页 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class TradeBrokerageUserPageReqVO extends PageParam {
+
+    @Schema(description = "推广员编号", example = "4587")
+    private Long brokerageUserId;
+
+    @Schema(description = "推广资格")
+    private Boolean brokerageEnabled;
+
+    @Schema(description = "创建时间")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    private LocalDateTime[] createTime;
+
+}

+ 22 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/user/vo/TradeBrokerageUserRespVO.java

@@ -0,0 +1,22 @@
+package cn.iocoder.yudao.module.trade.controller.admin.brokerage.user.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+import java.time.LocalDateTime;
+
+@Schema(description = "管理后台 - 分销用户 Response VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class TradeBrokerageUserRespVO extends TradeBrokerageUserBaseVO {
+
+    @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "20019")
+    private Long id;
+
+    @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
+    private LocalDateTime createTime;
+
+}

+ 22 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/user/vo/TradeBrokerageUserUpdateBrokerageEnabledReqVO.java

@@ -0,0 +1,22 @@
+package cn.iocoder.yudao.module.trade.controller.admin.brokerage.user.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.ToString;
+
+import javax.validation.constraints.NotNull;
+
+@Schema(description = "管理后台 - 分销用户 - 修改推广员 Request VO")
+@Data
+@ToString(callSuper = true)
+public class TradeBrokerageUserUpdateBrokerageEnabledReqVO {
+
+    @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "20019")
+    @NotNull(message = "用户编号不能为空")
+    private Long id;
+
+    @Schema(description = "推广资格", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotNull(message = "推广资格不能为空")
+    private Boolean brokerageEnabled;
+
+}

+ 22 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/user/vo/TradeBrokerageUserUpdateBrokerageUserReqVO.java

@@ -0,0 +1,22 @@
+package cn.iocoder.yudao.module.trade.controller.admin.brokerage.user.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.ToString;
+
+import javax.validation.constraints.NotNull;
+
+@Schema(description = "管理后台 - 分销用户 - 修改推广员 Request VO")
+@Data
+@ToString(callSuper = true)
+public class TradeBrokerageUserUpdateBrokerageUserReqVO {
+
+    @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "20019")
+    @NotNull(message = "用户编号不能为空")
+    private Long id;
+
+    @Schema(description = "推广员编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "4587")
+    @NotNull(message = "推广员编号不能为空")
+    private Long brokerageUserId;
+
+}

+ 45 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/config/TradeConfigController.java

@@ -0,0 +1,45 @@
+package cn.iocoder.yudao.module.trade.controller.admin.config;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.module.trade.controller.admin.config.vo.TradeConfigRespVO;
+import cn.iocoder.yudao.module.trade.controller.admin.config.vo.TradeConfigSaveReqVO;
+import cn.iocoder.yudao.module.trade.convert.config.TradeConfigConvert;
+import cn.iocoder.yudao.module.trade.dal.dataobject.config.TradeConfigDO;
+import cn.iocoder.yudao.module.trade.service.config.TradeConfigService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+import javax.validation.Valid;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+@Tag(name = "管理后台 - 交易中心配置")
+@RestController
+@RequestMapping("/trade/config")
+@Validated
+public class TradeConfigController {
+
+    @Resource
+    private TradeConfigService tradeConfigService;
+
+    @PutMapping("/save")
+    @Operation(summary = "更新交易中心配置")
+    @PreAuthorize("@ss.hasPermission('trade:config:save')")
+    public CommonResult<Boolean> updateConfig(@Valid @RequestBody TradeConfigSaveReqVO updateReqVO) {
+        tradeConfigService.saveTradeConfig(updateReqVO);
+        return success(true);
+    }
+
+    @GetMapping("/get")
+    @Operation(summary = "获得交易中心配置")
+    @PreAuthorize("@ss.hasPermission('trade:config:query')")
+    public CommonResult<TradeConfigRespVO> getConfig() {
+        TradeConfigDO config = tradeConfigService.getTradeConfig();
+        return success(TradeConfigConvert.INSTANCE.convert(config));
+    }
+
+}

+ 71 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/config/vo/TradeConfigBaseVO.java

@@ -0,0 +1,71 @@
+package cn.iocoder.yudao.module.trade.controller.admin.config.vo;
+
+import cn.iocoder.yudao.framework.common.validation.InEnum;
+import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageBindModeEnum;
+import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageEnabledConditionEnum;
+import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageWithdrawTypeEnum;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import org.hibernate.validator.constraints.Range;
+
+import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.NotNull;
+import javax.validation.constraints.PositiveOrZero;
+import java.util.List;
+
+/**
+ * 交易中心配置 Base VO,提供给添加、修改、详细的子 VO 使用
+ * 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
+ */
+@Data
+public class TradeConfigBaseVO {
+
+    // ========== 分销相关 ==========
+
+    @Schema(description = "是否启用分佣", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
+    @NotNull(message = "是否启用分佣不能为空")
+    private Boolean brokerageEnabled;
+
+    @Schema(description = "分佣模式", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
+    @NotNull(message = "分佣模式不能为空")
+    @InEnum(value = BrokerageEnabledConditionEnum.class, message = "分佣模式必须是 {value}")
+    private Integer brokerageEnabledCondition;
+
+    @Schema(description = "分销关系绑定模式", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
+    @NotNull(message = "分销关系绑定模式不能为空")
+    @InEnum(value = BrokerageBindModeEnum.class, message = "分销关系绑定模式必须是 {value}")
+    private Integer brokerageBindMode;
+
+    @Schema(description = "分销海报图地址数组", requiredMode = Schema.RequiredMode.REQUIRED, example = "[https://www.iocoder.cn/yudao.jpg]")
+    private List<String> brokeragePostUrls;
+
+    @Schema(description = "一级返佣比例", requiredMode = Schema.RequiredMode.REQUIRED, example = "5")
+    @NotNull(message = "一级返佣比例不能为空")
+    @Range(min = 0, max = 100, message = "一级返佣比例必须在 0 - 100 之间")
+    private Integer brokerageFirstPercent;
+
+    @Schema(description = "二级返佣比例", requiredMode = Schema.RequiredMode.REQUIRED, example = "5")
+    @NotNull(message = "二级返佣比例不能为空")
+    @Range(min = 0, max = 100, message = "二级返佣比例必须在 0 - 100 之间")
+    private Integer brokerageSecondPercent;
+
+    @Schema(description = "用户提现最低金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "1000")
+    @NotNull(message = "用户提现最低金额不能为空")
+    @PositiveOrZero(message = "用户提现最低金额不能是负数")
+    private Integer brokerageWithdrawMinPrice;
+
+    @Schema(description = "提现银行", requiredMode = Schema.RequiredMode.REQUIRED, example = "[0, 1]")
+    @NotEmpty(message = "提现银行不能为空")
+    private List<Integer> brokerageBankNames;
+
+    @Schema(description = "佣金冻结时间(天)", requiredMode = Schema.RequiredMode.REQUIRED, example = "7")
+    @NotNull(message = "佣金冻结时间(天)不能为空")
+    @PositiveOrZero(message = "佣金冻结时间不能是负数")
+    private Integer brokerageFrozenDays;
+
+    @Schema(description = "提现方式", requiredMode = Schema.RequiredMode.REQUIRED, example = "[0, 1]")
+    @NotNull(message = "提现方式不能为空")
+    @InEnum(value = BrokerageWithdrawTypeEnum.class, message = "提现方式必须是 {value}")
+    private List<Integer> brokerageWithdrawType;
+
+}

+ 17 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/config/vo/TradeConfigRespVO.java

@@ -0,0 +1,17 @@
+package cn.iocoder.yudao.module.trade.controller.admin.config.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+@Schema(description = "管理后台 - 交易中心配置 Response VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class TradeConfigRespVO extends TradeConfigBaseVO {
+
+    @Schema(description = "自增主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+    private Long id;
+
+}

+ 14 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/config/vo/TradeConfigSaveReqVO.java

@@ -0,0 +1,14 @@
+package cn.iocoder.yudao.module.trade.controller.admin.config.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+@Schema(description = "管理后台 - 交易中心配置更新 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class TradeConfigSaveReqVO extends TradeConfigBaseVO {
+
+}

+ 13 - 13
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/convert/brokerage/record/MemberBrokerageRecordConvert.java → yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/brokerage/record/TradeBrokerageRecordConvert.java

@@ -1,12 +1,12 @@
-package cn.iocoder.yudao.module.member.convert.brokerage.record;
+package cn.iocoder.yudao.module.trade.convert.brokerage.record;
 
 import cn.hutool.core.util.StrUtil;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.module.member.controller.admin.brokerage.record.vo.MemberBrokerageRecordRespVO;
-import cn.iocoder.yudao.module.member.dal.dataobject.brokerage.record.MemberBrokerageRecordDO;
-import cn.iocoder.yudao.module.member.dal.dataobject.user.MemberUserDO;
-import cn.iocoder.yudao.module.member.enums.brokerage.BrokerageRecordBizTypeEnum;
-import cn.iocoder.yudao.module.member.enums.brokerage.BrokerageRecordStatusEnum;
+import cn.iocoder.yudao.module.trade.controller.admin.brokerage.record.vo.TradeBrokerageRecordRespVO;
+import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.record.TradeBrokerageRecordDO;
+import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.user.TradeBrokerageUserDO;
+import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageRecordBizTypeEnum;
+import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageRecordStatusEnum;
 import org.mapstruct.Mapper;
 import org.mapstruct.factory.Mappers;
 
@@ -19,22 +19,22 @@ import java.util.List;
  * @author owen
  */
 @Mapper
-public interface MemberBrokerageRecordConvert {
+public interface TradeBrokerageRecordConvert {
 
-    MemberBrokerageRecordConvert INSTANCE = Mappers.getMapper(MemberBrokerageRecordConvert.class);
+    TradeBrokerageRecordConvert INSTANCE = Mappers.getMapper(TradeBrokerageRecordConvert.class);
 
-    MemberBrokerageRecordRespVO convert(MemberBrokerageRecordDO bean);
+    TradeBrokerageRecordRespVO convert(TradeBrokerageRecordDO bean);
 
-    List<MemberBrokerageRecordRespVO> convertList(List<MemberBrokerageRecordDO> list);
+    List<TradeBrokerageRecordRespVO> convertList(List<TradeBrokerageRecordDO> list);
 
-    PageResult<MemberBrokerageRecordRespVO> convertPage(PageResult<MemberBrokerageRecordDO> page);
+    PageResult<TradeBrokerageRecordRespVO> convertPage(PageResult<TradeBrokerageRecordDO> page);
 
-    default MemberBrokerageRecordDO convert(MemberUserDO user, String bizId, int brokerageFrozenDays, int brokerage, LocalDateTime unfreezeTime) {
+    default TradeBrokerageRecordDO convert(TradeBrokerageUserDO user, String bizId, int brokerageFrozenDays, int brokerage, LocalDateTime unfreezeTime) {
         // 不冻结时,佣金直接就是结算状态
         Integer status = brokerageFrozenDays > 0
                 ? BrokerageRecordStatusEnum.WAIT_SETTLEMENT.getStatus()
                 : BrokerageRecordStatusEnum.SETTLEMENT.getStatus();
-        return new MemberBrokerageRecordDO()
+        return new TradeBrokerageRecordDO()
                 .setUserId(user.getId())
                 .setBizType(BrokerageRecordBizTypeEnum.ORDER.getType())
                 .setBizId(bizId)

+ 27 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/brokerage/user/TradeBrokerageUserConvert.java

@@ -0,0 +1,27 @@
+package cn.iocoder.yudao.module.trade.convert.brokerage.user;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.trade.controller.admin.brokerage.user.vo.TradeBrokerageUserRespVO;
+import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.user.TradeBrokerageUserDO;
+import org.mapstruct.Mapper;
+import org.mapstruct.factory.Mappers;
+
+import java.util.List;
+
+/**
+ * 分销用户 Convert
+ *
+ * @author owen
+ */
+@Mapper
+public interface TradeBrokerageUserConvert {
+
+    TradeBrokerageUserConvert INSTANCE = Mappers.getMapper(TradeBrokerageUserConvert.class);
+
+    TradeBrokerageUserRespVO convert(TradeBrokerageUserDO bean);
+
+    List<TradeBrokerageUserRespVO> convertList(List<TradeBrokerageUserDO> list);
+
+    PageResult<TradeBrokerageUserRespVO> convertPage(PageResult<TradeBrokerageUserDO> page);
+
+}

+ 23 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/config/TradeConfigConvert.java

@@ -0,0 +1,23 @@
+package cn.iocoder.yudao.module.trade.convert.config;
+
+import cn.iocoder.yudao.module.trade.controller.admin.config.vo.TradeConfigRespVO;
+import cn.iocoder.yudao.module.trade.controller.admin.config.vo.TradeConfigSaveReqVO;
+import cn.iocoder.yudao.module.trade.dal.dataobject.config.TradeConfigDO;
+import org.mapstruct.Mapper;
+import org.mapstruct.factory.Mappers;
+
+/**
+ * 交易中心配置 Convert
+ *
+ * @author owen
+ */
+@Mapper
+public interface TradeConfigConvert {
+
+    TradeConfigConvert INSTANCE = Mappers.getMapper(TradeConfigConvert.class);
+
+    TradeConfigDO convert(TradeConfigSaveReqVO bean);
+
+    TradeConfigRespVO convert(TradeConfigDO bean);
+
+}

+ 3 - 3
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/order/TradeOrderConvert.java

@@ -7,7 +7,7 @@ import cn.iocoder.yudao.framework.common.util.string.StrUtils;
 import cn.iocoder.yudao.framework.dict.core.util.DictFrameworkUtils;
 import cn.iocoder.yudao.framework.ip.core.utils.AreaUtils;
 import cn.iocoder.yudao.module.member.api.address.dto.AddressRespDTO;
-import cn.iocoder.yudao.module.member.api.brokerage.dto.BrokerageAddReqDTO;
+import cn.iocoder.yudao.module.trade.service.brokerage.record.bo.BrokerageAddReqBO;
 import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
 import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderCreateReqDTO;
 import cn.iocoder.yudao.module.pay.enums.DictTypeConstants;
@@ -275,8 +275,8 @@ public interface TradeOrderConvert {
 
     TradeOrderDO convert(TradeOrderRemarkReqVO reqVO);
 
-    default BrokerageAddReqDTO convert(TradeOrderItemDO item, ProductSkuRespDTO sku) {
-        return new BrokerageAddReqDTO().setBizId(String.valueOf(item.getId()))
+    default BrokerageAddReqBO convert(TradeOrderItemDO item, ProductSkuRespDTO sku) {
+        return new BrokerageAddReqBO().setBizId(String.valueOf(item.getId()))
                 .setPayPrice(item.getPayPrice()).setCount(item.getCount())
                 .setSkuFirstBrokeragePrice(sku.getSubCommissionFirstPrice())
                 .setSkuSecondBrokeragePrice(sku.getSubCommissionSecondPrice());

+ 6 - 6
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/dataobject/brokerage/record/MemberBrokerageRecordDO.java → yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/brokerage/record/TradeBrokerageRecordDO.java

@@ -1,8 +1,8 @@
-package cn.iocoder.yudao.module.member.dal.dataobject.brokerage.record;
+package cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.record;
 
 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
-import cn.iocoder.yudao.module.member.enums.brokerage.BrokerageRecordBizTypeEnum;
-import cn.iocoder.yudao.module.member.enums.brokerage.BrokerageRecordStatusEnum;
+import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageRecordBizTypeEnum;
+import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageRecordStatusEnum;
 import com.baomidou.mybatisplus.annotation.KeySequence;
 import com.baomidou.mybatisplus.annotation.TableId;
 import com.baomidou.mybatisplus.annotation.TableName;
@@ -15,15 +15,15 @@ import java.time.LocalDateTime;
  *
  * @author owen
  */
-@TableName("member_brokerage_record")
-@KeySequence("member_brokerage_record_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@TableName("trade_brokerage_record")
+@KeySequence("trade_brokerage_record_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
 @Data
 @EqualsAndHashCode(callSuper = true)
 @ToString(callSuper = true)
 @Builder
 @NoArgsConstructor
 @AllArgsConstructor
-public class MemberBrokerageRecordDO extends BaseDO {
+public class TradeBrokerageRecordDO extends BaseDO {
 
     /**
      * 编号

+ 56 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/brokerage/user/TradeBrokerageUserDO.java

@@ -0,0 +1,56 @@
+package cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.user;
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+
+import java.time.LocalDateTime;
+
+/**
+ * 分销用户 DO
+ *
+ * @author owen
+ */
+@TableName("trade_brokerage_user")
+@KeySequence("trade_brokerage_user_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class TradeBrokerageUserDO extends BaseDO {
+
+    /**
+     * 用户编号
+     */
+    @TableId
+    private Long id;
+    /**
+     * 推广员编号
+     */
+    private Long brokerageUserId;
+    /**
+     * 推广员绑定时间
+     */
+    private LocalDateTime brokerageBindTime;
+    /**
+     * 推广资格
+     */
+    private Boolean brokerageEnabled;
+    /**
+     * 成为分销员时间
+     */
+    private LocalDateTime brokerageTime;
+    /**
+     * 可用佣金
+     */
+    private Integer brokeragePrice;
+    /**
+     * 冻结佣金
+     */
+    private Integer frozenBrokeragePrice;
+
+}

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

@@ -0,0 +1,90 @@
+package cn.iocoder.yudao.module.trade.dal.dataobject.config;
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import cn.iocoder.yudao.framework.mybatis.core.type.IntegerListTypeHandler;
+import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageBindModeEnum;
+import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageEnabledConditionEnum;
+import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageWithdrawTypeEnum;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
+import lombok.*;
+
+import java.util.List;
+
+/**
+ * 交易中心配置 DO
+ *
+ * @author owen
+ */
+@TableName(value = "trade_config", autoResultMap = true)
+@KeySequence("trade_config_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class TradeConfigDO extends BaseDO {
+
+    /**
+     * 自增主键
+     */
+    @TableId
+    private Long id;
+
+    // ========== 分销相关 ==========
+
+    /**
+     * 是否启用分佣
+     */
+    private Boolean brokerageEnabled;
+    /**
+     * 分佣模式
+     * <p>
+     * 枚举 {@link BrokerageEnabledConditionEnum 对应的类}
+     */
+    private Integer brokerageEnabledCondition;
+    /**
+     * 分销关系绑定模式
+     * <p>
+     * 枚举 {@link BrokerageBindModeEnum 对应的类}
+     */
+    private Integer brokerageBindMode;
+    /**
+     * 分销海报图地址数组
+     */
+    @TableField(typeHandler = JacksonTypeHandler.class)
+    private List<String> brokeragePostUrls;
+    /**
+     * 一级返佣比例
+     */
+    private Integer brokerageFirstPercent;
+    /**
+     * 二级返佣比例
+     */
+    private Integer brokerageSecondPercent;
+    /**
+     * 用户提现最低金额
+     */
+    private Integer brokerageWithdrawMinPrice;
+    /**
+     * 提现银行
+     */
+    @TableField(typeHandler = IntegerListTypeHandler.class)
+    private List<Integer> brokerageBankNames;
+    /**
+     * 佣金冻结时间(天)
+     */
+    private Integer brokerageFrozenDays;
+    /**
+     * 提现方式
+     * <p>
+     * 枚举 {@link BrokerageWithdrawTypeEnum 对应的类}
+     */
+    @TableField(typeHandler = IntegerListTypeHandler.class)
+    private List<Integer> brokerageWithdrawType;
+
+}

+ 47 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/brokerage/record/TradeBrokerageRecordMapper.java

@@ -0,0 +1,47 @@
+package cn.iocoder.yudao.module.trade.dal.mysql.brokerage.record;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.module.trade.controller.admin.brokerage.record.vo.TradeBrokerageRecordPageReqVO;
+import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.record.TradeBrokerageRecordDO;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+/**
+ * 佣金记录 Mapper
+ *
+ * @author owen
+ */
+@Mapper
+public interface TradeBrokerageRecordMapper extends BaseMapperX<TradeBrokerageRecordDO> {
+
+    default PageResult<TradeBrokerageRecordDO> selectPage(TradeBrokerageRecordPageReqVO reqVO) {
+        return selectPage(reqVO, new LambdaQueryWrapperX<TradeBrokerageRecordDO>()
+                .eqIfPresent(TradeBrokerageRecordDO::getUserId, reqVO.getUserId())
+                .eqIfPresent(TradeBrokerageRecordDO::getBizType, reqVO.getBizType())
+                .eqIfPresent(TradeBrokerageRecordDO::getStatus, reqVO.getStatus())
+                .betweenIfPresent(TradeBrokerageRecordDO::getCreateTime, reqVO.getCreateTime())
+                .orderByDesc(TradeBrokerageRecordDO::getId));
+    }
+
+    default List<TradeBrokerageRecordDO> selectListByStatusAndUnfreezeTimeLt(Integer status, LocalDateTime unfreezeTime) {
+        return selectList(new LambdaQueryWrapper<TradeBrokerageRecordDO>()
+                .eq(TradeBrokerageRecordDO::getStatus, status)
+                .lt(TradeBrokerageRecordDO::getUnfreezeTime, unfreezeTime));
+    }
+
+    default int updateByIdAndStatus(Integer id, Integer status, TradeBrokerageRecordDO updateObj) {
+        return update(updateObj, new LambdaQueryWrapper<TradeBrokerageRecordDO>()
+                .eq(TradeBrokerageRecordDO::getId, id)
+                .eq(TradeBrokerageRecordDO::getStatus, status));
+    }
+
+    default TradeBrokerageRecordDO selectByUserIdAndBizTypeAndBizId(Integer bizType, String bizId) {
+        return selectOne(TradeBrokerageRecordDO::getBizType, bizType,
+                TradeBrokerageRecordDO::getBizId, bizId);
+    }
+}

+ 103 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/brokerage/user/TradeBrokerageUserMapper.java

@@ -0,0 +1,103 @@
+package cn.iocoder.yudao.module.trade.dal.mysql.brokerage.user;
+
+import cn.hutool.core.lang.Assert;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.module.trade.controller.admin.brokerage.user.vo.TradeBrokerageUserPageReqVO;
+import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.user.TradeBrokerageUserDO;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * 分销用户 Mapper
+ *
+ * @author owen
+ */
+@Mapper
+public interface TradeBrokerageUserMapper extends BaseMapperX<TradeBrokerageUserDO> {
+
+    default PageResult<TradeBrokerageUserDO> selectPage(TradeBrokerageUserPageReqVO reqVO) {
+        return selectPage(reqVO, new LambdaQueryWrapperX<TradeBrokerageUserDO>()
+                .eqIfPresent(TradeBrokerageUserDO::getBrokerageUserId, reqVO.getBrokerageUserId())
+                .eqIfPresent(TradeBrokerageUserDO::getBrokerageEnabled, reqVO.getBrokerageEnabled())
+                .betweenIfPresent(TradeBrokerageUserDO::getCreateTime, reqVO.getCreateTime())
+                .orderByDesc(TradeBrokerageUserDO::getId));
+    }
+
+    /**
+     * 更新用户可用佣金(增加)
+     *
+     * @param id        用户编号
+     * @param incrCount 增加佣金(正数)
+     */
+    default void updateBrokeragePriceIncr(Long id, int incrCount) {
+        Assert.isTrue(incrCount > 0);
+        LambdaUpdateWrapper<TradeBrokerageUserDO> lambdaUpdateWrapper = new LambdaUpdateWrapper<TradeBrokerageUserDO>()
+                .setSql(" brokerage_price = brokerage_price + " + incrCount)
+                .eq(TradeBrokerageUserDO::getId, id);
+        update(null, lambdaUpdateWrapper);
+    }
+
+    /**
+     * 更新用户可用佣金(减少)
+     * 注意:理论上佣金可能已经提现,这时会扣出负数,确保平台不会造成损失
+     *
+     * @param id        用户编号
+     * @param incrCount 增加佣金(负数)
+     */
+    default void updateBrokeragePriceDecr(Long id, int incrCount) {
+        Assert.isTrue(incrCount < 0);
+        LambdaUpdateWrapper<TradeBrokerageUserDO> lambdaUpdateWrapper = new LambdaUpdateWrapper<TradeBrokerageUserDO>()
+                .setSql(" brokerage_price = brokerage_price + " + incrCount) // 负数,所以使用 + 号
+                .eq(TradeBrokerageUserDO::getId, id);
+        update(null, lambdaUpdateWrapper);
+    }
+
+    /**
+     * 更新用户冻结佣金(增加)
+     *
+     * @param id        用户编号
+     * @param incrCount 增加冻结佣金(正数)
+     */
+    default void updateFrozenBrokeragePriceIncr(Long id, int incrCount) {
+        Assert.isTrue(incrCount > 0);
+        LambdaUpdateWrapper<TradeBrokerageUserDO> lambdaUpdateWrapper = new LambdaUpdateWrapper<TradeBrokerageUserDO>()
+                .setSql(" frozen_brokerage_price = frozen_brokerage_price + " + incrCount)
+                .eq(TradeBrokerageUserDO::getId, id);
+        update(null, lambdaUpdateWrapper);
+    }
+
+    /**
+     * 更新用户冻结佣金(减少)
+     * 注意:理论上冻结佣金可能已经解冻,这时会扣出负数,确保平台不会造成损失
+     *
+     * @param id        用户编号
+     * @param incrCount 减少冻结佣金(负数)
+     */
+    default void updateFrozenBrokeragePriceDecr(Long id, int incrCount) {
+        Assert.isTrue(incrCount < 0);
+        LambdaUpdateWrapper<TradeBrokerageUserDO> lambdaUpdateWrapper = new LambdaUpdateWrapper<TradeBrokerageUserDO>()
+                .setSql(" frozen_brokerage_price = frozen_brokerage_price + " + incrCount) // 负数,所以使用 + 号
+                .eq(TradeBrokerageUserDO::getId, id);
+        update(null, lambdaUpdateWrapper);
+    }
+
+    /**
+     * 更新用户冻结佣金(减少), 更新用户佣金(增加)
+     *
+     * @param id        用户编号
+     * @param incrCount 减少冻结佣金(负数)
+     * @return 更新条数
+     */
+    default int updateFrozenBrokeragePriceDecrAndBrokeragePriceIncr(Long id, int incrCount) {
+        Assert.isTrue(incrCount < 0);
+        LambdaUpdateWrapper<TradeBrokerageUserDO> lambdaUpdateWrapper = new LambdaUpdateWrapper<TradeBrokerageUserDO>()
+                .setSql(" frozen_brokerage_price = frozen_brokerage_price + " + incrCount + // 负数,所以使用 + 号
+                        ", brokerage_price = brokerage_price + " + -incrCount) // 负数,所以使用 - 号
+                .eq(TradeBrokerageUserDO::getId, id)
+                .ge(TradeBrokerageUserDO::getFrozenBrokeragePrice, -incrCount); // cas 逻辑
+        return update(null, lambdaUpdateWrapper);
+    }
+
+}

+ 15 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/config/TradeConfigMapper.java

@@ -0,0 +1,15 @@
+package cn.iocoder.yudao.module.trade.dal.mysql.config;
+
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.module.trade.dal.dataobject.config.TradeConfigDO;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * 交易中心配置 Mapper
+ *
+ * @author owen
+ */
+@Mapper
+public interface TradeConfigMapper extends BaseMapperX<TradeConfigDO> {
+
+}

+ 29 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/job/brokerage/TradeBrokerageRecordUnfreezeJob.java

@@ -0,0 +1,29 @@
+package cn.iocoder.yudao.module.trade.job.brokerage;
+
+import cn.hutool.core.util.StrUtil;
+import cn.iocoder.yudao.framework.quartz.core.handler.JobHandler;
+import cn.iocoder.yudao.framework.tenant.core.job.TenantJob;
+import cn.iocoder.yudao.module.trade.service.brokerage.record.TradeBrokerageRecordService;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+
+/**
+ * 佣金解冻 Job
+ *
+ * @author owen
+ */
+@Component
+@TenantJob
+public class TradeBrokerageRecordUnfreezeJob implements JobHandler {
+
+    @Resource
+    private TradeBrokerageRecordService tradeBrokerageRecordService;
+
+    @Override
+    public String execute(String param) {
+        int count = tradeBrokerageRecordService.unfreezeRecord();
+        return StrUtil.format("解冻佣金 {} 个", count);
+    }
+
+}

+ 54 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/record/TradeBrokerageRecordService.java

@@ -0,0 +1,54 @@
+package cn.iocoder.yudao.module.trade.service.brokerage.record;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.record.TradeBrokerageRecordDO;
+import cn.iocoder.yudao.module.trade.service.brokerage.record.bo.BrokerageAddReqBO;
+import cn.iocoder.yudao.module.trade.controller.admin.brokerage.record.vo.TradeBrokerageRecordPageReqVO;
+
+import java.util.List;
+
+/**
+ * 佣金记录 Service 接口
+ *
+ * @author owen
+ */
+public interface TradeBrokerageRecordService {
+
+    /**
+     * 获得佣金记录
+     *
+     * @param id 编号
+     * @return 佣金记录
+     */
+    TradeBrokerageRecordDO getBrokerageRecord(Integer id);
+
+    /**
+     * 获得佣金记录分页
+     *
+     * @param pageReqVO 分页查询
+     * @return 佣金记录分页
+     */
+    PageResult<TradeBrokerageRecordDO> getBrokerageRecordPage(TradeBrokerageRecordPageReqVO pageReqVO);
+
+    /**
+     * 增加佣金
+     *
+     * @param userId 会员编号
+     * @param list   请求参数列表
+     */
+    void addBrokerage(Long userId, List<BrokerageAddReqBO> list);
+
+    /**
+     * 取消佣金:将佣金记录,状态修改为已失效
+     * @param userId 会员编号
+     * @param bizId 业务编号
+     */
+    void cancelBrokerage(Long userId, String bizId);
+
+    /**
+     * 解冻佣金:将待结算的佣金记录,状态修改为已结算
+     *
+     * @return 解冻佣金的数量
+     */
+    int unfreezeRecord();
+}

+ 232 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/record/TradeBrokerageRecordServiceImpl.java

@@ -0,0 +1,232 @@
+package cn.iocoder.yudao.module.trade.service.brokerage.record;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.BooleanUtil;
+import cn.hutool.core.util.NumberUtil;
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.extra.spring.SpringUtil;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.trade.convert.brokerage.record.TradeBrokerageRecordConvert;
+import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.record.TradeBrokerageRecordDO;
+import cn.iocoder.yudao.module.trade.service.brokerage.record.bo.BrokerageAddReqBO;
+import cn.iocoder.yudao.module.trade.controller.admin.brokerage.record.vo.TradeBrokerageRecordPageReqVO;
+import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.user.TradeBrokerageUserDO;
+import cn.iocoder.yudao.module.trade.dal.dataobject.config.TradeConfigDO;
+import cn.iocoder.yudao.module.trade.dal.mysql.brokerage.record.TradeBrokerageRecordMapper;
+import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageRecordBizTypeEnum;
+import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageRecordStatusEnum;
+import cn.iocoder.yudao.module.trade.service.brokerage.user.TradeBrokerageUserService;
+import cn.iocoder.yudao.module.trade.service.config.TradeConfigService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.validation.annotation.Validated;
+
+import javax.annotation.Resource;
+import java.math.RoundingMode;
+import java.time.LocalDateTime;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Function;
+
+/**
+ * 佣金记录 Service 实现类
+ *
+ * @author owen
+ */
+@Slf4j
+@Service
+@Validated
+public class TradeBrokerageRecordServiceImpl implements TradeBrokerageRecordService {
+
+    @Resource
+    private TradeBrokerageRecordMapper tradeBrokerageRecordMapper;
+    @Resource
+    private TradeConfigService tradeConfigService;
+    @Resource
+    private TradeBrokerageUserService tradeBrokerageUserService;
+
+    @Override
+    public TradeBrokerageRecordDO getBrokerageRecord(Integer id) {
+        return tradeBrokerageRecordMapper.selectById(id);
+    }
+
+    @Override
+    public PageResult<TradeBrokerageRecordDO> getBrokerageRecordPage(TradeBrokerageRecordPageReqVO pageReqVO) {
+        return tradeBrokerageRecordMapper.selectPage(pageReqVO);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void addBrokerage(Long buyerId, List<BrokerageAddReqBO> list) {
+        TradeConfigDO memberConfig = tradeConfigService.getTradeConfig();
+        // 0 未启用分销功能
+        if (memberConfig == null || !BooleanUtil.isTrue(memberConfig.getBrokerageEnabled())) {
+            log.warn("[addBrokerage][增加佣金失败:brokerageEnabled 未配置,buyerId({})", buyerId);
+            return;
+        }
+
+        // 1.1 获得一级推广人
+        TradeBrokerageUserDO firstUser = tradeBrokerageUserService.getInviteBrokerageUser(buyerId);
+        if (firstUser == null || !BooleanUtil.isTrue(firstUser.getBrokerageEnabled())) {
+            return;
+        }
+
+        // 1.2 计算一级分佣
+        addBrokerage(firstUser, list, memberConfig.getBrokerageFrozenDays(), memberConfig.getBrokerageFirstPercent(), BrokerageAddReqBO::getSkuFirstBrokeragePrice);
+
+
+        // 2.1 获得二级推广员
+        TradeBrokerageUserDO secondUser = tradeBrokerageUserService.getBrokerageUser(firstUser.getBrokerageUserId());
+        if (secondUser == null || !BooleanUtil.isTrue(secondUser.getBrokerageEnabled())) {
+            return;
+        }
+
+        // 2.2 计算二级分佣
+        addBrokerage(secondUser, list, memberConfig.getBrokerageFrozenDays(), memberConfig.getBrokerageSecondPercent(), BrokerageAddReqBO::getSkuSecondBrokeragePrice);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void cancelBrokerage(Long userId, String bizId) {
+        TradeBrokerageRecordDO record = tradeBrokerageRecordMapper.selectByUserIdAndBizTypeAndBizId(BrokerageRecordBizTypeEnum.ORDER.getType(), bizId);
+        if (record == null || ObjectUtil.notEqual(record.getUserId(), userId)) {
+            log.error("[cancelBrokerage][userId({})][bizId({}) 更新为已失效失败:记录不存在]", userId, bizId);
+            return;
+        }
+
+        TradeBrokerageRecordDO updateObj = new TradeBrokerageRecordDO().setStatus(BrokerageRecordStatusEnum.CANCEL.getStatus());
+        int updateRows = tradeBrokerageRecordMapper.updateByIdAndStatus(record.getId(), record.getStatus(), updateObj);
+        if (updateRows == 0) {
+            log.error("[cancelBrokerage][record({}) 更新为已失效失败]", record.getId());
+            return;
+        }
+
+        if (BrokerageRecordStatusEnum.WAIT_SETTLEMENT.getStatus().equals(record.getStatus())) {
+            tradeBrokerageUserService.updateUserFrozenBrokeragePrice(userId, -record.getPrice());
+        } else if (BrokerageRecordStatusEnum.SETTLEMENT.getStatus().equals(record.getStatus())) {
+            tradeBrokerageUserService.updateUserBrokeragePrice(userId, -record.getPrice());
+        }
+    }
+
+    /**
+     * 计算佣金
+     *
+     * @param payPrice          订单支付金额
+     * @param percent           商品 SKU 设置的佣金
+     * @param skuBrokeragePrice 商品的佣金
+     * @return 佣金
+     */
+    int calculateBrokerage(Integer payPrice, Integer percent, Integer skuBrokeragePrice) {
+        // 1. 优先使用商品 SKU 设置的佣金
+        if (skuBrokeragePrice != null && skuBrokeragePrice > 0) {
+            return ObjectUtil.defaultIfNull(skuBrokeragePrice, 0);
+        }
+
+        // 2. 根据订单支付金额计算佣金
+        if (payPrice != null && payPrice > 0 && percent != null && percent > 0) {
+            return NumberUtil.div(NumberUtil.mul(payPrice, percent), 100, 0, RoundingMode.DOWN).intValue();
+        }
+
+        return 0;
+    }
+
+    /**
+     * 增加用户佣金
+     *
+     * @param user                 用户
+     * @param list                 佣金增加参数列表
+     * @param brokerageFrozenDays  冻结天数
+     * @param brokeragePercent     佣金比例
+     * @param skuBrokeragePriceFun 商品 SKU 设置的佣金
+     */
+    private void addBrokerage(TradeBrokerageUserDO user, List<BrokerageAddReqBO> list, Integer brokerageFrozenDays,
+                              Integer brokeragePercent, Function<BrokerageAddReqBO, Integer> skuBrokeragePriceFun) {
+        // 处理冻结时间
+        brokerageFrozenDays = ObjectUtil.defaultIfNull(brokerageFrozenDays, 0);
+        LocalDateTime unfreezeTime = null;
+        if (brokerageFrozenDays > 0) {
+            unfreezeTime = LocalDateTime.now().plusDays(brokerageFrozenDays);
+        }
+
+        // 计算分佣
+        int totalBrokerage = 0;
+        List<TradeBrokerageRecordDO> records = new ArrayList<>();
+        for (BrokerageAddReqBO dto : list) {
+            int brokeragePerItem = calculateBrokerage(dto.getPayPrice(), brokeragePercent, skuBrokeragePriceFun.apply(dto));
+            if (brokeragePerItem > 0) {
+                int brokerage = brokeragePerItem * dto.getCount();
+                records.add(TradeBrokerageRecordConvert.INSTANCE.convert(user, dto.getBizId(), brokerageFrozenDays, brokerage, unfreezeTime));
+                totalBrokerage += brokerage;
+            }
+        }
+
+        if (records.isEmpty()) {
+            return;
+        }
+
+        // 保存佣金记录
+        tradeBrokerageRecordMapper.insertBatch(records);
+
+        if (brokerageFrozenDays > 0) {
+            // 更新用户冻结佣金
+            tradeBrokerageUserService.updateUserFrozenBrokeragePrice(user.getId(), totalBrokerage);
+        } else {
+            // 更新用户可用佣金
+            tradeBrokerageUserService.updateUserBrokeragePrice(user.getId(), totalBrokerage);
+        }
+    }
+
+    @Override
+    public int unfreezeRecord() {
+        // 1. 查询待结算的佣金记录
+        List<TradeBrokerageRecordDO> records = tradeBrokerageRecordMapper.selectListByStatusAndUnfreezeTimeLt(
+                BrokerageRecordStatusEnum.WAIT_SETTLEMENT.getStatus(), LocalDateTime.now());
+        if (CollUtil.isEmpty(records)) {
+            return 0;
+        }
+
+        // 2. 遍历执行
+        int count = 0;
+        for (TradeBrokerageRecordDO record : records) {
+            try {
+                boolean successful = getSelf().unfreezeRecord(record);
+                if (successful) {
+                    count++;
+                }
+            } catch (Exception e) {
+                log.error("[unfreezeRecord][record({}) 更新为已结算失败]", record.getId(), e);
+            }
+        }
+        return count;
+    }
+
+    @Transactional(rollbackFor = Exception.class)
+    public boolean unfreezeRecord(TradeBrokerageRecordDO record) {
+        // 更新记录状态
+        TradeBrokerageRecordDO updateObj = new TradeBrokerageRecordDO()
+                .setStatus(BrokerageRecordStatusEnum.SETTLEMENT.getStatus())
+                .setUnfreezeTime(LocalDateTime.now());
+        int updateRows = tradeBrokerageRecordMapper.updateByIdAndStatus(record.getId(), record.getStatus(), updateObj);
+        if (updateRows == 0) {
+            log.error("[unfreezeRecord][record({}) 更新为已结算失败]", record.getId());
+            return false;
+        }
+
+        // 更新用户冻结佣金
+        tradeBrokerageUserService.updateFrozenBrokeragePriceDecrAndBrokeragePriceIncr(record.getUserId(), -record.getPrice());
+
+        log.info("[unfreezeRecord][record({}) 更新为已结算成功]", record.getId());
+        return true;
+    }
+
+    /**
+     * 获得自身的代理对象,解决 AOP 生效问题
+     *
+     * @return 自己
+     */
+    private TradeBrokerageRecordServiceImpl getSelf() {
+        return SpringUtil.getBean(getClass());
+    }
+
+}

+ 3 - 3
yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/api/brokerage/dto/BrokerageAddReqDTO.java → yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/record/bo/BrokerageAddReqBO.java

@@ -1,17 +1,17 @@
-package cn.iocoder.yudao.module.member.api.brokerage.dto;
+package cn.iocoder.yudao.module.trade.service.brokerage.record.bo;
 
 import lombok.AllArgsConstructor;
 import lombok.Data;
 import lombok.NoArgsConstructor;
 
 /**
- * 佣金 增加 Request DTO
+ * 佣金 增加 Request BO
  * @author owen
  */
 @Data
 @NoArgsConstructor
 @AllArgsConstructor
-public class BrokerageAddReqDTO {
+public class BrokerageAddReqBO {
     /**
      * 业务ID
      */

+ 88 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/user/TradeBrokerageUserService.java

@@ -0,0 +1,88 @@
+package cn.iocoder.yudao.module.trade.service.brokerage.user;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.trade.controller.admin.brokerage.user.vo.TradeBrokerageUserPageReqVO;
+import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.user.TradeBrokerageUserDO;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * 分销用户 Service 接口
+ *
+ * @author owen
+ */
+public interface TradeBrokerageUserService {
+
+    /**
+     * 获得分销用户
+     *
+     * @param id 编号
+     * @return 分销用户
+     */
+    TradeBrokerageUserDO getBrokerageUser(Long id);
+
+    /**
+     * 获得分销用户列表
+     *
+     * @param ids 编号
+     * @return 分销用户列表
+     */
+    List<TradeBrokerageUserDO> getBrokerageUserList(Collection<Long> ids);
+
+    /**
+     * 获得分销用户分页
+     *
+     * @param pageReqVO 分页查询
+     * @return 分销用户分页
+     */
+    PageResult<TradeBrokerageUserDO> getBrokerageUserPage(TradeBrokerageUserPageReqVO pageReqVO);
+
+    /**
+     * 修改推广员编号
+     *
+     * @param id              用户编号
+     * @param brokerageUserId 推广员编号
+     */
+    void updateBrokerageUserId(Long id, Long brokerageUserId);
+
+    /**
+     * 修改推广资格
+     *
+     * @param id               用户编号
+     * @param brokerageEnabled 推广资格
+     */
+    void updateBrokerageEnabled(Long id, Boolean brokerageEnabled);
+
+    /**
+     * 获得用户的推广人
+     *
+     * @param id 用户编号
+     * @return 用户的推广人
+     */
+    TradeBrokerageUserDO getInviteBrokerageUser(Long id);
+
+    /**
+     * 更新用户佣金
+     *
+     * @param id             用户编号
+     * @param brokeragePrice 用户可用佣金
+     */
+    void updateUserBrokeragePrice(Long id, int brokeragePrice);
+
+    /**
+     * 更新用户冻结佣金
+     *
+     * @param id                   用户编号
+     * @param frozenBrokeragePrice 用户冻结佣金
+     */
+    void updateUserFrozenBrokeragePrice(Long id, int frozenBrokeragePrice);
+
+    /**
+     * 更新用户冻结佣金(减少), 更新用户佣金(增加)
+     *
+     * @param id                   用户编号
+     * @param frozenBrokeragePrice 减少冻结佣金(负数)
+     */
+    void updateFrozenBrokeragePriceDecrAndBrokeragePriceIncr(Long id, int frozenBrokeragePrice);
+}

+ 106 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/user/TradeBrokerageUserServiceImpl.java

@@ -0,0 +1,106 @@
+package cn.iocoder.yudao.module.trade.service.brokerage.user;
+
+import cn.hutool.core.lang.Assert;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.trade.controller.admin.brokerage.user.vo.TradeBrokerageUserPageReqVO;
+import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.user.TradeBrokerageUserDO;
+import cn.iocoder.yudao.module.trade.dal.mysql.brokerage.user.TradeBrokerageUserMapper;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.validation.annotation.Validated;
+
+import javax.annotation.Resource;
+import java.util.Collection;
+import java.util.List;
+import java.util.Optional;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.module.member.enums.ErrorCodeConstants.MEMBER_FROZEN_BROKERAGE_PRICE_NOT_ENOUGH;
+import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.BROKERAGE_USER_NOT_EXISTS;
+
+/**
+ * 分销用户 Service 实现类
+ *
+ * @author owen
+ */
+@Service
+@Validated
+public class TradeBrokerageUserServiceImpl implements TradeBrokerageUserService {
+
+    @Resource
+    private TradeBrokerageUserMapper brokerageUserMapper;
+
+    private void validateBrokerageUserExists(Long id) {
+        if (brokerageUserMapper.selectById(id) == null) {
+            throw exception(BROKERAGE_USER_NOT_EXISTS);
+        }
+    }
+
+    @Override
+    public TradeBrokerageUserDO getBrokerageUser(Long id) {
+        return brokerageUserMapper.selectById(id);
+    }
+
+    @Override
+    public List<TradeBrokerageUserDO> getBrokerageUserList(Collection<Long> ids) {
+        return brokerageUserMapper.selectBatchIds(ids);
+    }
+
+    @Override
+    public PageResult<TradeBrokerageUserDO> getBrokerageUserPage(TradeBrokerageUserPageReqVO pageReqVO) {
+        return brokerageUserMapper.selectPage(pageReqVO);
+    }
+
+    @Override
+    public void updateBrokerageUserId(Long id, Long brokerageUserId) {
+        // 校验存在
+        validateBrokerageUserExists(id);
+
+    }
+
+    @Override
+    public void updateBrokerageEnabled(Long id, Boolean brokerageEnabled) {
+        // 校验存在
+        validateBrokerageUserExists(id);
+    }
+
+    @Override
+    public TradeBrokerageUserDO getInviteBrokerageUser(Long id) {
+        return Optional.ofNullable(id)
+                .map(this::getBrokerageUser)
+                .map(TradeBrokerageUserDO::getBrokerageUserId)
+                .map(this::getBrokerageUser)
+                .orElse(null);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void updateUserBrokeragePrice(Long id, int brokeragePrice) {
+        if (brokeragePrice > 0) {
+            brokerageUserMapper.updateBrokeragePriceIncr(id, brokeragePrice);
+        } else if (brokeragePrice < 0) {
+            brokerageUserMapper.updateBrokeragePriceDecr(id, brokeragePrice);
+        }
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void updateUserFrozenBrokeragePrice(Long id, int frozenBrokeragePrice) {
+        if (frozenBrokeragePrice > 0) {
+            brokerageUserMapper.updateFrozenBrokeragePriceIncr(id, frozenBrokeragePrice);
+        } else if (frozenBrokeragePrice < 0) {
+            brokerageUserMapper.updateFrozenBrokeragePriceDecr(id, frozenBrokeragePrice);
+        }
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void updateFrozenBrokeragePriceDecrAndBrokeragePriceIncr(Long id, int frozenBrokeragePrice) {
+        Assert.isTrue(frozenBrokeragePrice < 0);
+        int updateRows = brokerageUserMapper.updateFrozenBrokeragePriceDecrAndBrokeragePriceIncr(id, frozenBrokeragePrice);
+        if (updateRows == 0) {
+            throw exception(MEMBER_FROZEN_BROKERAGE_PRICE_NOT_ENOUGH);
+        }
+    }
+
+}

+ 30 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/config/TradeConfigService.java

@@ -0,0 +1,30 @@
+package cn.iocoder.yudao.module.trade.service.config;
+
+import cn.iocoder.yudao.module.trade.controller.admin.config.vo.TradeConfigSaveReqVO;
+import cn.iocoder.yudao.module.trade.dal.dataobject.config.TradeConfigDO;
+
+import javax.validation.Valid;
+
+/**
+ * 交易中心配置 Service 接口
+ *
+ * @author owen
+ */
+public interface TradeConfigService {
+
+
+    /**
+     * 更新交易中心配置
+     *
+     * @param updateReqVO 更新信息
+     */
+    void saveTradeConfig(@Valid TradeConfigSaveReqVO updateReqVO);
+
+    /**
+     * 获得交易中心配置
+     *
+     * @return 交易中心配置
+     */
+    TradeConfigDO getTradeConfig();
+
+}

+ 44 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/config/TradeConfigServiceImpl.java

@@ -0,0 +1,44 @@
+package cn.iocoder.yudao.module.trade.service.config;
+
+import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
+import cn.iocoder.yudao.module.trade.controller.admin.config.vo.TradeConfigSaveReqVO;
+import cn.iocoder.yudao.module.trade.convert.config.TradeConfigConvert;
+import cn.iocoder.yudao.module.trade.dal.dataobject.config.TradeConfigDO;
+import cn.iocoder.yudao.module.trade.dal.mysql.config.TradeConfigMapper;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+import javax.annotation.Resource;
+import java.util.List;
+
+/**
+ * 交易中心配置 Service 实现类
+ *
+ * @author owen
+ */
+@Service
+@Validated
+public class TradeConfigServiceImpl implements TradeConfigService {
+
+    @Resource
+    private TradeConfigMapper tradeConfigMapper;
+
+    @Override
+    public void saveTradeConfig(TradeConfigSaveReqVO saveReqVO) {
+        // 存在,则进行更新
+        TradeConfigDO dbConfig = getTradeConfig();
+        if (dbConfig != null) {
+            tradeConfigMapper.updateById(TradeConfigConvert.INSTANCE.convert(saveReqVO).setId(dbConfig.getId()));
+            return;
+        }
+        // 不存在,则进行插入
+        tradeConfigMapper.insert(TradeConfigConvert.INSTANCE.convert(saveReqVO));
+    }
+
+    @Override
+    public TradeConfigDO getTradeConfig() {
+        List<TradeConfigDO> list = tradeConfigMapper.selectList();
+        return CollectionUtils.getFirst(list);
+    }
+
+}

+ 12 - 7
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java

@@ -12,8 +12,8 @@ import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
 import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
 import cn.iocoder.yudao.module.member.api.address.AddressApi;
 import cn.iocoder.yudao.module.member.api.address.dto.AddressRespDTO;
-import cn.iocoder.yudao.module.member.api.brokerage.BrokerageApi;
-import cn.iocoder.yudao.module.member.api.brokerage.dto.BrokerageAddReqDTO;
+import cn.iocoder.yudao.module.trade.service.brokerage.record.TradeBrokerageRecordService;
+import cn.iocoder.yudao.module.trade.service.brokerage.record.bo.BrokerageAddReqBO;
 import cn.iocoder.yudao.module.member.api.level.MemberLevelApi;
 import cn.iocoder.yudao.module.member.api.point.MemberPointApi;
 import cn.iocoder.yudao.module.member.api.user.MemberUserApi;
@@ -120,7 +120,7 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
     @Resource
     private MemberPointApi memberPointApi;
     @Resource
-    private BrokerageApi brokerageApi;
+    private TradeBrokerageRecordService tradeBrokerageRecordService;
     @Resource
     private ProductCommentApi productCommentApi;
 
@@ -635,12 +635,12 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
                     .setRefundStatus(TradeOrderRefundStatusEnum.PART.getStatus()).setRefundPrice(orderRefundPrice));
         }
 
-        // TODO 芋艿:未来如果有分佣,需要更新相关分佣订单为已失效
-
         // 扣减用户积分
         getSelf().reduceUserPointAsync(order.getUserId(), orderRefundPrice, afterSaleId);
         // 扣减用户经验
         getSelf().reduceUserExperienceAsync(order.getUserId(), orderRefundPrice, afterSaleId);
+        // 更新分佣记录为已失效
+        getSelf().cancelBrokerageAsync(order.getUserId(), id);
     }
 
     @Override
@@ -752,9 +752,14 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
     @Async
     protected void addBrokerageAsync(Long userId, Long orderId) {
         List<TradeOrderItemDO> orderItems = tradeOrderItemMapper.selectListByOrderId(orderId);
-        List<BrokerageAddReqDTO> list = convertList(orderItems,
+        List<BrokerageAddReqBO> list = convertList(orderItems,
                 item -> TradeOrderConvert.INSTANCE.convert(item, productSkuApi.getSku(item.getSkuId())));
-        brokerageApi.addBrokerage(userId, list);
+        tradeBrokerageRecordService.addBrokerage(userId, list);
+    }
+
+    @Async
+    protected void cancelBrokerageAsync(Long userId, Long orderItemId) {
+        tradeBrokerageRecordService.cancelBrokerage(userId, String.valueOf(orderItemId));
     }
 
     /**

+ 26 - 26
yudao-module-member/yudao-module-member-biz/src/test/java/cn/iocoder/yudao/module/member/service/brokerage/record/MemberBrokerageRecordServiceImplTest.java → yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/brokerage/record/TradeBrokerageRecordServiceImplTest.java

@@ -1,13 +1,13 @@
-package cn.iocoder.yudao.module.member.service.brokerage.record;
+package cn.iocoder.yudao.module.trade.service.brokerage.record;
 
 import cn.hutool.core.util.NumberUtil;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
-import cn.iocoder.yudao.module.member.controller.admin.brokerage.record.vo.MemberBrokerageRecordPageReqVO;
-import cn.iocoder.yudao.module.member.dal.dataobject.brokerage.record.MemberBrokerageRecordDO;
-import cn.iocoder.yudao.module.member.dal.mysql.brokerage.record.MemberBrokerageRecordMapper;
-import cn.iocoder.yudao.module.member.service.point.MemberPointConfigService;
-import cn.iocoder.yudao.module.member.service.user.MemberUserService;
+import cn.iocoder.yudao.module.trade.controller.admin.brokerage.record.vo.TradeBrokerageRecordPageReqVO;
+import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.record.TradeBrokerageRecordDO;
+import cn.iocoder.yudao.module.trade.dal.mysql.brokerage.record.TradeBrokerageRecordMapper;
+import cn.iocoder.yudao.module.trade.service.brokerage.user.TradeBrokerageUserService;
+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;
@@ -26,55 +26,55 @@ import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 
 /**
- * {@link MemberBrokerageRecordServiceImpl} 的单元测试类
+ * {@link TradeBrokerageRecordServiceImpl} 的单元测试类
  *
  * @author owen
  */
-@Import(MemberBrokerageRecordServiceImpl.class)
-public class MemberBrokerageRecordServiceImplTest extends BaseDbUnitTest {
+@Import(TradeBrokerageRecordServiceImpl.class)
+public class TradeBrokerageRecordServiceImplTest extends BaseDbUnitTest {
 
     @Resource
-    private MemberBrokerageRecordServiceImpl memberBrokerageRecordService;
+    private TradeBrokerageRecordServiceImpl tradeBrokerageRecordService;
     @Resource
-    private MemberBrokerageRecordMapper memberBrokerageRecordMapper;
+    private TradeBrokerageRecordMapper tradeBrokerageRecordMapper;
 
     @MockBean
-    private MemberPointConfigService memberPointConfigService;
+    private TradeConfigService tradeConfigService;
     @MockBean
-    private MemberUserService memberUserService;
+    private TradeBrokerageUserService tradeBrokerageUserService;
 
     @Test
     @Disabled  // TODO 请修改 null 为需要的值,然后删除 @Disabled 注解
-    public void testGetMemberBrokerageRecordPage() {
+    public void testGetBrokerageRecordPage() {
         // mock 数据
-        MemberBrokerageRecordDO dbMemberBrokerageRecord = randomPojo(MemberBrokerageRecordDO.class, o -> { // 等会查询到
+        TradeBrokerageRecordDO dbBrokerageRecord = randomPojo(TradeBrokerageRecordDO.class, o -> { // 等会查询到
             o.setUserId(null);
             o.setBizType(null);
             o.setStatus(null);
             o.setCreateTime(null);
         });
-        memberBrokerageRecordMapper.insert(dbMemberBrokerageRecord);
+        tradeBrokerageRecordMapper.insert(dbBrokerageRecord);
         // 测试 userId 不匹配
-        memberBrokerageRecordMapper.insert(cloneIgnoreId(dbMemberBrokerageRecord, o -> o.setUserId(null)));
+        tradeBrokerageRecordMapper.insert(cloneIgnoreId(dbBrokerageRecord, o -> o.setUserId(null)));
         // 测试 bizType 不匹配
-        memberBrokerageRecordMapper.insert(cloneIgnoreId(dbMemberBrokerageRecord, o -> o.setBizType(null)));
+        tradeBrokerageRecordMapper.insert(cloneIgnoreId(dbBrokerageRecord, o -> o.setBizType(null)));
         // 测试 status 不匹配
-        memberBrokerageRecordMapper.insert(cloneIgnoreId(dbMemberBrokerageRecord, o -> o.setStatus(null)));
+        tradeBrokerageRecordMapper.insert(cloneIgnoreId(dbBrokerageRecord, o -> o.setStatus(null)));
         // 测试 createTime 不匹配
-        memberBrokerageRecordMapper.insert(cloneIgnoreId(dbMemberBrokerageRecord, o -> o.setCreateTime(null)));
+        tradeBrokerageRecordMapper.insert(cloneIgnoreId(dbBrokerageRecord, o -> o.setCreateTime(null)));
         // 准备参数
-        MemberBrokerageRecordPageReqVO reqVO = new MemberBrokerageRecordPageReqVO();
+        TradeBrokerageRecordPageReqVO reqVO = new TradeBrokerageRecordPageReqVO();
         reqVO.setUserId(null);
         reqVO.setBizType(null);
         reqVO.setStatus(null);
         reqVO.setCreateTime(buildBetweenTime(2023, 2, 1, 2023, 2, 28));
 
         // 调用
-        PageResult<MemberBrokerageRecordDO> pageResult = memberBrokerageRecordService.getMemberBrokerageRecordPage(reqVO);
+        PageResult<TradeBrokerageRecordDO> pageResult = tradeBrokerageRecordService.getBrokerageRecordPage(reqVO);
         // 断言
         assertEquals(1, pageResult.getTotal());
         assertEquals(1, pageResult.getList().size());
-        assertPojoEquals(dbMemberBrokerageRecord, pageResult.getList().get(0));
+        assertPojoEquals(dbBrokerageRecord, pageResult.getList().get(0));
     }
 
     @Test
@@ -84,7 +84,7 @@ public class MemberBrokerageRecordServiceImplTest extends BaseDbUnitTest {
         Integer percent = randomInt(1, 101);
         Integer skuBrokeragePrice = randomInt();
         // 调用
-        int brokerage = memberBrokerageRecordService.calculateBrokerage(payPrice, percent, skuBrokeragePrice);
+        int brokerage = tradeBrokerageRecordService.calculateBrokerage(payPrice, percent, skuBrokeragePrice);
         // 断言
         assertEquals(brokerage, skuBrokeragePrice);
     }
@@ -97,7 +97,7 @@ public class MemberBrokerageRecordServiceImplTest extends BaseDbUnitTest {
         Integer skuBrokeragePrice = randomEle(new Integer[]{0, null});
         System.out.println("skuBrokeragePrice=" + skuBrokeragePrice);
         // 调用
-        int brokerage = memberBrokerageRecordService.calculateBrokerage(payPrice, percent, skuBrokeragePrice);
+        int brokerage = tradeBrokerageRecordService.calculateBrokerage(payPrice, percent, skuBrokeragePrice);
         // 断言
         assertEquals(brokerage, NumberUtil.div(NumberUtil.mul(payPrice, percent), 100, 0, RoundingMode.DOWN).intValue());
     }
@@ -109,7 +109,7 @@ public class MemberBrokerageRecordServiceImplTest extends BaseDbUnitTest {
         Integer percent = null;
         Integer skuBrokeragePrice = null;
         // 调用
-        int brokerage = memberBrokerageRecordService.calculateBrokerage(payPrice, percent, skuBrokeragePrice);
+        int brokerage = tradeBrokerageRecordService.calculateBrokerage(payPrice, percent, skuBrokeragePrice);
         // 断言
         assertEquals(brokerage, 0);
     }

+ 64 - 0
yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/brokerage/user/TradeBrokerageUserServiceImplTest.java

@@ -0,0 +1,64 @@
+package cn.iocoder.yudao.module.trade.service.brokerage.user;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
+import cn.iocoder.yudao.module.trade.controller.admin.brokerage.user.vo.TradeBrokerageUserPageReqVO;
+import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.user.TradeBrokerageUserDO;
+import cn.iocoder.yudao.module.trade.dal.mysql.brokerage.user.TradeBrokerageUserMapper;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
+import org.springframework.context.annotation.Import;
+
+import javax.annotation.Resource;
+
+import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildBetweenTime;
+import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId;
+import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
+import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+/**
+ * {@link TradeBrokerageUserServiceImpl} 的单元测试类
+ *
+ * @author owen
+ */
+@Import(TradeBrokerageUserServiceImpl.class)
+public class TradeBrokerageUserServiceImplTest extends BaseDbUnitTest {
+
+    @Resource
+    private TradeBrokerageUserServiceImpl brokerageUserService;
+
+    @Resource
+    private TradeBrokerageUserMapper brokerageUserMapper;
+
+    @Test
+    @Disabled  // TODO 请修改 null 为需要的值,然后删除 @Disabled 注解
+    public void testGetBrokerageUserPage() {
+        // mock 数据
+        TradeBrokerageUserDO dbBrokerageUser = randomPojo(TradeBrokerageUserDO.class, o -> { // 等会查询到
+            o.setBrokerageUserId(null);
+            o.setBrokerageEnabled(null);
+            o.setCreateTime(null);
+        });
+        brokerageUserMapper.insert(dbBrokerageUser);
+        // 测试 brokerageUserId 不匹配
+        brokerageUserMapper.insert(cloneIgnoreId(dbBrokerageUser, o -> o.setBrokerageUserId(null)));
+        // 测试 brokerageEnabled 不匹配
+        brokerageUserMapper.insert(cloneIgnoreId(dbBrokerageUser, o -> o.setBrokerageEnabled(null)));
+        // 测试 createTime 不匹配
+        brokerageUserMapper.insert(cloneIgnoreId(dbBrokerageUser, o -> o.setCreateTime(null)));
+        // 准备参数
+        TradeBrokerageUserPageReqVO reqVO = new TradeBrokerageUserPageReqVO();
+        reqVO.setBrokerageUserId(null);
+        reqVO.setBrokerageEnabled(null);
+        reqVO.setCreateTime(buildBetweenTime(2023, 2, 1, 2023, 2, 28));
+
+        // 调用
+        PageResult<TradeBrokerageUserDO> pageResult = brokerageUserService.getBrokerageUserPage(reqVO);
+        // 断言
+        assertEquals(1, pageResult.getTotal());
+        assertEquals(1, pageResult.getList().size());
+        assertPojoEquals(dbBrokerageUser, pageResult.getList().get(0));
+    }
+
+}

+ 2 - 0
yudao-module-mall/yudao-module-trade-biz/src/test/resources/sql/clean.sql

@@ -2,3 +2,5 @@ DELETE FROM trade_order;
 DELETE FROM trade_order_item;
 DELETE FROM trade_after_sale;
 DELETE FROM trade_after_sale_log;
+DELETE FROM trade_brokerage_user;
+DELETE FROM trade_brokerage_record;

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

@@ -125,3 +125,42 @@ CREATE TABLE IF NOT EXISTS "trade_after_sale_log" (
     "deleted" bit NOT NULL DEFAULT FALSE,
     PRIMARY KEY ("id")
 ) COMMENT '交易售后日志';
+
+CREATE TABLE IF NOT EXISTS "trade_brokerage_user"
+(
+    "id"                     bigint   NOT NULL GENERATED BY DEFAULT AS IDENTITY,
+    "brokerage_user_id"      bigint   NOT NULL,
+    "brokerage_bind_time"    varchar,
+    "brokerage_enabled"      bit      NOT NULL,
+    "brokerage_time"         varchar,
+    "brokerage_price"        int      NOT NULL,
+    "frozen_brokerage_price" int      NOT NULL,
+    "creator"                varchar           DEFAULT '',
+    "create_time"            datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
+    "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',
+    PRIMARY KEY ("id")
+) COMMENT '分销用户';
+CREATE TABLE IF NOT EXISTS "trade_brokerage_record"
+(
+    "id"            int      NOT NULL GENERATED BY DEFAULT AS IDENTITY,
+    "user_id"       bigint   NOT NULL,
+    "biz_id"        varchar  NOT NULL,
+    "biz_type"      varchar  NOT NULL,
+    "title"         varchar  NOT NULL,
+    "price"         int      NOT NULL,
+    "total_price"   int      NOT NULL,
+    "description"   varchar  NOT NULL,
+    "status"        varchar  NOT NULL,
+    "frozen_days"   int      NOT NULL,
+    "unfreeze_time" varchar,
+    "creator"       varchar           DEFAULT '',
+    "create_time"   datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
+    "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',
+    PRIMARY KEY ("id")
+) COMMENT '佣金记录';

+ 0 - 30
yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/api/brokerage/BrokerageApi.java

@@ -1,30 +0,0 @@
-package cn.iocoder.yudao.module.member.api.brokerage;
-
-import cn.iocoder.yudao.module.member.api.brokerage.dto.BrokerageAddReqDTO;
-
-import java.util.List;
-
-/**
- * 佣金 API 接口
- *
- * @author owen
- */
-public interface BrokerageApi {
-
-    /**
-     * 增加佣金
-     *
-     * @param userId 会员ID
-     * @param list   请求参数列表
-     */
-    void addBrokerage(Long userId, List<BrokerageAddReqDTO> list);
-
-    /**
-     * 取消佣金
-     *
-     * @param userId 会员ID
-     * @param bizId  业务编号
-     */
-    void cancelBrokerage(Long userId, String bizId);
-
-}

+ 1 - 0
yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/enums/ErrorCodeConstants.java

@@ -13,6 +13,7 @@ public interface ErrorCodeConstants {
     ErrorCode USER_NOT_EXISTS = new ErrorCode(1004001000, "用户不存在");
     ErrorCode USER_MOBILE_NOT_EXISTS = new ErrorCode(1004001001, "手机号未注册用户");
     ErrorCode USER_MOBILE_USED = new ErrorCode(1004001002, "修改手机失败,该手机号({})已经被使用");
+    ErrorCode MEMBER_FROZEN_BROKERAGE_PRICE_NOT_ENOUGH = new ErrorCode(1004001003, "用户冻结佣金({})数量不足");
 
     // ========== AUTH 模块 1004003000 ==========
     ErrorCode AUTH_LOGIN_BAD_CREDENTIALS = new ErrorCode(1004003000, "登录失败,账号密码不正确");

+ 0 - 32
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/api/brokerage/BrokerageApiImpl.java

@@ -1,32 +0,0 @@
-package cn.iocoder.yudao.module.member.api.brokerage;
-
-import cn.iocoder.yudao.module.member.api.brokerage.dto.BrokerageAddReqDTO;
-import cn.iocoder.yudao.module.member.service.brokerage.record.MemberBrokerageRecordService;
-import org.springframework.stereotype.Service;
-import org.springframework.validation.annotation.Validated;
-
-import javax.annotation.Resource;
-import java.util.List;
-
-/**
- * 佣金 API 实现类
- *
- * @author owen
- */
-@Service
-@Validated
-public class BrokerageApiImpl implements BrokerageApi {
-
-    @Resource
-    private MemberBrokerageRecordService memberBrokerageRecordService;
-
-    @Override
-    public void addBrokerage(Long userId, List<BrokerageAddReqDTO> list) {
-        memberBrokerageRecordService.addBrokerage(userId, list);
-    }
-
-    @Override
-    public void cancelBrokerage(Long userId, String bizId) {
-
-    }
-}

+ 0 - 51
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/brokerage/record/MemberBrokerageRecordController.java

@@ -1,51 +0,0 @@
-package cn.iocoder.yudao.module.member.controller.admin.brokerage.record;
-
-import cn.iocoder.yudao.framework.common.pojo.CommonResult;
-import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.module.member.controller.admin.brokerage.record.vo.MemberBrokerageRecordPageReqVO;
-import cn.iocoder.yudao.module.member.controller.admin.brokerage.record.vo.MemberBrokerageRecordRespVO;
-import cn.iocoder.yudao.module.member.convert.brokerage.record.MemberBrokerageRecordConvert;
-import cn.iocoder.yudao.module.member.dal.dataobject.brokerage.record.MemberBrokerageRecordDO;
-import cn.iocoder.yudao.module.member.service.brokerage.record.MemberBrokerageRecordService;
-import io.swagger.v3.oas.annotations.Operation;
-import io.swagger.v3.oas.annotations.Parameter;
-import io.swagger.v3.oas.annotations.tags.Tag;
-import org.springframework.security.access.prepost.PreAuthorize;
-import org.springframework.validation.annotation.Validated;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RequestParam;
-import org.springframework.web.bind.annotation.RestController;
-
-import javax.annotation.Resource;
-import javax.validation.Valid;
-
-import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
-
-@Tag(name = "管理后台 - 佣金记录")
-@RestController
-@RequestMapping("/member/member-brokerage-record")
-@Validated
-public class MemberBrokerageRecordController {
-
-    @Resource
-    private MemberBrokerageRecordService memberBrokerageRecordService;
-
-    @GetMapping("/get")
-    @Operation(summary = "获得佣金记录")
-    @Parameter(name = "id", description = "编号", required = true, example = "1024")
-    @PreAuthorize("@ss.hasPermission('member:member-brokerage-record:query')")
-    public CommonResult<MemberBrokerageRecordRespVO> getMemberBrokerageRecord(@RequestParam("id") Integer id) {
-        MemberBrokerageRecordDO memberBrokerageRecord = memberBrokerageRecordService.getMemberBrokerageRecord(id);
-        return success(MemberBrokerageRecordConvert.INSTANCE.convert(memberBrokerageRecord));
-    }
-
-    @GetMapping("/page")
-    @Operation(summary = "获得佣金记录分页")
-    @PreAuthorize("@ss.hasPermission('member:member-brokerage-record:query')")
-    public CommonResult<PageResult<MemberBrokerageRecordRespVO>> getMemberBrokerageRecordPage(@Valid MemberBrokerageRecordPageReqVO pageVO) {
-        PageResult<MemberBrokerageRecordDO> pageResult = memberBrokerageRecordService.getMemberBrokerageRecordPage(pageVO);
-        return success(MemberBrokerageRecordConvert.INSTANCE.convertPage(pageResult));
-    }
-
-}

+ 0 - 57
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/point/vo/config/MemberPointConfigBaseVO.java

@@ -1,17 +1,9 @@
 package cn.iocoder.yudao.module.member.controller.admin.point.vo.config;
 
-import cn.iocoder.yudao.framework.common.validation.InEnum;
-import cn.iocoder.yudao.module.member.enums.brokerage.BrokerageBindModeEnum;
-import cn.iocoder.yudao.module.member.enums.brokerage.BrokerageEnabledConditionEnum;
-import cn.iocoder.yudao.module.member.enums.brokerage.BrokerageWithdrawTypeEnum;
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
-import org.hibernate.validator.constraints.Range;
 
-import javax.validation.constraints.NotEmpty;
 import javax.validation.constraints.NotNull;
-import javax.validation.constraints.PositiveOrZero;
-import java.util.List;
 
 /**
  * 会员积分配置 Base VO,提供给添加、修改、详细的子 VO 使用
@@ -36,53 +28,4 @@ public class MemberPointConfigBaseVO {
     @NotNull(message = "1 元赠送积分不能为空")
     private Integer tradeGivePoint;
 
-    // ========== 分销相关 ==========
-
-    @Schema(description = "是否启用分佣", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
-    @NotNull(message = "是否启用分佣不能为空")
-    private Boolean brokerageEnabled;
-
-    @Schema(description = "分佣模式", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
-    @NotNull(message = "分佣模式不能为空")
-    @InEnum(value = BrokerageEnabledConditionEnum.class, message = "分佣模式必须是 {value}")
-    private Integer brokerageEnabledCondition;
-
-    @Schema(description = "分销关系绑定模式", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
-    @NotNull(message = "分销关系绑定模式不能为空")
-    @InEnum(value = BrokerageBindModeEnum.class, message = "分销关系绑定模式必须是 {value}")
-    private Integer brokerageBindMode;
-
-    @Schema(description = "分销海报图地址数组", requiredMode = Schema.RequiredMode.REQUIRED, example = "[https://www.iocoder.cn/yudao.jpg]")
-    private List<String> brokeragePostUrls;
-
-    @Schema(description = "一级返佣比例", requiredMode = Schema.RequiredMode.REQUIRED, example = "5")
-    @NotNull(message = "一级返佣比例不能为空")
-    @Range(min = 0, max = 100, message = "一级返佣比例必须在 0 - 100 之间")
-    private Integer brokerageFirstPercent;
-
-    @Schema(description = "二级返佣比例", requiredMode = Schema.RequiredMode.REQUIRED, example = "5")
-    @NotNull(message = "二级返佣比例不能为空")
-    @Range(min = 0, max = 100, message = "二级返佣比例必须在 0 - 100 之间")
-    private Integer brokerageSecondPercent;
-
-    @Schema(description = "用户提现最低金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "1000")
-    @NotNull(message = "用户提现最低金额不能为空")
-    @PositiveOrZero(message = "用户提现最低金额不能是负数")
-    private Integer brokerageWithdrawMinPrice;
-
-    @Schema(description = "提现银行", requiredMode = Schema.RequiredMode.REQUIRED, example = "[0, 1]")
-    @NotEmpty(message = "提现银行不能为空")
-    private List<Integer> brokerageBankNames;
-
-    @Schema(description = "佣金冻结时间(天)", requiredMode = Schema.RequiredMode.REQUIRED, example = "7")
-    @NotNull(message = "佣金冻结时间(天)不能为空")
-    @PositiveOrZero(message = "佣金冻结时间不能是负数")
-    private Integer brokerageFrozenDays;
-
-    @Schema(description = "提现方式", requiredMode = Schema.RequiredMode.REQUIRED, example = "[0, 1]")
-    @NotNull(message = "提现方式不能为空")
-    @InEnum(value = BrokerageWithdrawTypeEnum.class, message = "提现方式必须是 {value}")
-    private List<Integer> brokerageWithdrawType;
-
-
 }

+ 0 - 60
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/dataobject/point/MemberPointConfigDO.java

@@ -1,19 +1,11 @@
 package cn.iocoder.yudao.module.member.dal.dataobject.point;
 
 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
-import cn.iocoder.yudao.framework.mybatis.core.type.IntegerListTypeHandler;
-import cn.iocoder.yudao.module.member.enums.brokerage.BrokerageBindModeEnum;
-import cn.iocoder.yudao.module.member.enums.brokerage.BrokerageEnabledConditionEnum;
-import cn.iocoder.yudao.module.member.enums.brokerage.BrokerageWithdrawTypeEnum;
 import com.baomidou.mybatisplus.annotation.KeySequence;
-import com.baomidou.mybatisplus.annotation.TableField;
 import com.baomidou.mybatisplus.annotation.TableId;
 import com.baomidou.mybatisplus.annotation.TableName;
-import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
 import lombok.*;
 
-import java.util.List;
-
 /**
  * 会员积分配置 DO
  *
@@ -53,56 +45,4 @@ public class MemberPointConfigDO extends BaseDO {
      */
     private Integer tradeGivePoint;
 
-    // ========== 分销相关 ==========
-
-    /**
-     * 是否启用分佣
-     */
-    private Boolean brokerageEnabled;
-    /**
-     * 分佣模式
-     *
-     * 枚举 {@link BrokerageEnabledConditionEnum 对应的类}
-     */
-    private Integer brokerageEnabledCondition;
-    /**
-     * 分销关系绑定模式
-     *
-     * 枚举 {@link BrokerageBindModeEnum 对应的类}
-     */
-    private Integer brokerageBindMode;
-    /**
-     * 分销海报图地址数组
-     */
-    @TableField(typeHandler = JacksonTypeHandler.class)
-    private List<String> brokeragePostUrls;
-    /**
-     * 一级返佣比例
-     */
-    private Integer brokerageFirstPercent;
-    /**
-     * 二级返佣比例
-     */
-    private Integer brokerageSecondPercent;
-    /**
-     * 用户提现最低金额
-     */
-    private Integer brokerageWithdrawMinPrice;
-    /**
-     * 提现银行
-     */
-    @TableField(typeHandler = IntegerListTypeHandler.class)
-    private List<Integer> brokerageBankNames;
-    /**
-     * 佣金冻结时间(天)
-     */
-    private Integer brokerageFrozenDays;
-    /**
-     * 提现方式
-     *
-     * 枚举 {@link BrokerageWithdrawTypeEnum 对应的类}
-     */
-    @TableField(typeHandler = IntegerListTypeHandler.class)
-    private List<Integer> brokerageWithdrawType;
-
 }

+ 0 - 27
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/mysql/brokerage/record/MemberBrokerageRecordMapper.java

@@ -1,27 +0,0 @@
-package cn.iocoder.yudao.module.member.dal.mysql.brokerage.record;
-
-import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
-import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
-import cn.iocoder.yudao.module.member.controller.admin.brokerage.record.vo.MemberBrokerageRecordPageReqVO;
-import cn.iocoder.yudao.module.member.dal.dataobject.brokerage.record.MemberBrokerageRecordDO;
-import org.apache.ibatis.annotations.Mapper;
-
-/**
- * 佣金记录 Mapper
- *
- * @author owen
- */
-@Mapper
-public interface MemberBrokerageRecordMapper extends BaseMapperX<MemberBrokerageRecordDO> {
-
-    default PageResult<MemberBrokerageRecordDO> selectPage(MemberBrokerageRecordPageReqVO reqVO) {
-        return selectPage(reqVO, new LambdaQueryWrapperX<MemberBrokerageRecordDO>()
-                .eqIfPresent(MemberBrokerageRecordDO::getUserId, reqVO.getUserId())
-                .eqIfPresent(MemberBrokerageRecordDO::getBizType, reqVO.getBizType())
-                .eqIfPresent(MemberBrokerageRecordDO::getStatus, reqVO.getStatus())
-                .betweenIfPresent(MemberBrokerageRecordDO::getCreateTime, reqVO.getCreateTime())
-                .orderByDesc(MemberBrokerageRecordDO::getId));
-    }
-
-}

+ 0 - 31
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/mysql/user/MemberUserMapper.java

@@ -1,14 +1,12 @@
 package cn.iocoder.yudao.module.member.dal.mysql.user;
 
 import cn.hutool.core.collection.CollUtil;
-import cn.hutool.core.lang.Assert;
 import cn.hutool.core.util.StrUtil;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
 import cn.iocoder.yudao.module.member.controller.admin.user.vo.MemberUserPageReqVO;
 import cn.iocoder.yudao.module.member.dal.dataobject.user.MemberUserDO;
-import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
 import org.apache.ibatis.annotations.Mapper;
 
 import java.util.List;
@@ -63,33 +61,4 @@ public interface MemberUserMapper extends BaseMapperX<MemberUserDO> {
         return selectCount(new LambdaQueryWrapperX<MemberUserDO>()
                 .apply("FIND_IN_SET({0}, tag_ids)", tagId));
     }
-
-    /**
-     * 更新用户可用佣金(增加)
-     *
-     * @param id        用户编号
-     * @param incrCount 增加佣金(正数)
-     */
-    default void updateBrokeragePriceIncr(Long id, int incrCount) {
-        Assert.isTrue(incrCount > 0);
-        LambdaUpdateWrapper<MemberUserDO> lambdaUpdateWrapper = new LambdaUpdateWrapper<MemberUserDO>()
-                .setSql(" brokerage_price = brokerage_price + " + incrCount)
-                .eq(MemberUserDO::getId, id);
-        update(null, lambdaUpdateWrapper);
-    }
-
-    /**
-     * 更新用户冻结佣金(增加)
-     *
-     * @param id        用户编号
-     * @param incrCount 增加冻结佣金(正数)
-     */
-    default void updateFrozenBrokeragePriceIncr(Long id, int incrCount) {
-        Assert.isTrue(incrCount > 0);
-        LambdaUpdateWrapper<MemberUserDO> lambdaUpdateWrapper = new LambdaUpdateWrapper<MemberUserDO>()
-                .setSql(" frozen_brokerage_price = frozen_brokerage_price + " + incrCount)
-                .eq(MemberUserDO::getId, id);
-        update(null, lambdaUpdateWrapper);
-    }
-
 }

+ 0 - 40
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/brokerage/record/MemberBrokerageRecordService.java

@@ -1,40 +0,0 @@
-package cn.iocoder.yudao.module.member.service.brokerage.record;
-
-import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.module.member.api.brokerage.dto.BrokerageAddReqDTO;
-import cn.iocoder.yudao.module.member.controller.admin.brokerage.record.vo.MemberBrokerageRecordPageReqVO;
-import cn.iocoder.yudao.module.member.dal.dataobject.brokerage.record.MemberBrokerageRecordDO;
-
-import java.util.List;
-
-/**
- * 佣金记录 Service 接口
- *
- * @author owen
- */
-public interface MemberBrokerageRecordService {
-
-    /**
-     * 获得佣金记录
-     *
-     * @param id 编号
-     * @return 佣金记录
-     */
-    MemberBrokerageRecordDO getMemberBrokerageRecord(Integer id);
-
-    /**
-     * 获得佣金记录分页
-     *
-     * @param pageReqVO 分页查询
-     * @return 佣金记录分页
-     */
-    PageResult<MemberBrokerageRecordDO> getMemberBrokerageRecordPage(MemberBrokerageRecordPageReqVO pageReqVO);
-
-    /**
-     * 增加佣金
-     *
-     * @param userId 会员ID
-     * @param list   请求参数列表
-     */
-    void addBrokerage(Long userId, List<BrokerageAddReqDTO> list);
-}

+ 0 - 151
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/brokerage/record/MemberBrokerageRecordServiceImpl.java

@@ -1,151 +0,0 @@
-package cn.iocoder.yudao.module.member.service.brokerage.record;
-
-import cn.hutool.core.util.BooleanUtil;
-import cn.hutool.core.util.NumberUtil;
-import cn.hutool.core.util.ObjectUtil;
-import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.module.member.api.brokerage.dto.BrokerageAddReqDTO;
-import cn.iocoder.yudao.module.member.controller.admin.brokerage.record.vo.MemberBrokerageRecordPageReqVO;
-import cn.iocoder.yudao.module.member.convert.brokerage.record.MemberBrokerageRecordConvert;
-import cn.iocoder.yudao.module.member.dal.dataobject.brokerage.record.MemberBrokerageRecordDO;
-import cn.iocoder.yudao.module.member.dal.dataobject.point.MemberPointConfigDO;
-import cn.iocoder.yudao.module.member.dal.dataobject.user.MemberUserDO;
-import cn.iocoder.yudao.module.member.dal.mysql.brokerage.record.MemberBrokerageRecordMapper;
-import cn.iocoder.yudao.module.member.service.point.MemberPointConfigService;
-import cn.iocoder.yudao.module.member.service.user.MemberUserService;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.stereotype.Service;
-import org.springframework.validation.annotation.Validated;
-
-import javax.annotation.Resource;
-import java.math.RoundingMode;
-import java.time.LocalDateTime;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.function.Function;
-
-/**
- * 佣金记录 Service 实现类
- *
- * @author owen
- */
-@Slf4j
-@Service
-@Validated
-public class MemberBrokerageRecordServiceImpl implements MemberBrokerageRecordService {
-
-    @Resource
-    private MemberBrokerageRecordMapper memberBrokerageRecordMapper;
-    @Resource
-    private MemberPointConfigService memberConfigService;
-    @Resource
-    private MemberUserService memberUserService;
-
-    @Override
-    public MemberBrokerageRecordDO getMemberBrokerageRecord(Integer id) {
-        return memberBrokerageRecordMapper.selectById(id);
-    }
-
-    @Override
-    public PageResult<MemberBrokerageRecordDO> getMemberBrokerageRecordPage(MemberBrokerageRecordPageReqVO pageReqVO) {
-        return memberBrokerageRecordMapper.selectPage(pageReqVO);
-    }
-
-    @Override
-    public void addBrokerage(Long buyerId, List<BrokerageAddReqDTO> list) {
-        MemberPointConfigDO memberConfig = memberConfigService.getPointConfig();
-        // 0 未启用分销功能
-        if (memberConfig == null || !BooleanUtil.isTrue(memberConfig.getBrokerageEnabled())) {
-            log.warn("[addBrokerage][增加佣金失败:brokerageEnabled 未配置,buyerId({})", buyerId);
-            return;
-        }
-
-        // 1.1 获得一级推广人
-        MemberUserDO firstUser = memberUserService.getBrokerageUser(buyerId);
-        if (firstUser == null || !BooleanUtil.isTrue(firstUser.getBrokerageEnabled())) {
-            return;
-        }
-
-        // 1.2 计算一级分佣
-        addBrokerage(firstUser, list, memberConfig.getBrokerageFrozenDays(), memberConfig.getBrokerageFirstPercent(), BrokerageAddReqDTO::getSkuFirstBrokeragePrice);
-
-
-        // 2.1 获得二级推广员
-        MemberUserDO secondUser = memberUserService.getUser(firstUser.getBrokerageUserId());
-        if (secondUser == null || !BooleanUtil.isTrue(secondUser.getBrokerageEnabled())) {
-            return;
-        }
-
-        // 2.2 计算二级分佣
-        addBrokerage(secondUser, list, memberConfig.getBrokerageFrozenDays(), memberConfig.getBrokerageSecondPercent(), BrokerageAddReqDTO::getSkuSecondBrokeragePrice);
-    }
-
-    /**
-     * 计算佣金
-     *
-     * @param payPrice          订单支付金额
-     * @param percent           商品 SKU 设置的佣金
-     * @param skuBrokeragePrice 商品的佣金
-     * @return 佣金
-     */
-    int calculateBrokerage(Integer payPrice, Integer percent, Integer skuBrokeragePrice) {
-        // 1. 优先使用商品 SKU 设置的佣金
-        if (skuBrokeragePrice != null && skuBrokeragePrice > 0) {
-            return ObjectUtil.defaultIfNull(skuBrokeragePrice, 0);
-        }
-
-        // 2. 根据订单支付金额计算佣金
-        if (payPrice != null && payPrice > 0 && percent != null && percent > 0) {
-            return NumberUtil.div(NumberUtil.mul(payPrice, percent), 100, 0, RoundingMode.DOWN).intValue();
-        }
-
-        return 0;
-    }
-
-    /**
-     * 增加用户佣金
-     *
-     * @param user                 用户
-     * @param list                 佣金增加参数列表
-     * @param brokerageFrozenDays  冻结天数
-     * @param brokeragePercent     佣金比例
-     * @param skuBrokeragePriceFun 商品 SKU 设置的佣金
-     */
-    private void addBrokerage(MemberUserDO user, List<BrokerageAddReqDTO> list, Integer brokerageFrozenDays,
-                              Integer brokeragePercent, Function<BrokerageAddReqDTO, Integer> skuBrokeragePriceFun) {
-        // 处理冻结时间
-        brokerageFrozenDays = ObjectUtil.defaultIfNull(brokerageFrozenDays, 0);
-        LocalDateTime unfreezeTime = null;
-        if (brokerageFrozenDays > 0) {
-            unfreezeTime = LocalDateTime.now().plusDays(brokerageFrozenDays);
-        }
-
-        // 计算分佣
-        int totalBrokerage = 0;
-        List<MemberBrokerageRecordDO> records = new ArrayList<>();
-        for (BrokerageAddReqDTO dto : list) {
-            int brokeragePerItem = calculateBrokerage(dto.getPayPrice(), brokeragePercent, skuBrokeragePriceFun.apply(dto));
-            if (brokeragePerItem > 0) {
-                int brokerage = brokeragePerItem * dto.getCount();
-                records.add(MemberBrokerageRecordConvert.INSTANCE.convert(user, dto.getBizId(), brokerageFrozenDays, brokerage, unfreezeTime));
-                totalBrokerage += brokerage;
-            }
-        }
-
-        if (records.isEmpty()) {
-            return;
-        }
-
-        // 保存佣金记录
-        memberBrokerageRecordMapper.insertBatch(records);
-
-        if (brokerageFrozenDays > 0) {
-            // 更新用户冻结佣金
-            memberUserService.updateUserFrozenBrokeragePrice(user.getId(), totalBrokerage);
-        } else {
-            // 更新用户可用佣金
-            memberUserService.updateUserBrokeragePrice(user.getId(), totalBrokerage);
-        }
-    }
-
-}

+ 0 - 24
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/user/MemberUserService.java

@@ -166,28 +166,4 @@ public interface MemberUserService {
      * @param point  积分数量
      */
     void updateUserPoint(Long userId, Integer point);
-
-    /**
-     * 获得用户的推广人
-     *
-     * @param id 用户编号
-     * @return 用户的推广人
-     */
-    MemberUserDO getBrokerageUser(Long id);
-
-    /**
-     * 增加用户佣金
-     *
-     * @param id             用户编号
-     * @param brokeragePrice 用户可用佣金
-     */
-    void updateUserBrokeragePrice(Long id, int brokeragePrice);
-
-    /**
-     * 增加用户佣金
-     *
-     * @param id                   用户编号
-     * @param frozenBrokeragePrice 用户冻结佣金
-     */
-    void updateUserFrozenBrokeragePrice(Long id, int frozenBrokeragePrice);
 }

+ 0 - 22
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/user/MemberUserServiceImpl.java

@@ -30,7 +30,6 @@ import javax.validation.Valid;
 import java.time.LocalDateTime;
 import java.util.Collection;
 import java.util.List;
-import java.util.Optional;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
 import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getClientIP;
@@ -260,25 +259,4 @@ public class MemberUserServiceImpl implements MemberUserService {
         memberUserMapper.updateById(new MemberUserDO().setId(userId).setPoint(point));
     }
 
-    @Override
-    public MemberUserDO getBrokerageUser(Long id) {
-        return Optional.ofNullable(id)
-                .map(this::getUser)
-                .map(MemberUserDO::getBrokerageUserId)
-                .map(this::getUser)
-                .orElse(null);
-    }
-
-    @Override
-    @Transactional(rollbackFor = Exception.class)
-    public void updateUserBrokeragePrice(Long id, int brokeragePrice) {
-        memberUserMapper.updateBrokeragePriceIncr(id, brokeragePrice);
-    }
-
-    @Override
-    @Transactional(rollbackFor = Exception.class)
-    public void updateUserFrozenBrokeragePrice(Long id, int frozenBrokeragePrice) {
-        memberUserMapper.updateFrozenBrokeragePriceIncr(id, frozenBrokeragePrice);
-    }
-
 }

+ 1 - 2
yudao-module-member/yudao-module-member-biz/src/test/resources/sql/clean.sql

@@ -2,5 +2,4 @@ DELETE FROM "member_user";
 DELETE FROM "member_address";
 DELETE FROM "member_tag";
 DELETE FROM "member_level";
-DELETE FROM "member_group";
-DELETE FROM "member_brokerage_record";
+DELETE FROM "member_group";

+ 0 - 1
yudao-module-member/yudao-module-member-biz/src/test/resources/sql/create_tables.sql

@@ -90,7 +90,6 @@ CREATE TABLE IF NOT EXISTS "member_group"
     "tenant_id"      bigint   not null default '0',
     PRIMARY KEY ("id")
 ) COMMENT '用户分组';
-
 CREATE TABLE IF NOT EXISTS "member_brokerage_record"
 (
     "id"            int      NOT NULL GENERATED BY DEFAULT AS IDENTITY,

+ 1 - 0
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/PayOrderController.java

@@ -70,6 +70,7 @@ public class PayOrderController {
     @PostMapping("/submit")
     @Operation(summary = "提交支付订单")
     public CommonResult<PayOrderSubmitRespVO> submitPayOrder(@RequestBody PayOrderSubmitReqVO reqVO) {
+        // 钱包支付需要 额外传 user_id 和 user_type
         if (Objects.equals(reqVO.getChannelCode(), PayChannelEnum.WALLET.getCode())) {
             Map<String, String> channelExtras = reqVO.getChannelExtras() == null ? new HashMap<>(8) : reqVO.getChannelExtras();
             channelExtras.put("user_id", String.valueOf(WebFrameworkUtils.getLoginUserId()));

+ 4 - 0
yudao-ui-admin/src/utils/constants.js

@@ -163,6 +163,10 @@ export const PayChannelEnum = {
   MOCK : {
     "code": "mock",
     "name": "模拟支付"
+  },
+  WALLET : {
+    "code": "wallet",
+    "name": "钱包支付"
   }
 }
 

+ 27 - 26
yudao-ui-admin/src/views/pay/app/components/mockChannelForm.vue → yudao-ui-admin/src/views/pay/app/components/noneConfigChannelForm.vue

@@ -5,7 +5,8 @@
         <el-form-item label-width="180px" label="渠道状态" prop="status">
           <el-radio-group v-model="formData.status" size="medium">
             <el-radio v-for="dict in this.getDictDatas(DICT_TYPE.COMMON_STATUS)" :key="parseInt(dict.value)"
-                      :label="parseInt(dict.value)">
+                      :label="parseInt(dict.value)"
+            >
               {{ dict.label }}
             </el-radio>
           </el-radio-group>
@@ -22,16 +23,16 @@
   </div>
 </template>
 <script>
-import { createChannel, getChannel, updateChannel } from "@/api/pay/channel";
-import { CommonStatusEnum } from "@/utils/constants";
+import { createChannel, getChannel, updateChannel } from '@/api/pay/channel'
+import { CommonStatusEnum } from '@/utils/constants'
 
 export default {
-  name: "mockChannelForm",
+  name: 'noneConfigChannelForm',
   data() {
     return {
       dialogVisible: false,
       formLoading: false,
-      title:'',
+      title: '',
       formData: {
         appId: '',
         code: '',
@@ -39,54 +40,54 @@ export default {
         feeRate: 0,
         remark: '',
         config: {
-          name: 'mock-conf'
+          name: 'none-config'
         }
       },
       rules: {
-        status: [{ required: true,  message: '渠道状态不能为空',  trigger: 'blur' }]
+        status: [{ required: true, message: '渠道状态不能为空', trigger: 'blur' }]
       }
     }
   },
   methods: {
     open(appId, code) {
-      this.dialogVisible = true;
-      this.formLoading = true;
-      this.reset(appId, code);
+      this.dialogVisible = true
+      this.formLoading = true
+      this.reset(appId, code)
       getChannel(appId, code).then(response => {
         if (response.data && response.data.id) {
-          this.formData = response.data;
-          this.formData.config = JSON.parse(response.data.config);
+          this.formData = response.data
+          this.formData.config = JSON.parse(response.data.config)
         }
         this.title = !this.formData.id ? '创建支付渠道' : '编辑支付渠道'
       }).finally(() => {
-        this.formLoading = false;
-      });
+        this.formLoading = false
+      })
     },
     close() {
-      this.dialogVisible = false;
-      this.reset(undefined, undefined);
+      this.dialogVisible = false
+      this.reset(undefined, undefined)
     },
     submitForm() {
       this.$refs['form'].validate(valid => {
         if (!valid) {
           return
         }
-        const data = { ...this.formData };
-        data.config = JSON.stringify(this.formData.config);
+        const data = { ...this.formData }
+        data.config = JSON.stringify(this.formData.config)
         if (!data.id) {
           createChannel(data).then(response => {
-            this.$modal.msgSuccess("新增成功");
+            this.$modal.msgSuccess('新增成功')
             this.$emit('success')
-            this.close();
-          });
+            this.close()
+          })
         } else {
           updateChannel(data).then(response => {
-            this.$modal.msgSuccess("修改成功");
+            this.$modal.msgSuccess('修改成功')
             this.$emit('success')
-            this.close();
+            this.close()
           })
         }
-      });
+      })
     },
     /** 重置表单 */
     reset(appId, code) {
@@ -97,11 +98,11 @@ export default {
         remark: '',
         feeRate: 0,
         config: {
-          name: 'mock-conf'
+          name: 'none-config'
         }
       }
       this.resetForm('form')
-    },
+    }
   }
 }
 </script>

+ 21 - 4
yudao-ui-admin/src/views/pay/app/index.vue

@@ -170,6 +170,19 @@
           </template>
         </el-table-column>
       </el-table-column>
+      <el-table-column label="钱包支付配置" align="center">
+        <el-table-column :label="payChannelEnum.WALLET.name" align="center">
+          <template v-slot="scope">
+            <el-button type="success" icon="el-icon-check" circle
+                       v-if="isChannelExists(scope.row.channelCodes, payChannelEnum.WALLET.code)"
+                       @click="handleChannel(scope.row, payChannelEnum.WALLET.code)">
+            </el-button>
+            <el-button v-else type="danger" icon="el-icon-close" circle
+                       @click="handleChannel(scope.row, payChannelEnum.WALLET.code)">
+            </el-button>
+          </template>
+        </el-table-column>
+      </el-table-column>
       <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
         <template v-slot="scope">
           <el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)"
@@ -219,7 +232,7 @@
     <!-- 对话框(支付应用的配置) -->
     <weixin-channel-form ref="weixinChannelFormRef" @success="getList" />
     <alipay-channel-form ref="alipayChannelFormRef" @success="getList" />
-    <mock-channel-form ref="mockChannelFormRef" @success="getList" />
+    <none-config-channel-form ref="noneConfigChannelFormRef" @success="getList" />
   </div>
 </template>
 
@@ -228,14 +241,14 @@ import { createApp, updateApp, changeAppStatus, deleteApp, getApp, getAppPage }
 import { PayChannelEnum, CommonStatusEnum } from "@/utils/constants";
 import weixinChannelForm from "@/views/pay/app/components/weixinChannelForm";
 import alipayChannelForm from "@/views/pay/app/components/alipayChannelForm";
-import mockChannelForm from '@/views/pay/app/components/mockChannelForm';
+import noneConfigChannelForm from '@/views/pay/app/components/noneConfigChannelForm';
 
 export default {
   name: "PayApp",
   components: {
     weixinChannelForm,
     alipayChannelForm,
-    mockChannelForm
+    noneConfigChannelForm
   },
   data() {
     return {
@@ -391,7 +404,11 @@ export default {
         return
       }
       if (code === 'mock') {
-        this.$refs['mockChannelFormRef'].open(row.id, code);
+        this.$refs['noneConfigChannelFormRef'].open(row.id, code);
+        return
+      }
+      if (code === 'wallet') {
+        this.$refs['noneConfigChannelFormRef'].open(row.id, code);
         return
       }
     },

+ 4 - 0
yudao-ui-admin/src/views/pay/cashier/index.vue

@@ -136,6 +136,10 @@ export default {
         name: '模拟支付',
         icon: require("@/assets/images/pay/icon/mock.svg"),
         code: "mock"
+      }, {
+          name: '钱包支付',
+          icon: require("@/assets/images/pay/icon/mock.svg"),
+          code: "wallet"
       }],
       submitLoading: false, // 提交支付的 loading
       interval: undefined, // 定时任务,轮询是否完成支付