Forráskód Böngészése

Merge branch 'master-jdk17' of https://gitee.com/zhijiantianya/ruoyi-vue-pro

# Conflicts:
#	yudao-dependencies/pom.xml
#	yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/vo/withdraw/BrokerageWithdrawBaseVO.java
#	yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/brokerage/AppBrokerageWithdrawController.java
#	yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/framework/aftersale/core/aop/AfterSaleLogAspect.java
#	yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageWithdrawServiceImpl.java
#	yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/notify/dto/PayTransferNotifyReqDTO.java
#	yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/transfer/PayTransferApi.java
#	yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/transfer/dto/PayTransferCreateReqDTO.java
#	yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/api/refund/PayRefundApiImpl.java
#	yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/PayDemoOrderController.java
#	yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/PayDemoTransferController.java
#	yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/vo/transfer/PayDemoTransferCreateReqVO.java
#	yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/transfer/PayTransferController.java
#	yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/transfer/vo/PayTransferCreateReqVO.java
#	yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/order/AppPayOrderController.java
#	yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/demo/PayDemoTransferService.java
#	yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/demo/PayDemoTransferServiceImpl.java
#	yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/transfer/PayTransferService.java
#	yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/transfer/PayTransferServiceImpl.java
#	yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/transfer/PayTransferUnifiedReqDTO.java
YunaiV 6 hónapja
szülő
commit
efc535c051
100 módosított fájl, 1653 hozzáadás és 1210 törlés
  1. 55 62
      sql/mysql/ruoyi-vue-pro.sql
  2. 1 1
      yudao-dependencies/pom.xml
  3. 6 0
      yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/date/LocalDateTimeUtils.java
  4. 1 1
      yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3_vben5_antd/general/views/form.vue.vm
  5. 4 5
      yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3_vben5_antd/general/views/index.vue.vm
  6. 1 1
      yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3_vben5_antd/general/views/modules/form_sub_erp.vue.vm
  7. 1 1
      yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3_vben5_antd/general/views/modules/form_sub_normal.vue.vm
  8. 3 4
      yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3_vben5_antd/general/views/modules/list_sub_erp.vue.vm
  9. 2 3
      yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3_vben5_antd/schema/views/data.ts.vm
  10. 1 4
      yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3_vben5_antd/schema/views/form.vue.vm
  11. 3 6
      yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3_vben5_antd/schema/views/index.vue.vm
  12. 1 4
      yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3_vben5_antd/schema/views/modules/form_sub_erp.vue.vm
  13. 2 5
      yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3_vben5_antd/schema/views/modules/list_sub_erp.vue.vm
  14. 3 0
      yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/DictTypeConstants.java
  15. 15 4
      yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/ErrorCodeConstants.java
  16. 3 1
      yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/aftersale/AfterSaleOperateTypeEnum.java
  17. 0 1
      yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/brokerage/BrokerageWithdrawStatusEnum.java
  18. 5 3
      yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/brokerage/BrokerageWithdrawTypeEnum.java
  19. 17 4
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/aftersale/AfterSaleController.java
  20. 3 4
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/BrokerageWithdrawController.java
  21. 0 68
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/vo/withdraw/BrokerageWithdrawBaseVO.java
  22. 2 2
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/vo/withdraw/BrokerageWithdrawPageReqVO.java
  23. 51 7
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/vo/withdraw/BrokerageWithdrawRespVO.java
  24. 8 4
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/brokerage/AppBrokerageRecordController.java
  25. 42 5
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/brokerage/AppBrokerageWithdrawController.java
  26. 2 2
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/brokerage/vo/record/AppBrokerageRecordPageReqVO.java
  27. 3 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/brokerage/vo/record/AppBrokerageRecordRespVO.java
  28. 31 18
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/brokerage/vo/withdraw/AppBrokerageWithdrawCreateReqVO.java
  29. 9 9
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/brokerage/vo/withdraw/AppBrokerageWithdrawPageReqVO.java
  30. 17 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/brokerage/vo/withdraw/AppBrokerageWithdrawRespVO.java
  31. 0 6
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/brokerage/BrokerageRecordConvert.java
  32. 0 19
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/brokerage/BrokerageWithdrawConvert.java
  33. 40 8
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/brokerage/BrokerageWithdrawDO.java
  34. 5 5
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/brokerage/BrokerageWithdrawMapper.java
  35. 14 2
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/framework/aftersale/core/aop/AfterSaleLogAspect.java
  36. 5 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/framework/aftersale/core/utils/AfterSaleLogUtils.java
  37. 9 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/aftersale/AfterSaleService.java
  38. 81 37
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/aftersale/AfterSaleServiceImpl.java
  39. 126 65
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageWithdrawServiceImpl.java
  40. 8 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateService.java
  41. 18 2
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java
  42. 6 6
      yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageWithdrawServiceImplTest.java
  43. 6 1
      yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/notify/dto/PayRefundNotifyReqDTO.java
  44. 8 3
      yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/notify/dto/PayTransferNotifyReqDTO.java
  45. 15 0
      yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/refund/dto/PayRefundRespDTO.java
  46. 4 4
      yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/transfer/PayTransferApi.java
  47. 63 20
      yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/transfer/dto/PayTransferCreateReqDTO.java
  48. 28 0
      yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/transfer/dto/PayTransferCreateRespDTO.java
  49. 50 0
      yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/transfer/dto/PayTransferRespDTO.java
  50. 10 0
      yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/wallet/PayWalletApi.java
  51. 52 0
      yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/wallet/dto/PayWalletRespDTO.java
  52. 2 0
      yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/DictTypeConstants.java
  53. 14 10
      yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/ErrorCodeConstants.java
  54. 11 1
      yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/PayChannelEnum.java
  55. 42 0
      yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/demo/PayDemoWithdrawStatusEnum.java
  56. 39 0
      yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/demo/PayDemoWithdrawTypeEnum.java
  57. 22 14
      yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/transfer/PayTransferStatusEnum.java
  58. 0 44
      yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/transfer/PayTransferTypeEnum.java
  59. 1 1
      yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/wallet/PayWalletBizTypeEnum.java
  60. 6 5
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/api/refund/PayRefundApiImpl.java
  61. 16 2
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/api/transfer/PayTransferApiImpl.java
  62. 8 0
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/api/wallet/PayWalletApiImpl.java
  63. 9 8
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/PayDemoOrderController.java
  64. 0 52
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/PayDemoTransferController.java
  65. 50 0
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/PayDemoWithdrawController.http
  66. 63 0
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/PayDemoWithdrawController.java
  67. 0 67
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/vo/transfer/PayDemoTransferCreateReqVO.java
  68. 0 47
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/vo/transfer/PayDemoTransferRespVO.java
  69. 42 0
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/vo/withdraw/PayDemoWithdrawCreateReqVO.java
  70. 47 0
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/vo/withdraw/PayDemoWithdrawRespVO.java
  71. 16 4
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/notify/PayNotifyController.java
  72. 0 45
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/notify/vo/PayNotifyTaskBaseVO.java
  73. 1 13
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/notify/vo/PayNotifyTaskDetailRespVO.java
  74. 6 0
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/notify/vo/PayNotifyTaskPageReqVO.java
  75. 39 5
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/notify/vo/PayNotifyTaskRespVO.java
  76. 9 5
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/PayOrderController.java
  77. 1 7
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/refund/PayRefundController.java
  78. 56 17
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/transfer/PayTransferController.java
  79. 0 95
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/transfer/vo/PayTransferCreateReqVO.java
  80. 0 62
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/transfer/vo/PayTransferPageItemRespVO.java
  81. 4 4
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/transfer/vo/PayTransferPageReqVO.java
  82. 31 9
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/transfer/vo/PayTransferRespVO.java
  83. 4 3
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/PayWalletRechargeController.java
  84. 10 7
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/order/AppPayOrderController.java
  85. 36 0
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/transfer/AppPayTransferController.java
  86. 0 26
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/convert/demo/PayDemoOrderConvert.java
  87. 0 21
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/convert/demo/PayDemoTransferConvert.java
  88. 0 43
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/convert/notify/PayNotifyTaskConvert.java
  89. 0 3
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/convert/refund/PayRefundConvert.java
  90. 0 31
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/convert/transfer/PayTransferConvert.java
  91. 0 84
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/demo/PayDemoTransferDO.java
  92. 84 0
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/demo/PayDemoWithdrawDO.java
  93. 7 1
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/notify/PayNotifyTaskDO.java
  94. 12 24
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/transfer/PayTransferDO.java
  95. 0 17
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/demo/PayDemoTransferMapper.java
  96. 24 0
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/demo/PayDemoWithdrawMapper.java
  97. 2 0
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/notify/PayNotifyTaskMapper.java
  98. 20 8
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/transfer/PayTransferMapper.java
  99. 74 18
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/WalletPayClient.java
  100. 5 0
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/app/PayAppServiceImpl.java

+ 55 - 62
sql/mysql/ruoyi-vue-pro.sql

@@ -11,7 +11,7 @@
  Target Server Version : 80200 (8.2.0)
  File Encoding         : 65001
 
- Date: 23/04/2025 23:07:47
+ Date: 12/05/2025 09:09:45
 */
 
 SET NAMES utf8mb4;
@@ -91,7 +91,7 @@ CREATE TABLE `infra_api_error_log`  (
   `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
   `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
   PRIMARY KEY (`id`) USING BTREE
-) ENGINE = InnoDB AUTO_INCREMENT = 21559 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '系统异常日志';
+) ENGINE = InnoDB AUTO_INCREMENT = 22175 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '系统异常日志';
 
 -- ----------------------------
 -- Records of infra_api_error_log
@@ -193,7 +193,7 @@ CREATE TABLE `infra_config`  (
   `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
   `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
   PRIMARY KEY (`id`) USING BTREE
-) ENGINE = InnoDB AUTO_INCREMENT = 13 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '参数配置表';
+) ENGINE = InnoDB AUTO_INCREMENT = 14 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '参数配置表';
 
 -- ----------------------------
 -- Records of infra_config
@@ -206,6 +206,7 @@ INSERT INTO `infra_config` (`id`, `category`, `type`, `name`, `config_key`, `val
 INSERT INTO `infra_config` (`id`, `category`, `type`, `name`, `config_key`, `value`, `visible`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (10, 'url', 2, 'Swagger 接口文档的地址', 'url.swagger', '', b'1', '', '1', '2023-04-07 13:41:16', '1', '2023-04-07 14:59:00', b'0');
 INSERT INTO `infra_config` (`id`, `category`, `type`, `name`, `config_key`, `value`, `visible`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (11, 'ui', 2, '腾讯地图 key', 'tencent.lbs.key', 'TVDBZ-TDILD-4ON4B-PFDZA-RNLKH-VVF6E', b'1', '腾讯地图 key', '1', '2023-06-03 19:16:27', '1', '2023-06-03 19:16:27', b'0');
 INSERT INTO `infra_config` (`id`, `category`, `type`, `name`, `config_key`, `value`, `visible`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (12, 'test2', 2, 'test3', 'test4', 'test5', b'1', 'test6', '1', '2023-12-03 09:55:16', '1', '2025-04-06 21:00:09', b'0');
+INSERT INTO `infra_config` (`id`, `category`, `type`, `name`, `config_key`, `value`, `visible`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (13, '用户管理-账号初始密码', 2, '用户管理-注册开关', 'system.user.register-enabled', 'true', b'0', '', '1', '2025-04-26 17:23:41', '1', '2025-04-26 17:23:41', b'0');
 COMMIT;
 
 -- ----------------------------
@@ -250,7 +251,7 @@ CREATE TABLE `infra_file`  (
   `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
   `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
   PRIMARY KEY (`id`) USING BTREE
-) ENGINE = InnoDB AUTO_INCREMENT = 1700 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '文件表';
+) ENGINE = InnoDB AUTO_INCREMENT = 1898 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '文件表';
 
 -- ----------------------------
 -- Records of infra_file
@@ -275,19 +276,21 @@ CREATE TABLE `infra_file_config`  (
   `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
   `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
   PRIMARY KEY (`id`) USING BTREE
-) ENGINE = InnoDB AUTO_INCREMENT = 29 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '文件配置表';
+) ENGINE = InnoDB AUTO_INCREMENT = 31 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '文件配置表';
 
 -- ----------------------------
 -- Records of infra_file_config
 -- ----------------------------
 BEGIN;
-INSERT INTO `infra_file_config` (`id`, `name`, `storage`, `remark`, `master`, `config`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (4, '数据库(示例)', 1, '我是数据库', b'0', '{\"@class\":\"cn.iocoder.yudao.module.infra.framework.file.core.client.db.DBFileClientConfig\",\"domain\":\"http://127.0.0.1:48080\"}', '1', '2022-03-15 23:56:24', '1', '2025-04-07 09:15:02', b'0');
-INSERT INTO `infra_file_config` (`id`, `name`, `storage`, `remark`, `master`, `config`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (22, '七牛存储器(示例)', 20, '请换成你自己的密钥!!!', b'1', '{\"@class\":\"cn.iocoder.yudao.module.infra.framework.file.core.client.s3.S3FileClientConfig\",\"endpoint\":\"s3.cn-south-1.qiniucs.com\",\"domain\":\"http://test.yudao.iocoder.cn\",\"bucket\":\"ruoyi-vue-pro\",\"accessKey\":\"3TvrJ70gl2Gt6IBe7_IZT1F6i_k0iMuRtyEv4EyS\",\"accessSecret\":\"wd0tbVBYlp0S-ihA8Qg2hPLncoP83wyrIq24OZuY\"}', '1', '2024-01-13 22:11:12', '1', '2025-04-07 09:15:02', b'0');
-INSERT INTO `infra_file_config` (`id`, `name`, `storage`, `remark`, `master`, `config`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (24, '腾讯云存储(示例)', 20, '请换成你的密钥!!!', b'0', '{\"@class\":\"cn.iocoder.yudao.module.infra.framework.file.core.client.s3.S3FileClientConfig\",\"endpoint\":\"https://cos.ap-shanghai.myqcloud.com\",\"domain\":\"http://tengxun-oss.iocoder.cn\",\"bucket\":\"aoteman-1255880240\",\"accessKey\":\"AKIDAF6WSh1uiIjwqtrOsGSN3WryqTM6cTMt\",\"accessSecret\":\"X\"}', '1', '2024-11-09 16:03:22', '1', '2025-04-07 09:15:02', b'0');
-INSERT INTO `infra_file_config` (`id`, `name`, `storage`, `remark`, `master`, `config`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (25, '阿里云存储(示例)', 20, '', b'0', '{\"@class\":\"cn.iocoder.yudao.module.infra.framework.file.core.client.s3.S3FileClientConfig\",\"endpoint\":\"oss-cn-beijing.aliyuncs.com\",\"domain\":\"http://ali-oss.iocoder.cn\",\"bucket\":\"yunai-aoteman\",\"accessKey\":\"LTAI5tEQLgnDyjh3WpNcdMKA\",\"accessSecret\":\"X\"}', '1', '2024-11-09 16:47:08', '1', '2025-04-07 09:15:02', b'0');
-INSERT INTO `infra_file_config` (`id`, `name`, `storage`, `remark`, `master`, `config`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (26, '火山云存储(示例)', 20, '', b'0', '{\"@class\":\"cn.iocoder.yudao.module.infra.framework.file.core.client.s3.S3FileClientConfig\",\"endpoint\":\"tos-s3-cn-beijing.volces.com\",\"domain\":null,\"bucket\":\"yunai\",\"accessKey\":\"AKLTZjc3Zjc4MzZmMjU3NDk0ZTgxYmIyMmFkNTIwMDI1ZGE\",\"accessSecret\":\"X==\"}', '1', '2024-11-09 16:56:42', '1', '2025-04-07 09:15:02', b'0');
-INSERT INTO `infra_file_config` (`id`, `name`, `storage`, `remark`, `master`, `config`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (27, '华为云存储(示例)', 20, '', b'0', '{\"@class\":\"cn.iocoder.yudao.module.infra.framework.file.core.client.s3.S3FileClientConfig\",\"endpoint\":\"obs.cn-east-3.myhuaweicloud.com\",\"domain\":\"\",\"bucket\":\"yudao\",\"accessKey\":\"PVDONDEIOTW88LF8DC4U\",\"accessSecret\":\"X\"}', '1', '2024-11-09 17:18:41', '1', '2025-04-07 09:15:02', b'0');
-INSERT INTO `infra_file_config` (`id`, `name`, `storage`, `remark`, `master`, `config`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (28, 'MinIO 存储(示例)', 20, '', b'0', '{\"@class\":\"cn.iocoder.yudao.module.infra.framework.file.core.client.s3.S3FileClientConfig\",\"endpoint\":\"http://127.0.0.1:9000\",\"domain\":\"http://127.0.0.1:9000/yudao\",\"bucket\":\"yudao\",\"accessKey\":\"admin\",\"accessSecret\":\"password\"}', '1', '2024-11-09 17:43:10', '1', '2025-04-07 09:15:02', b'0');
+INSERT INTO `infra_file_config` (`id`, `name`, `storage`, `remark`, `master`, `config`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (4, '数据库(示例)', 1, '我是数据库', b'0', '{\"@class\":\"cn.iocoder.yudao.module.infra.framework.file.core.client.db.DBFileClientConfig\",\"domain\":\"http://127.0.0.1:48080\"}', '1', '2022-03-15 23:56:24', '1', '2025-05-02 18:30:28', b'0');
+INSERT INTO `infra_file_config` (`id`, `name`, `storage`, `remark`, `master`, `config`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (22, '七牛存储器(示例)', 20, '请换成你自己的密钥!!!', b'1', '{\"@class\":\"cn.iocoder.yudao.module.infra.framework.file.core.client.s3.S3FileClientConfig\",\"endpoint\":\"s3.cn-south-1.qiniucs.com\",\"domain\":\"http://test.yudao.iocoder.cn\",\"bucket\":\"ruoyi-vue-pro\",\"accessKey\":\"3TvrJ70gl2Gt6IBe7_IZT1F6i_k0iMuRtyEv4EyS\",\"accessSecret\":\"wd0tbVBYlp0S-ihA8Qg2hPLncoP83wyrIq24OZuY\",\"enablePathStyleAccess\":false}', '1', '2024-01-13 22:11:12', '1', '2025-05-02 18:30:28', b'0');
+INSERT INTO `infra_file_config` (`id`, `name`, `storage`, `remark`, `master`, `config`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (24, '腾讯云存储(示例)', 20, '请换成你的密钥!!!', b'0', '{\"@class\":\"cn.iocoder.yudao.module.infra.framework.file.core.client.s3.S3FileClientConfig\",\"endpoint\":\"https://cos.ap-shanghai.myqcloud.com\",\"domain\":\"http://tengxun-oss.iocoder.cn\",\"bucket\":\"aoteman-1255880240\",\"accessKey\":\"AKIDAF6WSh1uiIjwqtrOsGSN3WryqTM6cTMt\",\"accessSecret\":\"X\"}', '1', '2024-11-09 16:03:22', '1', '2025-05-02 18:30:28', b'0');
+INSERT INTO `infra_file_config` (`id`, `name`, `storage`, `remark`, `master`, `config`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (25, '阿里云存储(示例)', 20, '', b'0', '{\"@class\":\"cn.iocoder.yudao.module.infra.framework.file.core.client.s3.S3FileClientConfig\",\"endpoint\":\"oss-cn-beijing.aliyuncs.com\",\"domain\":\"http://ali-oss.iocoder.cn\",\"bucket\":\"yunai-aoteman\",\"accessKey\":\"LTAI5tEQLgnDyjh3WpNcdMKA\",\"accessSecret\":\"X\",\"enablePathStyleAccess\":false}', '1', '2024-11-09 16:47:08', '1', '2025-05-02 18:30:28', b'0');
+INSERT INTO `infra_file_config` (`id`, `name`, `storage`, `remark`, `master`, `config`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (26, '火山云存储(示例)', 20, '', b'0', '{\"@class\":\"cn.iocoder.yudao.module.infra.framework.file.core.client.s3.S3FileClientConfig\",\"endpoint\":\"tos-s3-cn-beijing.volces.com\",\"domain\":null,\"bucket\":\"yunai\",\"accessKey\":\"AKLTZjc3Zjc4MzZmMjU3NDk0ZTgxYmIyMmFkNTIwMDI1ZGE\",\"accessSecret\":\"X==\",\"enablePathStyleAccess\":false}', '1', '2024-11-09 16:56:42', '1', '2025-05-02 18:30:28', b'0');
+INSERT INTO `infra_file_config` (`id`, `name`, `storage`, `remark`, `master`, `config`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (27, '华为云存储(示例)', 20, '', b'0', '{\"@class\":\"cn.iocoder.yudao.module.infra.framework.file.core.client.s3.S3FileClientConfig\",\"endpoint\":\"obs.cn-east-3.myhuaweicloud.com\",\"domain\":\"\",\"bucket\":\"yudao\",\"accessKey\":\"PVDONDEIOTW88LF8DC4U\",\"accessSecret\":\"X\",\"enablePathStyleAccess\":false}', '1', '2024-11-09 17:18:41', '1', '2025-05-02 18:30:28', b'0');
+INSERT INTO `infra_file_config` (`id`, `name`, `storage`, `remark`, `master`, `config`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (28, 'MinIO 存储(示例)', 20, '', b'0', '{\"@class\":\"cn.iocoder.yudao.module.infra.framework.file.core.client.s3.S3FileClientConfig\",\"endpoint\":\"http://127.0.0.1:9000\",\"domain\":\"http://127.0.0.1:9000/yudao\",\"bucket\":\"yudao\",\"accessKey\":\"admin\",\"accessSecret\":\"password\",\"enablePathStyleAccess\":false}', '1', '2024-11-09 17:43:10', '1', '2025-05-02 18:30:28', b'0');
+INSERT INTO `infra_file_config` (`id`, `name`, `storage`, `remark`, `master`, `config`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (29, '本地存储(示例)', 10, '仅适合 mac 或 windows', b'0', '{\"@class\":\"cn.iocoder.yudao.module.infra.framework.file.core.client.local.LocalFileClientConfig\",\"basePath\":\"/Users/yunai/tmp/file\",\"domain\":\"http://127.0.0.1:48080\"}', '1', '2025-05-02 11:25:45', '1', '2025-05-02 18:30:28', b'0');
+INSERT INTO `infra_file_config` (`id`, `name`, `storage`, `remark`, `master`, `config`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (30, 'SFTP 存储(示例)', 12, '', b'0', '{\"@class\":\"cn.iocoder.yudao.module.infra.framework.file.core.client.sftp.SftpFileClientConfig\",\"basePath\":\"/upload\",\"domain\":\"http://127.0.0.1:48080\",\"host\":\"127.0.0.1\",\"port\":2222,\"username\":\"foo\",\"password\":\"pass\"}', '1', '2025-05-02 16:34:10', '1', '2025-05-02 18:30:28', b'0');
 COMMIT;
 
 -- ----------------------------
@@ -305,7 +308,7 @@ CREATE TABLE `infra_file_content`  (
   `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
   `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
   PRIMARY KEY (`id`) USING BTREE
-) ENGINE = InnoDB AUTO_INCREMENT = 283 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '文件表';
+) ENGINE = InnoDB AUTO_INCREMENT = 286 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '文件表';
 
 -- ----------------------------
 -- Records of infra_file_content
@@ -333,7 +336,7 @@ CREATE TABLE `infra_job`  (
   `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
   `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
   PRIMARY KEY (`id`) USING BTREE
-) ENGINE = InnoDB AUTO_INCREMENT = 34 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '定时任务表';
+) ENGINE = InnoDB AUTO_INCREMENT = 36 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '定时任务表';
 
 -- ----------------------------
 -- Records of infra_job
@@ -350,7 +353,8 @@ INSERT INTO `infra_job` (`id`, `name`, `status`, `handler_name`, `handler_param`
 INSERT INTO `infra_job` (`id`, `name`, `status`, `handler_name`, `handler_param`, `cron_expression`, `retry_count`, `retry_interval`, `monitor_timeout`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (25, '访问日志清理 Job', 2, 'accessLogCleanJob', '', '0 0 0 * * ?', 3, 0, 0, '1', '2023-10-03 10:59:41', '1', '2023-10-03 11:01:10', b'0');
 INSERT INTO `infra_job` (`id`, `name`, `status`, `handler_name`, `handler_param`, `cron_expression`, `retry_count`, `retry_interval`, `monitor_timeout`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (26, '错误日志清理 Job', 2, 'errorLogCleanJob', '', '0 0 0 * * ?', 3, 0, 0, '1', '2023-10-03 11:00:43', '1', '2023-10-03 11:01:12', b'0');
 INSERT INTO `infra_job` (`id`, `name`, `status`, `handler_name`, `handler_param`, `cron_expression`, `retry_count`, `retry_interval`, `monitor_timeout`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (27, '任务日志清理 Job', 2, 'jobLogCleanJob', '', '0 0 0 * * ?', 3, 0, 0, '1', '2023-10-03 11:01:33', '1', '2024-09-12 13:40:34', b'0');
-INSERT INTO `infra_job` (`id`, `name`, `status`, `handler_name`, `handler_param`, `cron_expression`, `retry_count`, `retry_interval`, `monitor_timeout`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (33, 'demoJob', 2, 'demoJob', '', '0 * * * * ?', 1, 1, 0, '1', '2024-10-27 19:38:46', '1', '2024-10-27 19:40:23', b'0');
+INSERT INTO `infra_job` (`id`, `name`, `status`, `handler_name`, `handler_param`, `cron_expression`, `retry_count`, `retry_interval`, `monitor_timeout`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (33, 'demoJob', 2, 'demoJob', '', '0 * * * * ?', 1, 1, 0, '1', '2024-10-27 19:38:46', '1', '2025-05-10 18:13:54', b'0');
+INSERT INTO `infra_job` (`id`, `name`, `status`, `handler_name`, `handler_param`, `cron_expression`, `retry_count`, `retry_interval`, `monitor_timeout`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (35, '转账订单的同步 Job', 2, 'payTransferSyncJob', '', '0 * * * * ?', 0, 0, 0, '1', '2025-05-10 17:35:54', '1', '2025-05-10 18:13:52', b'0');
 COMMIT;
 
 -- ----------------------------
@@ -374,7 +378,7 @@ CREATE TABLE `infra_job_log`  (
   `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
   `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
   PRIMARY KEY (`id`) USING BTREE
-) ENGINE = InnoDB AUTO_INCREMENT = 638 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '定时任务日志表';
+) ENGINE = InnoDB AUTO_INCREMENT = 972 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '定时任务日志表';
 
 -- ----------------------------
 -- Records of infra_job_log
@@ -444,7 +448,7 @@ CREATE TABLE `system_dict_data`  (
   `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
   `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
   PRIMARY KEY (`id`) USING BTREE
-) ENGINE = InnoDB AUTO_INCREMENT = 3002 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '字典数据表';
+) ENGINE = InnoDB AUTO_INCREMENT = 3003 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '字典数据表';
 
 -- ----------------------------
 -- Records of system_dict_data
@@ -666,8 +670,8 @@ INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `st
 INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1363, 3, '覆盖绑定', '3', 'brokerage_bind_mode', 0, '', '', '如果用户已经有推广人,推广人会被变更', '', '2023-09-28 02:46:05', '', '2023-09-28 02:46:05', b'0');
 INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1364, 1, '钱包', '1', 'brokerage_withdraw_type', 0, '', '', NULL, '', '2023-09-28 02:46:05', '', '2023-09-28 02:46:05', b'0');
 INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1365, 2, '银行卡', '2', 'brokerage_withdraw_type', 0, '', '', NULL, '', '2023-09-28 02:46:05', '', '2023-09-28 02:46:05', b'0');
-INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1366, 3, '微信', '3', 'brokerage_withdraw_type', 0, '', '', '手动打款', '', '2023-09-28 02:46:05', '1', '2024-10-13 11:06:54', b'0');
-INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1367, 4, '支付宝', '4', 'brokerage_withdraw_type', 0, '', '', NULL, '', '2023-09-28 02:46:05', '', '2023-09-28 02:46:05', b'0');
+INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1366, 3, '微信收款码', '3', 'brokerage_withdraw_type', 0, '', '', '手动打款', '', '2023-09-28 02:46:05', '1', '2025-05-10 08:24:25', b'0');
+INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1367, 4, '支付宝收款码', '4', 'brokerage_withdraw_type', 0, '', '', '手动打款', '', '2023-09-28 02:46:05', '1', '2025-05-10 08:24:37', b'0');
 INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1368, 1, '订单返佣', '1', 'brokerage_record_biz_type', 0, '', '', NULL, '', '2023-09-28 02:46:05', '', '2023-09-28 02:46:05', b'0');
 INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1369, 2, '申请提现', '2', 'brokerage_record_biz_type', 0, '', '', NULL, '', '2023-09-28 02:46:05', '', '2023-09-28 02:46:05', b'0');
 INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1370, 3, '申请提现驳回', '3', 'brokerage_record_biz_type', 0, '', '', NULL, '', '2023-09-28 02:46:05', '', '2023-09-28 02:46:05', b'0');
@@ -764,13 +768,9 @@ INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `st
 INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1475, 2, '发短信', '2', 'crm_follow_up_type', 0, '', '', '', '1', '2024-01-15 20:48:31', '1', '2024-01-15 20:48:31', b'0');
 INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1476, 3, '上门拜访', '3', 'crm_follow_up_type', 0, '', '', '', '1', '2024-01-15 20:49:07', '1', '2024-01-15 20:49:07', b'0');
 INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1477, 4, '微信沟通', '4', 'crm_follow_up_type', 0, '', '', '', '1', '2024-01-15 20:49:15', '1', '2024-01-15 20:49:15', b'0');
-INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1478, 4, '钱包余额', '4', 'pay_transfer_type', 0, 'info', '', '', '1', '2023-10-28 16:28:37', '1', '2023-10-28 16:28:37', b'0');
-INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1479, 3, '银行卡', '3', 'pay_transfer_type', 0, 'default', '', '', '1', '2023-10-28 16:28:21', '1', '2023-10-28 16:28:21', b'0');
-INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1480, 2, '微信余额', '2', 'pay_transfer_type', 0, 'info', '', '', '1', '2023-10-28 16:28:07', '1', '2023-10-28 16:28:07', b'0');
-INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1481, 1, '支付宝余额', '1', 'pay_transfer_type', 0, 'default', '', '', '1', '2023-10-28 16:27:44', '1', '2023-10-28 16:27:44', b'0');
-INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1482, 4, '转账失败', '30', 'pay_transfer_status', 0, 'warning', '', '', '1', '2023-10-28 16:24:16', '1', '2023-10-28 16:24:16', b'0');
-INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1483, 3, '转账成功', '20', 'pay_transfer_status', 0, 'success', '', '', '1', '2023-10-28 16:23:50', '1', '2023-10-28 16:23:50', b'0');
-INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1484, 2, '转账进行中', '10', 'pay_transfer_status', 0, 'info', '', '', '1', '2023-10-28 16:23:12', '1', '2023-10-28 16:23:12', b'0');
+INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1482, 4, '转账失败', '20', 'pay_transfer_status', 0, 'warning', '', '', '1', '2023-10-28 16:24:16', '1', '2025-05-08 12:59:01', b'0');
+INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1483, 3, '转账成功', '10', 'pay_transfer_status', 0, 'success', '', '', '1', '2023-10-28 16:23:50', '1', '2025-05-08 12:58:58', b'0');
+INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1484, 2, '转账进行中', '5', 'pay_transfer_status', 0, 'info', '', '', '1', '2023-10-28 16:23:12', '1', '2025-05-08 12:58:54', b'0');
 INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1485, 1, '等待转账', '0', 'pay_transfer_status', 0, 'default', '', '', '1', '2023-10-28 16:21:43', '1', '2023-10-28 16:23:22', b'0');
 INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1486, 10, '其它入库', '10', 'erp_stock_record_biz_type', 0, '', '', '', '1', '2024-02-05 18:07:25', '1', '2024-02-05 18:07:43', b'0');
 INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1487, 11, '其它入库(作废)', '11', 'erp_stock_record_biz_type', 0, 'danger', '', '', '1', '2024-02-05 18:08:07', '1', '2024-02-05 19:20:16', b'0');
@@ -869,7 +869,7 @@ INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `st
 INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1590, 20, 'SIMPLE 设计器', '20', 'bpm_model_type', 0, 'success', '', '', '1', '2024-08-26 15:22:27', '1', '2024-08-26 16:45:58', b'0');
 INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1591, 4, '七牛云', 'QINIU', 'system_sms_channel_code', 0, '', '', '', '1', '2024-08-31 08:45:03', '1', '2024-08-31 08:45:24', b'0');
 INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1592, 3, '新人券', '3', 'promotion_coupon_take_type', 0, 'info', '', '新人注册后,自动发放', '1', '2024-09-03 11:57:16', '1', '2024-09-03 11:57:28', b'0');
-INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1593, 5, '微信零钱', '5', 'brokerage_withdraw_type', 0, '', '', '自动打款', '1', '2024-10-13 11:06:48', '1', '2024-10-13 11:06:59', b'0');
+INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1593, 5, '微信零钱', '5', 'brokerage_withdraw_type', 0, '', '', 'API 打款', '1', '2024-10-13 11:06:48', '1', '2025-05-10 08:24:55', b'0');
 INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1683, 10, '字节豆包', 'DouBao', 'ai_platform', 0, '', '', '', '1', '2025-02-23 19:51:40', '1', '2025-02-23 19:52:02', b'0');
 INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1684, 11, '腾讯混元', 'HunYuan', 'ai_platform', 0, '', '', '', '1', '2025-02-23 20:58:04', '1', '2025-02-23 20:58:04', b'0');
 INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1685, 12, '硅基流动', 'SiliconFlow', 'ai_platform', 0, '', '', '', '1', '2025-02-24 20:19:09', '1', '2025-02-24 20:19:09', b'0');
@@ -1055,7 +1055,8 @@ INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `st
 INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2172, 31, 'RABBITMQ', '31', 'iot_data_bridge_type_enum', 0, 'primary', '', '', '1', '2025-03-09 12:41:47', '1', '2025-03-17 09:40:46', b'0');
 INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2173, 32, 'KAFKA', '32', 'iot_data_bridge_type_enum', 0, 'primary', '', '', '1', '2025-03-09 12:41:59', '1', '2025-03-17 09:40:46', b'0');
 INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3000, 16, '百川智能', 'BaiChuan', 'ai_platform', 0, '', '', '', '1', '2025-03-23 12:15:46', '1', '2025-03-23 12:15:46', b'0');
-INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3001, 50, 'Vben5.0 Ant Design Schema 模版', '40', 'infra_codegen_front_type', 0, '', '', NULL, '1', '2025-04-23 21:47:47', '1', '2025-04-23 21:47:47', b'0');
+INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3001, 50, 'Vben5.0 Ant Design Schema 模版', '40', 'infra_codegen_front_type', 0, '', '', NULL, '1', '2025-04-23 21:47:47', '1', '2025-05-02 12:01:15', b'0');
+INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3002, 6, '支付宝余额', '6', 'brokerage_withdraw_type', 0, '', '', 'API 打款', '1', '2025-05-10 08:24:49', '1', '2025-05-10 08:24:49', b'0');
 COMMIT;
 
 -- ----------------------------
@@ -1156,7 +1157,6 @@ INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creat
 INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (606, 'CRM 审批状态', 'crm_audit_status', 0, '', '1', '2023-11-30 18:56:23', '1', '2023-11-30 18:56:23', b'0', '1970-01-01 00:00:00');
 INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (607, 'CRM 产品单位', 'crm_product_unit', 0, '', '1', '2023-12-05 23:01:51', '1', '2023-12-05 23:01:51', b'0', '1970-01-01 00:00:00');
 INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (608, 'CRM 跟进方式', 'crm_follow_up_type', 0, '', '1', '2024-01-15 20:48:05', '1', '2024-01-15 20:48:05', b'0', '1970-01-01 00:00:00');
-INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (609, '支付转账类型', 'pay_transfer_type', 0, '', '1', '2023-10-28 16:27:18', '1', '2023-10-28 16:27:18', b'0', '1970-01-01 00:00:00');
 INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (610, '转账订单状态', 'pay_transfer_status', 0, '', '1', '2023-10-28 16:18:32', '1', '2023-10-28 16:18:32', b'0', '1970-01-01 00:00:00');
 INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (611, 'ERP 库存明细的业务类型', 'erp_stock_record_biz_type', 0, 'ERP 库存明细的业务类型', '1', '2024-02-05 18:07:02', '1', '2024-02-05 18:07:02', b'0', '1970-01-01 00:00:00');
 INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (612, 'ERP 审批状态', 'erp_audit_status', 0, '', '1', '2024-02-06 00:00:07', '1', '2024-02-06 00:00:07', b'0', '1970-01-01 00:00:00');
@@ -1212,7 +1212,7 @@ CREATE TABLE `system_login_log`  (
   `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
   `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
   PRIMARY KEY (`id`) USING BTREE
-) ENGINE = InnoDB AUTO_INCREMENT = 3746 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '系统访问记录';
+) ENGINE = InnoDB AUTO_INCREMENT = 3822 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '系统访问记录';
 
 -- ----------------------------
 -- Records of system_login_log
@@ -1343,7 +1343,7 @@ CREATE TABLE `system_menu`  (
   `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
   `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
   PRIMARY KEY (`id`) USING BTREE
-) ENGINE = InnoDB AUTO_INCREMENT = 5008 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '菜单权限表';
+) ENGINE = InnoDB AUTO_INCREMENT = 5013 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '菜单权限表';
 
 -- ----------------------------
 -- Records of system_menu
@@ -1486,15 +1486,9 @@ INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_i
 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1150, '秘钥解析', '', 3, 6, 1129, '', '', '', NULL, 0, b'1', b'1', b'1', '1', '2021-11-08 15:15:47', '1', '2022-04-20 17:03:10', b'0');
 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1161, '退款订单', '', 2, 3, 1117, 'refund', 'fa:registered', 'pay/refund/index', 'PayRefund', 0, b'1', b'1', b'1', '', '2021-12-25 08:29:07', '1', '2024-02-29 08:59:20', b'0');
 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1162, '退款订单查询', 'pay:refund:query', 3, 1, 1161, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2021-12-25 08:29:07', '', '2022-04-20 17:03:10', b'0');
-INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1163, '退款订单创建', 'pay:refund:create', 3, 2, 1161, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2021-12-25 08:29:07', '', '2022-04-20 17:03:10', b'0');
-INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1164, '退款订单更新', 'pay:refund:update', 3, 3, 1161, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2021-12-25 08:29:07', '', '2022-04-20 17:03:10', b'0');
-INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1165, '退款订单删除', 'pay:refund:delete', 3, 4, 1161, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2021-12-25 08:29:07', '', '2022-04-20 17:03:10', b'0');
 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1166, '退款订单导出', 'pay:refund:export', 3, 5, 1161, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2021-12-25 08:29:07', '', '2022-04-20 17:03:10', b'0');
 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1173, '支付订单', '', 2, 2, 1117, 'order', 'fa:cc-paypal', 'pay/order/index', 'PayOrder', 0, b'1', b'1', b'1', '', '2021-12-25 08:49:43', '1', '2024-02-29 08:59:43', b'0');
 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1174, '支付订单查询', 'pay:order:query', 3, 1, 1173, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2021-12-25 08:49:43', '', '2022-04-20 17:03:10', b'0');
-INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1175, '支付订单创建', 'pay:order:create', 3, 2, 1173, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2021-12-25 08:49:43', '', '2022-04-20 17:03:10', b'0');
-INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1176, '支付订单更新', 'pay:order:update', 3, 3, 1173, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2021-12-25 08:49:43', '', '2022-04-20 17:03:10', b'0');
-INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1177, '支付订单删除', 'pay:order:delete', 3, 4, 1173, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2021-12-25 08:49:43', '', '2022-04-20 17:03:10', b'0');
 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1178, '支付订单导出', 'pay:order:export', 3, 5, 1173, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2021-12-25 08:49:43', '', '2022-04-20 17:03:10', b'0');
 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1185, '工作流程', '', 1, 50, 0, '/bpm', 'fa:medium', NULL, NULL, 0, b'1', b'1', b'1', '1', '2021-12-30 20:26:36', '1', '2024-02-29 12:43:43', b'0');
 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1186, '流程管理', '', 1, 10, 1185, 'manager', 'fa:dedent', NULL, NULL, 0, b'1', b'1', b'1', '1', '2021-12-30 20:28:30', '1', '2024-02-29 12:36:02', b'0');
@@ -1541,7 +1535,7 @@ INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_i
 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1241, '文件配置删除', 'infra:file-config:delete', 3, 4, 1237, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2022-03-15 14:35:28', '', '2022-04-20 17:03:10', b'0');
 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1242, '文件配置导出', 'infra:file-config:export', 3, 5, 1237, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2022-03-15 14:35:28', '', '2022-04-20 17:03:10', b'0');
 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1243, '文件管理', '', 2, 6, 2, 'file', 'ep:files', NULL, '', 0, b'1', b'1', b'1', '1', '2022-03-16 23:47:40', '1', '2024-04-23 00:02:11', b'0');
-INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1254, '作者动态', '', 1, 0, 0, 'https://www.iocoder.cn', 'ep:avatar', NULL, NULL, 0, b'1', b'1', b'1', '1', '2022-04-23 01:03:15', '104', '2025-01-04 10:59:37', b'0');
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1254, '作者动态', '', 1, 0, 0, 'https://www.iocoder.cn', 'ep:avatar', NULL, NULL, 0, b'1', b'1', b'1', '1', '2022-04-23 01:03:15', '1', '2025-04-29 17:45:38', b'0');
 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1255, '数据源配置', '', 2, 1, 2, 'data-source-config', 'ep:data-analysis', 'infra/dataSourceConfig/index', 'InfraDataSourceConfig', 0, b'1', b'1', b'1', '', '2022-04-27 14:37:32', '1', '2024-02-29 08:51:25', b'0');
 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1256, '数据源配置查询', 'infra:data-source-config:query', 3, 1, 1255, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2022-04-27 14:37:32', '', '2022-04-27 14:37:32', b'0');
 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1257, '数据源配置创建', 'infra:data-source-config:create', 3, 2, 1255, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2022-04-27 14:37:32', '', '2022-04-27 14:37:32', b'0');
@@ -1555,7 +1549,7 @@ INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_i
 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1266, '客户端更新', 'system:oauth2-client:update', 3, 3, 1263, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2022-05-10 16:26:33', '1', '2022-05-11 00:31:28', b'0');
 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1267, '客户端删除', 'system:oauth2-client:delete', 3, 4, 1263, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2022-05-10 16:26:33', '1', '2022-05-11 00:31:33', b'0');
 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1281, '报表管理', '', 2, 40, 0, '/report', 'ep:pie-chart', NULL, NULL, 0, b'1', b'1', b'1', '1', '2022-07-10 20:22:15', '1', '2024-02-29 12:33:03', b'0');
-INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1282, '报表设计器', '', 2, 1, 1281, 'jimu-report', 'ep:trend-charts', 'report/jmreport/index', 'GoView', 0, b'1', b'1', b'1', '1', '2022-07-10 20:26:36', '1', '2024-02-29 12:33:54', b'0');
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1282, '报表设计器', '', 2, 1, 1281, 'jimu-report', 'ep:trend-charts', 'report/jmreport/index', 'JimuReport', 0, b'1', b'1', b'1', '1', '2022-07-10 20:26:36', '1', '2025-05-03 09:57:07', b'0');
 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2000, '商品中心', '', 1, 60, 2362, 'product', 'fa:product-hunt', NULL, NULL, 0, b'1', b'1', b'1', '', '2022-07-29 15:53:53', '1', '2023-09-30 11:52:36', b'0');
 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2002, '商品分类', '', 2, 2, 2000, 'category', 'ep:cellphone', 'mall/product/category/index', 'ProductCategory', 0, b'1', b'1', b'1', '', '2022-07-29 15:53:53', '1', '2023-08-21 10:27:15', b'0');
 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2003, '分类查询', 'product:category:query', 3, 1, 2002, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2022-07-29 15:53:53', '', '2022-07-29 15:53:53', b'0');
@@ -1688,7 +1682,7 @@ INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_i
 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2150, '发送测试站内信', 'system:notify-template:send-notify', 3, 5, 2145, '', '', '', NULL, 0, b'1', b'1', b'1', '1', '2023-01-28 10:54:43', '1', '2023-01-28 10:54:43', b'0');
 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2151, '消息记录', '', 2, 0, 2144, 'notify-message', 'fa:edit', 'system/notify/message/index', 'SystemNotifyMessage', 0, b'1', b'1', b'1', '', '2023-01-28 04:28:22', '1', '2024-02-29 08:49:22', b'0');
 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2152, '站内信消息查询', 'system:notify-message:query', 3, 1, 2151, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-01-28 04:28:22', '', '2023-01-28 04:28:22', b'0');
-INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2153, '大屏设计器', '', 2, 2, 1281, 'go-view', 'fa:area-chart', 'report/goview/index', 'JimuReport', 0, b'1', b'1', b'1', '1', '2023-02-07 00:03:19', '1', '2024-02-29 12:34:02', b'0');
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2153, '大屏设计器', '', 2, 2, 1281, 'go-view', 'fa:area-chart', 'report/goview/index', 'GoView', 0, b'1', b'1', b'1', '1', '2023-02-07 00:03:19', '1', '2025-05-03 09:57:03', b'0');
 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2154, '创建项目', 'report:go-view-project:create', 3, 1, 2153, '', '', '', NULL, 0, b'1', b'1', b'1', '1', '2023-02-07 19:25:14', '1', '2023-02-07 19:25:14', b'0');
 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2155, '更新项目', 'report:go-view-project:update', 3, 2, 2153, '', '', '', '', 0, b'1', b'1', b'1', '1', '2023-02-07 19:25:34', '1', '2024-04-24 20:01:18', b'0');
 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2156, '查询项目', 'report:go-view-project:query', 3, 0, 2153, '', '', '', NULL, 0, b'1', b'1', b'1', '1', '2023-02-07 19:25:53', '1', '2023-02-07 19:25:53', b'0');
@@ -1931,7 +1925,7 @@ INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_i
 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2547, '订单查询', 'trade:order:query', 3, 1, 2076, '', '', '', '', 0, b'1', b'1', b'1', '1', '2024-01-16 08:52:00', '1', '2024-01-16 08:52:00', b'0');
 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2548, '订单更新', 'trade:order:update', 3, 2, 2076, '', '', '', '', 0, b'1', b'1', b'1', '1', '2024-01-16 08:52:21', '1', '2024-01-16 08:52:21', b'0');
 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2549, '支付&退款案例', '', 2, 1, 2161, 'order', 'fa:paypal', 'pay/demo/order/index', '', 0, b'1', b'1', b'1', '1', '2024-01-18 23:45:00', '1', '2024-01-18 23:47:21', b'0');
-INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2550, '转账案例', '', 2, 2, 2161, 'transfer', 'fa:transgender-alt', 'pay/demo/transfer/index', '', 0, b'1', b'1', b'1', '1', '2024-01-18 23:51:16', '1', '2024-01-18 23:51:16', b'0');
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2550, '提现转账案例', '', 2, 2, 2161, 'transfer', 'fa:transgender-alt', 'pay/demo/withdraw/index', '', 0, b'1', b'1', b'1', '1', '2024-01-18 23:51:16', '1', '2025-05-08 13:04:36', b'0');
 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2551, '钱包管理', '', 1, 4, 1117, 'wallet', 'ep:wallet', '', '', 0, b'1', b'1', b'1', '', '2023-12-29 02:32:54', '1', '2024-02-29 08:58:54', b'0');
 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2552, '充值套餐', '', 2, 2, 2551, 'wallet-recharge-package', 'fa:leaf', 'pay/wallet/rechargePackage/index', 'WalletRechargePackage', 0, b'1', b'1', b'1', '', '2023-12-29 02:32:54', '', '2023-12-29 02:32:54', b'0');
 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2553, '钱包充值套餐查询', 'pay:wallet-recharge-package:query', 3, 1, 2552, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-12-29 02:32:54', '', '2023-12-29 02:32:54', b'0');
@@ -2256,12 +2250,16 @@ INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_i
 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (4054, 'IoT 数据桥梁更新', 'iot:data-bridge:update', 3, 3, 4051, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2025-03-09 13:47:11', '', '2025-03-09 13:47:11', b'0');
 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (4055, 'IoT 数据桥梁删除', 'iot:data-bridge:delete', 3, 4, 4051, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2025-03-09 13:47:12', '', '2025-03-09 13:47:12', b'0');
 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (4056, 'IoT 数据桥梁导出', 'iot:data-bridge:export', 3, 5, 4051, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2025-03-09 13:47:12', '', '2025-03-09 13:47:12', b'0');
-INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (5000, 'AI 工作流', '', 2, 5, 2758, 'workflow', 'fa:hand-grab-o', 'ai/workflow/index.vue', '', 0, b'1', b'1', b'1', '1', '2025-03-25 09:50:27', '1', '2025-03-30 10:24:52', b'0');
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (5000, 'AI 工作流', '', 2, 5, 2758, 'workflow', 'fa:hand-grab-o', 'ai/workflow/index.vue', 'AiWorkflow', 0, b'1', b'1', b'1', '1', '2025-03-25 09:50:27', '1', '2025-05-03 18:55:12', b'0');
 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (5001, 'AI 工作流查询', 'ai:workflow:query', 3, 1, 5000, '', '', '', '', 0, b'1', b'1', b'1', '1', '2025-03-25 09:51:11', '1', '2025-03-25 09:51:11', b'0');
 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (5002, 'AI 工作流创建', 'ai:workflow:create', 3, 2, 5000, '', '', '', '', 0, b'1', b'1', b'1', '1', '2025-03-25 09:51:28', '1', '2025-03-25 09:51:28', b'0');
 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (5003, 'AI 工作流更新', 'ai:workflow:update', 3, 3, 5000, '', '', '', '', 0, b'1', b'1', b'1', '1', '2025-03-25 09:51:42', '1', '2025-03-25 09:51:42', b'0');
 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (5004, 'AI 工作流删除', 'ai:workflow:delete', 3, 4, 5000, '', '', '', '', 0, b'1', b'1', b'1', '1', '2025-03-25 09:51:55', '1', '2025-03-25 09:52:03', b'0');
 INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (5005, 'AI 工作流测试', 'ai:workflow:test', 3, 5, 5000, '', '', '', '', 0, b'1', b'1', b'1', '1', '2025-03-30 10:29:41', '1', '2025-03-30 10:29:41', b'0');
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (5009, '仪表盘设计器', '', 2, 1, 1281, 'jimu-bi', 'fa:y-combinator', 'report/jmreport/bi', 'JimuBI', 0, b'1', b'1', b'1', '1', '2025-05-03 09:57:15', '1', '2025-05-03 10:02:05', b'0');
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (5010, '租户切换', 'system:tenant:visit', 3, 999, 1138, '', '', '', '', 0, b'1', b'1', b'1', '1', '2025-05-05 15:25:32', '1', '2025-05-05 15:25:32', b'0');
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (5011, '转账订单查询', 'pay:transfer:query', 3, 1, 2559, '', '', '', '', 0, b'1', b'1', b'1', '1', '2025-05-08 12:46:53', '1', '2025-05-08 12:46:53', b'0');
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (5012, '转账订单导出', 'pay:transfer:export', 3, 2, 2559, '', '', '', '', 0, b'1', b'1', b'1', '1', '2025-05-10 17:00:28', '1', '2025-05-10 17:00:28', b'0');
 COMMIT;
 
 -- ----------------------------
@@ -2383,7 +2381,7 @@ CREATE TABLE `system_oauth2_access_token`  (
   PRIMARY KEY (`id`) USING BTREE,
   INDEX `idx_access_token`(`access_token` ASC) USING BTREE,
   INDEX `idx_refresh_token`(`refresh_token` ASC) USING BTREE
-) ENGINE = InnoDB AUTO_INCREMENT = 15215 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'OAuth2 访问令牌';
+) ENGINE = InnoDB AUTO_INCREMENT = 16697 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'OAuth2 访问令牌';
 
 -- ----------------------------
 -- Records of system_oauth2_access_token
@@ -2451,10 +2449,10 @@ CREATE TABLE `system_oauth2_client`  (
 -- Records of system_oauth2_client
 -- ----------------------------
 BEGIN;
-INSERT INTO `system_oauth2_client` (`id`, `client_id`, `secret`, `name`, `logo`, `description`, `status`, `access_token_validity_seconds`, `refresh_token_validity_seconds`, `redirect_uris`, `authorized_grant_types`, `scopes`, `auto_approve_scopes`, `authorities`, `resource_ids`, `additional_information`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1, 'default', 'admin123', '芋道源码', 'http://test.yudao.iocoder.cn/a5e2e244368878a366b516805a4aabf1.png', '我是描述', 0, 1800, 2592000, '[\"https://www.iocoder.cn\",\"https://doc.iocoder.cn\"]', '[\"password\",\"authorization_code\",\"implicit\",\"refresh_token\"]', '[\"user.read\",\"user.write\"]', '[]', '[\"user.read\",\"user.write\"]', '[]', '{}', '1', '2022-05-11 21:47:12', '1', '2025-03-27 10:30:12', b'0');
-INSERT INTO `system_oauth2_client` (`id`, `client_id`, `secret`, `name`, `logo`, `description`, `status`, `access_token_validity_seconds`, `refresh_token_validity_seconds`, `redirect_uris`, `authorized_grant_types`, `scopes`, `auto_approve_scopes`, `authorities`, `resource_ids`, `additional_information`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (40, 'test', 'test2', 'biubiu', 'http://test.yudao.iocoder.cn/277a899d573723f1fcdfb57340f00379.png', '啦啦啦啦', 0, 1800, 43200, '[\"https://www.iocoder.cn\"]', '[\"password\",\"authorization_code\",\"implicit\"]', '[\"user_info\",\"projects\"]', '[\"user_info\"]', '[]', '[]', '{}', '1', '2022-05-12 00:28:20', '1', '2025-04-06 19:45:45', b'0');
-INSERT INTO `system_oauth2_client` (`id`, `client_id`, `secret`, `name`, `logo`, `description`, `status`, `access_token_validity_seconds`, `refresh_token_validity_seconds`, `redirect_uris`, `authorized_grant_types`, `scopes`, `auto_approve_scopes`, `authorities`, `resource_ids`, `additional_information`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (41, 'yudao-sso-demo-by-code', 'test', '基于授权码模式,如何实现 SSO 单点登录?', 'http://test.yudao.iocoder.cn/fe4ed36596adad5120036ef61a6d0153654544d44af8dd4ad3ffe8f759933d6f.png', NULL, 0, 1800, 43200, '[\"http://127.0.0.1:18080\"]', '[\"authorization_code\",\"refresh_token\"]', '[\"user.read\",\"user.write\"]', '[]', '[]', '[]', NULL, '1', '2022-09-29 13:28:31', '1', '2025-04-06 19:39:40', b'0');
-INSERT INTO `system_oauth2_client` (`id`, `client_id`, `secret`, `name`, `logo`, `description`, `status`, `access_token_validity_seconds`, `refresh_token_validity_seconds`, `redirect_uris`, `authorized_grant_types`, `scopes`, `auto_approve_scopes`, `authorities`, `resource_ids`, `additional_information`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (42, 'yudao-sso-demo-by-password', 'test', '基于密码模式,如何实现 SSO 单点登录?', 'http://test.yudao.iocoder.cn/604bdc695e13b3b22745be704d1f2aa8ee05c5f26f9fead6d1ca49005afbc857.jpeg', NULL, 0, 1800, 43200, '[\"http://127.0.0.1:18080\"]', '[\"password\",\"refresh_token\"]', '[\"user.read\",\"user.write\"]', '[]', '[]', '[]', NULL, '1', '2022-10-04 17:40:16', '1', '2022-10-04 20:31:21', b'0');
+INSERT INTO `system_oauth2_client` (`id`, `client_id`, `secret`, `name`, `logo`, `description`, `status`, `access_token_validity_seconds`, `refresh_token_validity_seconds`, `redirect_uris`, `authorized_grant_types`, `scopes`, `auto_approve_scopes`, `authorities`, `resource_ids`, `additional_information`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1, 'default', 'admin123', '芋道源码', 'http://test.yudao.iocoder.cn/20250502/sort2_1746189740718.png', '我是描述', 0, 1800, 2592000, '[\"https://www.iocoder.cn\",\"https://doc.iocoder.cn\"]', '[\"password\",\"authorization_code\",\"implicit\",\"refresh_token\"]', '[\"user.read\",\"user.write\"]', '[]', '[\"user.read\",\"user.write\"]', '[]', '{}', '1', '2022-05-11 21:47:12', '1', '2025-05-02 20:42:22', b'0');
+INSERT INTO `system_oauth2_client` (`id`, `client_id`, `secret`, `name`, `logo`, `description`, `status`, `access_token_validity_seconds`, `refresh_token_validity_seconds`, `redirect_uris`, `authorized_grant_types`, `scopes`, `auto_approve_scopes`, `authorities`, `resource_ids`, `additional_information`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (40, 'test', 'test2', 'biubiu', 'http://test.yudao.iocoder.cn/xx/20250502/ed07110a37464b5299f8bd7c67ad65c7_1746187077009.jpg', '啦啦啦啦', 0, 1800, 43200, '[\"https://www.iocoder.cn\"]', '[\"password\",\"authorization_code\",\"implicit\"]', '[\"user_info\",\"projects\"]', '[\"user_info\"]', '[]', '[]', '{}', '1', '2022-05-12 00:28:20', '1', '2025-05-02 19:58:08', b'0');
+INSERT INTO `system_oauth2_client` (`id`, `client_id`, `secret`, `name`, `logo`, `description`, `status`, `access_token_validity_seconds`, `refresh_token_validity_seconds`, `redirect_uris`, `authorized_grant_types`, `scopes`, `auto_approve_scopes`, `authorities`, `resource_ids`, `additional_information`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (41, 'yudao-sso-demo-by-code', 'test', '基于授权码模式,如何实现 SSO 单点登录?', 'http://test.yudao.iocoder.cn/it/20250502/sign_1746181948685.png', NULL, 0, 1800, 43200, '[\"http://127.0.0.1:18080\"]', '[\"authorization_code\",\"refresh_token\"]', '[\"user.read\",\"user.write\"]', '[]', '[]', '[]', NULL, '1', '2022-09-29 13:28:31', '1', '2025-05-02 18:32:30', b'0');
+INSERT INTO `system_oauth2_client` (`id`, `client_id`, `secret`, `name`, `logo`, `description`, `status`, `access_token_validity_seconds`, `refresh_token_validity_seconds`, `redirect_uris`, `authorized_grant_types`, `scopes`, `auto_approve_scopes`, `authorities`, `resource_ids`, `additional_information`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (42, 'yudao-sso-demo-by-password', 'test', '基于密码模式,如何实现 SSO 单点登录?', 'http://test.yudao.iocoder.cn/604bdc695e13b3b22745be704d1f2aa8ee05c5f26f9fead6d1ca49005afbc857.jpeg', NULL, 0, 1800, 43200, '[\"http://127.0.0.1:18080\"]', '[\"password\",\"refresh_token\"]', '[\"user.read\",\"user.write\"]', '[]', '[]', '[]', NULL, '1', '2022-10-04 17:40:16', '1', '2025-05-04 16:00:46', b'0');
 COMMIT;
 
 -- ----------------------------
@@ -2505,7 +2503,7 @@ CREATE TABLE `system_oauth2_refresh_token`  (
   `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
   `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
   PRIMARY KEY (`id`) USING BTREE
-) ENGINE = InnoDB AUTO_INCREMENT = 1983 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'OAuth2 刷新令牌';
+) ENGINE = InnoDB AUTO_INCREMENT = 2036 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'OAuth2 刷新令牌';
 
 -- ----------------------------
 -- Records of system_oauth2_refresh_token
@@ -2539,7 +2537,7 @@ CREATE TABLE `system_operate_log`  (
   `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
   `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
   PRIMARY KEY (`id`) USING BTREE
-) ENGINE = InnoDB AUTO_INCREMENT = 9088 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '操作日志记录 V2 版本';
+) ENGINE = InnoDB AUTO_INCREMENT = 9090 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '操作日志记录 V2 版本';
 
 -- ----------------------------
 -- Records of system_operate_log
@@ -2607,7 +2605,7 @@ BEGIN;
 INSERT INTO `system_role` (`id`, `name`, `code`, `sort`, `data_scope`, `data_scope_dept_ids`, `status`, `type`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1, '超级管理员', 'super_admin', 1, 1, '', 0, 1, '超级管理员', 'admin', '2021-01-05 17:03:48', '', '2022-02-22 05:08:21', b'0', 1);
 INSERT INTO `system_role` (`id`, `name`, `code`, `sort`, `data_scope`, `data_scope_dept_ids`, `status`, `type`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2, '普通角色', 'common', 2, 2, '', 0, 1, '普通角色', 'admin', '2021-01-05 17:03:48', '', '2022-02-22 05:08:20', b'0', 1);
 INSERT INTO `system_role` (`id`, `name`, `code`, `sort`, `data_scope`, `data_scope_dept_ids`, `status`, `type`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (3, 'CRM 管理员', 'crm_admin', 2, 1, '', 0, 1, 'CRM 专属角色', '1', '2024-02-24 10:51:13', '1', '2024-02-24 02:51:32', b'0', 1);
-INSERT INTO `system_role` (`id`, `name`, `code`, `sort`, `data_scope`, `data_scope_dept_ids`, `status`, `type`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (101, '测试账号', 'test', 0, 1, '[]', 0, 2, '123', '', '2021-01-06 13:49:35', '1', '2025-04-03 07:42:30', b'0', 1);
+INSERT INTO `system_role` (`id`, `name`, `code`, `sort`, `data_scope`, `data_scope_dept_ids`, `status`, `type`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (101, '测试账号', 'test', 0, 1, '[]', 0, 2, '123', '', '2021-01-06 13:49:35', '1', '2025-04-30 17:38:28', b'0', 1);
 INSERT INTO `system_role` (`id`, `name`, `code`, `sort`, `data_scope`, `data_scope_dept_ids`, `status`, `type`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (109, '租户管理员', 'tenant_admin', 0, 1, '', 0, 1, '系统自动生成', '1', '2022-02-22 00:56:14', '1', '2022-02-22 00:56:14', b'0', 121);
 INSERT INTO `system_role` (`id`, `name`, `code`, `sort`, `data_scope`, `data_scope_dept_ids`, `status`, `type`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (111, '租户管理员', 'tenant_admin', 0, 1, '', 0, 1, '系统自动生成', '1', '2022-03-07 21:37:58', '1', '2022-03-07 21:37:58', b'0', 122);
 INSERT INTO `system_role` (`id`, `name`, `code`, `sort`, `data_scope`, `data_scope_dept_ids`, `status`, `type`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (155, '测试数据权限', 'test-dp', 3, 2, '[100,102,103,104,105,108]', 0, 2, '', '1', '2025-03-31 14:58:06', '1', '2025-04-17 23:07:44', b'0', 1);
@@ -2629,7 +2627,7 @@ CREATE TABLE `system_role_menu`  (
   `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
   `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
   PRIMARY KEY (`id`) USING BTREE
-) ENGINE = InnoDB AUTO_INCREMENT = 6138 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '角色和菜单关联表';
+) ENGINE = InnoDB AUTO_INCREMENT = 6139 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '角色和菜单关联表';
 
 -- ----------------------------
 -- Records of system_role_menu
@@ -2954,15 +2952,9 @@ INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_t
 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2080, 2, 1150, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2081, 2, 1161, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2082, 2, 1162, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
-INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2083, 2, 1163, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
-INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2084, 2, 1164, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
-INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2085, 2, 1165, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2086, 2, 1166, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2087, 2, 1173, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2088, 2, 1174, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
-INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2089, 2, 1175, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
-INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2090, 2, 1176, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
-INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2091, 2, 1177, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2092, 2, 1178, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2099, 2, 1226, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2100, 2, 1227, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
@@ -3503,6 +3495,7 @@ INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_t
 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6135, 155, 4017, '1', '2025-04-01 13:49:30', '1', '2025-04-01 13:49:30', b'0', 1);
 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6136, 155, 4018, '1', '2025-04-01 13:49:30', '1', '2025-04-01 13:49:30', b'0', 1);
 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6137, 155, 4031, '1', '2025-04-01 13:49:30', '1', '2025-04-01 13:49:30', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6138, 101, 5010, '1', '2025-05-05 17:49:17', '1', '2025-05-05 17:49:17', b'0', 1);
 COMMIT;
 
 -- ----------------------------
@@ -3557,7 +3550,7 @@ CREATE TABLE `system_sms_code`  (
   `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
   PRIMARY KEY (`id`) USING BTREE,
   INDEX `idx_mobile`(`mobile` ASC) USING BTREE COMMENT '手机号'
-) ENGINE = InnoDB AUTO_INCREMENT = 657 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '手机验证码';
+) ENGINE = InnoDB AUTO_INCREMENT = 666 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '手机验证码';
 
 -- ----------------------------
 -- Records of system_sms_code
@@ -3598,7 +3591,7 @@ CREATE TABLE `system_sms_log`  (
   `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
   `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
   PRIMARY KEY (`id`) USING BTREE
-) ENGINE = InnoDB AUTO_INCREMENT = 1281 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '短信日志';
+) ENGINE = InnoDB AUTO_INCREMENT = 1290 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '短信日志';
 
 -- ----------------------------
 -- Records of system_sms_log
@@ -3731,7 +3724,7 @@ CREATE TABLE `system_social_user_bind`  (
   `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
   `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
   PRIMARY KEY (`id`) USING BTREE
-) ENGINE = InnoDB AUTO_INCREMENT = 156 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '社交绑定表';
+) ENGINE = InnoDB AUTO_INCREMENT = 164 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '社交绑定表';
 
 -- ----------------------------
 -- Records of system_social_user_bind
@@ -3901,7 +3894,7 @@ CREATE TABLE `system_users`  (
 -- Records of system_users
 -- ----------------------------
 BEGIN;
-INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1, 'admin', '$2a$04$KljJDa/LK7QfDm0lF5OhuePhlPfjRH3tB2Wu351Uidz.oQGJXevPi', '芋道源码', '管理员', 103, '[1,2]', '11aoteman@126.com', '18818260277', 2, 'http://test.yudao.iocoder.cn/f5660f0f8998e8d89f2d742fedee7cbb7e85ecc505bd33b3cc0f75b6a0395931.png', 0, '0:0:0:0:0:0:0:1', '2025-04-23 12:46:09', 'admin', '2021-01-05 17:03:47', NULL, '2025-04-23 12:46:09', b'0', 1);
+INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1, 'admin', '$2a$04$KljJDa/LK7QfDm0lF5OhuePhlPfjRH3tB2Wu351Uidz.oQGJXevPi', '芋道源码', '管理员', 103, '[1,2]', '11aoteman@126.com', '18818260277', 2, 'http://test.yudao.iocoder.cn/test/20250502/avatar_1746154660449.png', 0, '0:0:0:0:0:0:0:1', '2025-05-10 18:03:15', 'admin', '2021-01-05 17:03:47', NULL, '2025-05-10 18:03:15', b'0', 1);
 INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (100, 'yudao', '$2a$04$h.aaPKgO.odHepnk5PCsWeEwKdojFWdTItxGKfx1r0e1CSeBzsTJ6', '芋道', '不要吓我', 104, '[1]', 'yudao@iocoder.cn', '15601691300', 1, NULL, 0, '0:0:0:0:0:0:0:1', '2025-04-08 09:36:40', '', '2021-01-07 09:07:17', NULL, '2025-04-21 14:23:08', b'0', 1);
 INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (103, 'yuanma', '$2a$04$fUBSmjKCPYAUmnMzOb6qE.eZCGPhHi1JmAKclODbfS/O7fHOl2bH6', '源码', NULL, 106, NULL, 'yuanma@iocoder.cn', '15601701300', 0, NULL, 0, '0:0:0:0:0:0:0:1', '2024-08-11 17:48:12', '', '2021-01-13 23:50:35', NULL, '2025-04-21 14:23:08', b'0', 1);
 INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (104, 'test', '$2a$04$BrwaYn303hjA/6TnXqdGoOLhyHOAA0bVrAFu6.1dJKycqKUnIoRz2', '测试号', NULL, 107, '[1,2]', '111@qq.com', '15601691200', 1, NULL, 0, '0:0:0:0:0:0:0:1', '2025-03-28 20:01:16', '', '2021-01-21 02:13:53', NULL, '2025-04-21 14:23:08', b'0', 1);
@@ -3911,7 +3904,7 @@ INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`,
 INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (110, 'admin110', '$2a$10$mRMIYLDtRHlf6.9ipiqH1.Z.bh/R9dO9d5iHiGYPigi6r5KOoR2Wm', '小王', NULL, NULL, NULL, '', '15601691300', 0, NULL, 0, '0:0:0:0:0:0:0:1', '2024-07-20 22:23:17', '1', '2022-02-22 00:56:14', NULL, '2025-04-21 14:23:08', b'0', 121);
 INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (111, 'test', '$2a$10$mRMIYLDtRHlf6.9ipiqH1.Z.bh/R9dO9d5iHiGYPigi6r5KOoR2Wm', '测试用户', NULL, NULL, '[]', '', '', 0, NULL, 0, '0:0:0:0:0:0:0:1', '2023-12-30 11:42:17', '110', '2022-02-23 13:14:33', NULL, '2025-04-21 14:23:08', b'0', 121);
 INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (112, 'newobject', '$2a$04$dB0z8Q819fJWz0hbaLe6B.VfHCjYgWx6LFfET5lyz3JwcqlyCkQ4C', '新对象', NULL, 100, '[]', '', '15601691235', 1, NULL, 0, '0:0:0:0:0:0:0:1', '2024-03-16 23:11:38', '1', '2022-02-23 19:08:03', NULL, '2025-04-21 14:23:08', b'0', 1);
-INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (113, 'aoteman', '$2a$10$0acJOIk2D25/oC87nyclE..0lzeu9DtQ/n3geP4fkun/zIVRhHJIO', '芋道', NULL, NULL, NULL, '', '15601691300', 0, NULL, 0, '127.0.0.1', '2022-03-19 18:38:51', '1', '2022-03-07 21:37:58', NULL, '2025-04-21 14:23:08', b'0', 122);
+INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (113, 'aoteman', '$2a$10$0acJOIk2D25/oC87nyclE..0lzeu9DtQ/n3geP4fkun/zIVRhHJIO', '芋道1', NULL, NULL, NULL, '', '15601691300', 0, NULL, 0, '127.0.0.1', '2022-03-19 18:38:51', '1', '2022-03-07 21:37:58', '1', '2025-05-05 15:30:53', b'0', 122);
 INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (114, 'hrmgr', '$2a$10$TR4eybBioGRhBmDBWkqWLO6NIh3mzYa8KBKDDB5woiGYFVlRAi.fu', 'hr 小姐姐', NULL, NULL, '[5]', '', '15601691236', 1, NULL, 0, '0:0:0:0:0:0:0:1', '2024-03-24 22:21:05', '1', '2022-03-19 21:50:58', NULL, '2025-04-21 14:23:08', b'0', 1);
 INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (115, 'aotemane', '$2a$04$GcyP0Vyzb2F2Yni5PuIK9ueGxM0tkZGMtDwVRwrNbtMvorzbpNsV2', '阿呆', '11222', 102, '[1,2]', '7648@qq.com', '15601691229', 2, NULL, 0, '', NULL, '1', '2022-04-30 02:55:43', '1', '2025-04-21 14:23:08', b'0', 1);
 INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (117, 'admin123', '$2a$04$sEtimsHu9YCkYY4/oqElHem2Ijc9ld20eYO6lN.g/21NfLUTDLB9W', '测试号02', '1111', 100, '[2]', '', '15601691234', 1, NULL, 0, '0:0:0:0:0:0:0:1', '2024-10-02 10:16:20', '1', '2022-07-09 17:40:26', NULL, '2025-04-21 14:23:08', b'0', 1);

+ 1 - 1
yudao-dependencies/pom.xml

@@ -77,7 +77,7 @@
         <justauth.version>1.16.7</justauth.version>
         <justauth-starter.version>1.4.0</justauth-starter.version>
         <jimureport.version>1.9.4</jimureport.version>
-        <weixin-java.version>4.7.4.B</weixin-java.version>
+        <weixin-java.version>4.7.5.B</weixin-java.version>
         <!-- 专属于 JDK8 安全漏洞升级 -->
         <logback.version>1.2.13</logback.version> <!-- 无法使用 1.3.X 版本,启动会报错 -->
     </properties>

+ 6 - 0
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/date/LocalDateTimeUtils.java

@@ -8,12 +8,16 @@ import cn.hutool.core.util.StrUtil;
 import cn.iocoder.yudao.framework.common.enums.DateIntervalEnum;
 
 import java.time.*;
+import java.time.format.DateTimeFormatter;
 import java.time.format.DateTimeParseException;
 import java.time.temporal.ChronoUnit;
 import java.time.temporal.TemporalAdjusters;
 import java.util.ArrayList;
 import java.util.List;
 
+import static cn.hutool.core.date.DatePattern.UTC_MS_WITH_XXX_OFFSET_PATTERN;
+import static cn.hutool.core.date.DatePattern.createFormatter;
+
 /**
  * 时间工具类,用于 {@link java.time.LocalDateTime}
  *
@@ -26,6 +30,8 @@ public class LocalDateTimeUtils {
      */
     public static LocalDateTime EMPTY = buildTime(1970, 1, 1);
 
+    public static DateTimeFormatter UTC_MS_WITH_XXX_OFFSET_FORMATTER = createFormatter(UTC_MS_WITH_XXX_OFFSET_PATTERN);
+
     /**
      * 解析时间
      *

+ 1 - 1
yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3_vben5_antd/general/views/form.vue.vm

@@ -6,7 +6,7 @@ import { useVbenModal } from '@vben/common-ui';
 import { Tinymce as RichTextarea } from '#/components/tinymce';
 import { ImageUpload, FileUpload } from "#/components/upload";
 import { message, Tabs, Form, Input, Textarea, Select, RadioGroup, Radio, CheckboxGroup, Checkbox, DatePicker, TreeSelect } from 'ant-design-vue';
-import { DICT_TYPE, getDictOptions } from '#/utils/dict';
+import { DICT_TYPE, getDictOptions } from '#/utils';
 #if($table.templateType == 2)## 树表需要导入这些
 import { get${simpleClassName}List } from '#/api/${table.moduleName}/${simpleClassName_strikeCase}';
 import { handleTree } from '@vben/utils'

+ 4 - 5
yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3_vben5_antd/general/views/index.vue.vm

@@ -6,13 +6,12 @@ import { Page, useVbenModal } from '@vben/common-ui';
 import { cloneDeep, formatDateTime } from '@vben/utils';
 import { Button, message,Tabs,Pagination,Form,RangePicker,DatePicker,Select,Input } from 'ant-design-vue';
 import { DictTag } from '#/components/dict-tag';
-import { DICT_TYPE, getDictOptions } from '#/utils/dict';
+import { DICT_TYPE, getDictOptions, getRangePickerDefaultProps } from '#/utils';
 import ${simpleClassName}Form from './modules/form.vue';
 import { Download, Plus, RefreshCw, Search } from '@vben/icons';
 import { ContentWrap } from "#/components/content-wrap";
 import { VxeColumn, VxeTable } from 'vxe-table';
 import { TableToolbar } from '#/components/table-toolbar';
-import { getRangePickerDefaultProps } from '#/utils/date';
 
 ## 特殊:主子表专属逻辑
 #if ( $table.templateType == 11 || $table.templateType == 12 )
@@ -31,7 +30,7 @@ import { get${simpleClassName}List, delete${simpleClassName}, export${simpleClas
 #else## 标准表接口
 import { get${simpleClassName}Page, delete${simpleClassName}, export${simpleClassName} } from '#/api/${table.moduleName}/${simpleClassName_strikeCase}';
 #end
-import { downloadByData } from '#/utils/download';
+import { downloadFileFromBlobPart } from '@vben/utils';
 
 #if ($table.templateType == 12 || $table.templateType == 11) ## 内嵌和erp情况
 /** 子表的列表 */
@@ -162,7 +161,7 @@ async function onExport() {
 try {
   exportLoading.value = true;
   const data = await export${simpleClassName}(queryParams);
-  downloadByData(data, '${table.classComment}.xls');
+  downloadFileFromBlobPart({ fileName: '${table.classComment}.xls', source: data });
 }finally {
   exportLoading.value = false;
 }
@@ -281,7 +280,7 @@ onMounted(async () => {
         </Form.Item>
       </Form>
     </ContentWrap>
-    
+
     <!-- 列表 -->
     <ContentWrap title="${table.classComment}">
       <template #extra>

+ 1 - 1
yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3_vben5_antd/general/views/modules/form_sub_erp.vue.vm

@@ -10,7 +10,7 @@
   import { Tinymce as RichTextarea } from '#/components/tinymce';
   import { ImageUpload, FileUpload } from "#/components/upload";
   import { message, Tabs, Form, Input, Textarea, Select, RadioGroup, Radio, CheckboxGroup, Checkbox, DatePicker, TreeSelect } from 'ant-design-vue';
-  import { DICT_TYPE, getDictOptions } from '#/utils/dict';
+  import { DICT_TYPE, getDictOptions } from '#/utils';
 
   import { computed, ref } from 'vue';
   import { $t } from '#/locales';

+ 1 - 1
yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3_vben5_antd/general/views/modules/form_sub_normal.vue.vm

@@ -10,7 +10,7 @@
   import { message, Tabs, Form, Input, Textarea,Button, Select, RadioGroup, Radio, CheckboxGroup, Checkbox, DatePicker } from 'ant-design-vue';
   import { computed, ref, h, onMounted,watch,nextTick } from 'vue';
   import { $t } from '#/locales';
-  import { DICT_TYPE, getDictOptions } from '#/utils/dict';
+  import { DICT_TYPE, getDictOptions } from '#/utils';
 
 #if ($subTable.subJoinMany) ## 一对多
 import type { VxeTableInstance } from 'vxe-table';

+ 3 - 4
yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3_vben5_antd/general/views/modules/list_sub_erp.vue.vm

@@ -10,7 +10,7 @@
   import type { VxeTableInstance } from 'vxe-table';
 
   import { DictTag } from '#/components/dict-tag';
-  import { DICT_TYPE, getDictOptions } from '#/utils/dict';
+  import { DICT_TYPE, getDictOptions } from '#/utils';
   import { VxeColumn, VxeTable } from 'vxe-table';
   import { reactive,ref, h, nextTick,watch,onMounted } from 'vue';
   import { cloneDeep, formatDateTime } from '@vben/utils';
@@ -22,11 +22,10 @@
     import { Tinymce as RichTextarea } from '#/components/tinymce';
     import { ImageUpload, FileUpload } from "#/components/upload";
     import { message,Button, Tabs,Pagination, Form, Input, Textarea, Select, RadioGroup, Radio, CheckboxGroup, Checkbox,RangePicker, DatePicker, TreeSelect } from 'ant-design-vue';
-    import { DICT_TYPE, getDictOptions } from '#/utils/dict';
+    import { DICT_TYPE, getDictOptions, getRangePickerDefaultProps } from '#/utils';
     import { Plus } from '@vben/icons';
     import { $t } from '#/locales';
     import { TableToolbar } from '#/components/table-toolbar';
-    import { getRangePickerDefaultProps } from '#/utils/date';
 #end
 
 #if ($table.templateType == 11) ## erp
@@ -387,4 +386,4 @@ onMounted(async () => {
       </vxe-table>
     </ContentWrap>
     #end
-</template>
+</template>

+ 2 - 3
yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3_vben5_antd/schema/views/data.ts.vm

@@ -5,10 +5,9 @@ import type { ${simpleClassName}Api } from '#/api/${table.moduleName}/${simpleCl
 import { z } from '#/adapter/form';
 #if(${table.templateType} == 2)## 树表需要导入这些
 import { get${simpleClassName}List } from '#/api/${table.moduleName}/${simpleClassName_strikeCase}';
-import { handleTree } from '#/utils/tree';
+import { handleTree } from '@vben/utils';
 #end
-import { DICT_TYPE, getDictOptions } from '#/utils/dict';
-import { getRangePickerDefaultProps } from '#/utils/date';
+import { DICT_TYPE, getDictOptions, getRangePickerDefaultProps } from '#/utils';
 import { useAccess } from '@vben/access';
 
 const { hasAccessByCodes } = useAccess();

+ 1 - 4
yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3_vben5_antd/schema/views/form.vue.vm

@@ -102,10 +102,7 @@ const [Modal, modalApi] = useVbenModal({
       // 关闭并提示
       await modalApi.close();
       emit('success');
-      message.success({
-        content: $t('ui.actionMessage.operationSuccess'),
-        key: 'action_process_msg',
-      });
+      message.success( $t('ui.actionMessage.operationSuccess') );
     } finally {
       modalApi.unlock();
     }

+ 3 - 6
yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3_vben5_antd/schema/views/index.vue.vm

@@ -24,7 +24,7 @@ import { get${simpleClassName}List, delete${simpleClassName}, export${simpleClas
 #else## 标准表接口
 import { get${simpleClassName}Page, delete${simpleClassName}, export${simpleClassName} } from '#/api/${table.moduleName}/${simpleClassName_strikeCase}';
 #end
-import { downloadByData } from '#/utils/download';
+import { downloadFileFromBlobPart } from '@vben/utils';
 
 import { useGridColumns, useGridFormSchema } from './data';
 
@@ -85,10 +85,7 @@ async function onDelete(row: ${simpleClassName}Api.${simpleClassName}) {
   });
   try {
     await delete${simpleClassName}(row.id as number);
-    message.success({
-      content: $t('ui.actionMessage.deleteSuccess', [row.id]),
-      key: 'action_process_msg',
-    });
+    message.success( $t('ui.actionMessage.deleteSuccess', [row.id]) );
     onRefresh();
   } catch {
     hideLoading();
@@ -98,7 +95,7 @@ async function onDelete(row: ${simpleClassName}Api.${simpleClassName}) {
 /** 导出表格 */
 async function onExport() {
   const data = await export${simpleClassName}(await gridApi.formApi.getValues());
-  downloadByData(data, '${table.classComment}.xls');
+  downloadFileFromBlobPart({ fileName: '${table.classComment}.xls', source: data });
 }
 
 /** 表格操作按钮的回调函数 */

+ 1 - 4
yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3_vben5_antd/schema/views/modules/form_sub_erp.vue.vm

@@ -52,10 +52,7 @@
         // 关闭并提示
         await modalApi.close();
         emit('success');
-        message.success({
-          content: $t('ui.actionMessage.operationSuccess'),
-          key: 'action_process_msg',
-        });
+        message.success( $t('ui.actionMessage.operationSuccess') );
       } finally {
         modalApi.unlock();
       }

+ 2 - 5
yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3_vben5_antd/schema/views/modules/list_sub_erp.vue.vm

@@ -65,10 +65,7 @@ async function onDelete(row: ${simpleClassName}Api.${subSimpleClassName}) {
   });
   try {
     await delete${subSimpleClassName}(row.id as number);
-    message.success({
-      content: $t('ui.actionMessage.deleteSuccess', [row.id]),
-      key: 'action_process_msg',
-    });
+    message.success( $t('ui.actionMessage.deleteSuccess', [row.id]) );
     onRefresh();
   } catch {
     hideLoading();
@@ -181,4 +178,4 @@ const onRefresh = async ()=> {
     #else
       <Grid table-title="${subTable.classComment}列表" />
     #end
-</template>
+</template>

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

@@ -7,6 +7,9 @@ package cn.iocoder.yudao.module.trade.enums;
  */
 public interface DictTypeConstants {
 
+    String BROKERAGE_RECORD_STATUS = "brokerage_record_status"; // 佣金记录状态
+
+    String BROKERAGE_WITHDRAW_TYPE = "brokerage_withdraw_type"; // 佣金提现类型
     String BROKERAGE_WITHDRAW_STATUS = "brokerage_withdraw_status"; // 佣金提现状态
 
 }

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

@@ -36,8 +36,10 @@ public interface ErrorCodeConstants {
     ErrorCode ORDER_UPDATE_ADDRESS_FAIL_STATUS_NOT_DELIVERED = new ErrorCode(1_011_000_031, "交易订单修改收货地址失败,原因:订单不是【待发货】状态");
     ErrorCode ORDER_CREATE_FAIL_EXIST_UNPAID = new ErrorCode(1_011_000_032, "交易订单创建失败,原因:存在未付款订单");
     ErrorCode ORDER_CANCEL_PAID_FAIL = new ErrorCode(1_011_000_033, "交易订单取消支付失败,原因:订单不是【{}】状态");
-    ErrorCode ORDER_PICK_UP_FAIL_NOT_VERIFY_USER = new ErrorCode(1_011_000_034, "交易订单自提失败,原因:你没有核销该门店订单的权限");
-    ErrorCode ORDER_CREATE_FAIL_INSUFFICIENT_USER_POINTS = new ErrorCode(1_011_000_035, "交易订单创建失败,原因:用户积分不足");
+    ErrorCode ORDER_UPDATE_PAID_ORDER_REFUNDED_FAIL_REFUND_NOT_FOUND = new ErrorCode(1_011_000_034, "交易订单更新支付订单退款状态失败,原因:退款单不存在");
+    ErrorCode ORDER_UPDATE_PAID_ORDER_REFUNDED_FAIL_REFUND_STATUS_NOT_SUCCESS = new ErrorCode(1_011_000_035, "交易订单更新支付订单退款状态失败,原因:退款单状态不是【退款成功】");
+    ErrorCode ORDER_PICK_UP_FAIL_NOT_VERIFY_USER = new ErrorCode(1_011_000_036, "交易订单自提失败,原因:你没有核销该门店订单的权限");
+    ErrorCode ORDER_CREATE_FAIL_INSUFFICIENT_USER_POINTS = new ErrorCode(1_011_000_037, "交易订单创建失败,原因:用户积分不足");
 
     // ========== After Sale 模块 1-011-000-100 ==========
     ErrorCode AFTER_SALE_NOT_FOUND = new ErrorCode(1_011_000_100, "售后单不存在");
@@ -51,9 +53,13 @@ public interface ErrorCodeConstants {
     ErrorCode AFTER_SALE_DELIVERY_FAIL_STATUS_NOT_SELLER_AGREE = new ErrorCode(1_011_000_108, "退货失败,售后单状态不处于【待买家退货】");
     ErrorCode AFTER_SALE_CONFIRM_FAIL_STATUS_NOT_BUYER_DELIVERY = new ErrorCode(1_011_000_109, "确认收货失败,售后单状态不处于【待确认收货】");
     ErrorCode AFTER_SALE_REFUND_FAIL_STATUS_NOT_WAIT_REFUND = new ErrorCode(1_011_000_110, "退款失败,售后单状态不是【待退款】");
+    ErrorCode AFTER_SALE_REFUND_FAIL_REFUND_NOT_FOUND = new ErrorCode(1_011_000_111, "退款失败,退款单不存在");
+    ErrorCode AFTER_SALE_REFUND_FAIL_REFUND_NOT_SUCCESS_OR_FAILURE = new ErrorCode(1_011_000_112, "退款失败,退款单未退款");
+    ErrorCode AFTER_SALE_REFUND_FAIL_REFUND_PRICE_NOT_MATCH = new ErrorCode(1_011_000_113, "退款失败,退款金额不匹配");
+    ErrorCode AFTER_SALE_REFUND_FAIL_REFUND_ORDER_ID_ERROR = new ErrorCode(1_011_000_114, "退款失败,退款单不匹配");
     ErrorCode AFTER_SALE_CANCEL_FAIL_STATUS_NOT_APPLY_OR_AGREE_OR_BUYER_DELIVERY =
-            new ErrorCode(1_011_000_111, "取消售后单失败,售后单状态不是【待审核】或【卖家同意】或【商家待收货】");
-    ErrorCode AFTER_SALE_CREATE_FAIL_ORDER_STATUS_COMBINATION_IN_PROGRESS = new ErrorCode(1_011_000_112, "订单拼团中,无法申请售后");
+            new ErrorCode(1_011_000_115, "取消售后单失败,售后单状态不是【待审核】或【卖家同意】或【商家待收货】");
+    ErrorCode AFTER_SALE_CREATE_FAIL_ORDER_STATUS_COMBINATION_IN_PROGRESS = new ErrorCode(1_011_000_116, "订单拼团中,无法申请售后");
 
     // ========== Cart 模块 1-011-002-000 ==========
     ErrorCode CARD_ITEM_NOT_FOUND = new ErrorCode(1_011_002_000, "购物车项不存在");
@@ -101,5 +107,10 @@ public interface ErrorCodeConstants {
     ErrorCode BROKERAGE_WITHDRAW_STATUS_NOT_AUDITING = new ErrorCode(1_011_008_001, "佣金提现记录状态不是审核中");
     ErrorCode BROKERAGE_WITHDRAW_MIN_PRICE = new ErrorCode(1_011_008_002, "提现金额不能低于 {} 元");
     ErrorCode BROKERAGE_WITHDRAW_USER_BALANCE_NOT_ENOUGH = new ErrorCode(1_011_008_003, "您当前最多可提现 {} 元");
+    ErrorCode BROKERAGE_WITHDRAW_UPDATE_STATUS_FAIL_PAY_TRANSFER_ID_ERROR = new ErrorCode(1_011_008_005, "提现单更新转账状态失败,转账单不匹配");
+    ErrorCode BROKERAGE_WITHDRAW_UPDATE_STATUS_FAIL_PAY_TRANSFER_STATUS_NOT_SUCCESS_OR_CLOSED = new ErrorCode(1_011_008_006, "提现单更新转账状态失败,转账单状态不是成功或关闭状态");
+    ErrorCode BROKERAGE_WITHDRAW_UPDATE_STATUS_FAIL_PAY_PRICE_NOT_MATCH = new ErrorCode(1_011_008_007, "提现单更新转账状态失败,转账单金额不匹配");
+    ErrorCode BROKERAGE_WITHDRAW_UPDATE_STATUS_FAIL_PAY_MERCHANT_EXISTS = new ErrorCode(1_011_008_008, "提现单更新转账状态失败,转账单的商户订单不匹配");
+    ErrorCode BROKERAGE_WITHDRAW_UPDATE_STATUS_FAIL_PAY_CHANNEL_NOT_MATCH = new ErrorCode(1_011_008_009, "提现单更新转账状态失败,转账渠道不匹配");
 
 }

+ 3 - 1
yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/aftersale/AfterSaleOperateTypeEnum.java

@@ -19,7 +19,9 @@ public enum AfterSaleOperateTypeEnum {
     MEMBER_DELIVERY(20, "会员填写退货物流信息,快递公司:{deliveryName},快递单号:{logisticsNo}"),
     ADMIN_AGREE_RECEIVE(21, "商家收货"),
     ADMIN_DISAGREE_RECEIVE(22, "商家拒绝收货,原因:{reason}"),
-    ADMIN_REFUND(30, "商家退款"),
+    ADMIN_REFUND(30, "商家发起退款"),
+    SYSTEM_REFUND_SUCCESS(31, "退款成功"),
+    SYSTEM_REFUND_FAIL(32, "退款失败"),
     MEMBER_CANCEL(40, "会员取消退款"),
     ;
 

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

@@ -6,7 +6,6 @@ import lombok.Getter;
 
 import java.util.Arrays;
 
-// TODO 芋艿:提现的打通,在纠结下;
 /**
  * 佣金提现状态枚举
  *

+ 5 - 3
yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/brokerage/BrokerageWithdrawTypeEnum.java

@@ -1,6 +1,7 @@
 package cn.iocoder.yudao.module.trade.enums.brokerage;
 
 import cn.iocoder.yudao.framework.common.core.ArrayValuable;
+import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
 import lombok.AllArgsConstructor;
 import lombok.Getter;
 
@@ -17,9 +18,10 @@ public enum BrokerageWithdrawTypeEnum implements ArrayValuable<Integer> {
 
     WALLET(1, "钱包"),
     BANK(2, "银行卡"),
-    WECHAT(3, "微信"), // 手动打款
-    ALIPAY(4, "支付宝"),
+    WECHAT_QR(3, "微信收款码"), // 手动打款
+    ALIPAY_QR(4, "支付宝收款码"), // 手动打款
     WECHAT_API(5, "微信零钱"), // 自动打款,通过微信转账 API
+    ALIPAY_API(6, "支付宝余额"), // 自动打款,通过支付宝转账 API
     ;
 
     public static final Integer[] ARRAYS = Arrays.stream(values()).map(BrokerageWithdrawTypeEnum::getType).toArray(Integer[]::new);
@@ -45,7 +47,7 @@ public enum BrokerageWithdrawTypeEnum implements ArrayValuable<Integer> {
      * @return 是否
      */
     public static boolean isApi(Integer type) {
-        return WECHAT_API.getType().equals(type);
+        return ObjectUtils.equalsAny(type, WALLET.getType(), ALIPAY_API.getType(), WECHAT_API.getType());
     }
 
 }

+ 17 - 4
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/aftersale/AfterSaleController.java

@@ -1,6 +1,7 @@
 package cn.iocoder.yudao.module.trade.controller.admin.aftersale;
 
 import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.StrUtil;
 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.module.member.api.user.MemberUserApi;
@@ -15,10 +16,12 @@ import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO;
 import cn.iocoder.yudao.module.trade.service.aftersale.AfterSaleLogService;
 import cn.iocoder.yudao.module.trade.service.aftersale.AfterSaleService;
 import cn.iocoder.yudao.module.trade.service.order.TradeOrderQueryService;
+import cn.iocoder.yudao.module.trade.service.order.TradeOrderUpdateService;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.Parameter;
 import io.swagger.v3.oas.annotations.tags.Tag;
 import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.*;
@@ -46,6 +49,8 @@ public class AfterSaleController {
     @Resource
     private TradeOrderQueryService tradeOrderQueryService;
     @Resource
+    private TradeOrderUpdateService tradeOrderUpdateService;
+    @Resource
     private AfterSaleLogService afterSaleLogService;
     @Resource
     private MemberUserApi memberUserApi;
@@ -133,11 +138,19 @@ public class AfterSaleController {
 
     @PostMapping("/update-refunded")
     @Operation(summary = "更新售后订单为已退款") // 由 pay-module 支付服务,进行回调,可见 PayNotifyJob
-    @PermitAll // 无需登录,安全由 PayDemoOrderService 内部校验实现
-    public CommonResult<Boolean> updateAfterRefund(@RequestBody PayRefundNotifyReqDTO notifyReqDTO) {
-        // 目前业务逻辑,不需要做任何事情
-        // 当然,退款会有小概率会失败的情况,可以监控失败状态,进行告警
+    @PermitAll // 无需登录,安全由 AfterSaleService 内部校验实现
+    public CommonResult<Boolean> updateAfterSaleRefunded(@RequestBody PayRefundNotifyReqDTO notifyReqDTO) {
         log.info("[updateAfterRefund][notifyReqDTO({})]", notifyReqDTO);
+        if (StrUtil.startWithAny(notifyReqDTO.getMerchantRefundId(), "order-")) {
+            tradeOrderUpdateService.updatePaidOrderRefunded(
+                    Long.parseLong(notifyReqDTO.getMerchantRefundId()),
+                    notifyReqDTO.getPayRefundId());
+        } else {
+            afterSaleService.updateAfterSaleRefunded(
+                    Long.parseLong(notifyReqDTO.getMerchantRefundId()),
+                    Long.parseLong(notifyReqDTO.getMerchantOrderId()),
+                    notifyReqDTO.getPayRefundId());
+        }
         return success(true);
     }
 

+ 3 - 4
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/BrokerageWithdrawController.java

@@ -82,10 +82,9 @@ public class BrokerageWithdrawController {
         return success(BrokerageWithdrawConvert.INSTANCE.convertPage(pageResult, userMap));
     }
 
-    // TODO @luchi:update-transferred,url 改成这个。和 update-paid 、update-refunded 保持一致
-    @PostMapping("/update-transfer")
-    @Operation(summary = "更新转账订单为转账成功") // 由 pay-module 支付服务,进行回调,可见 PayNotifyJob
-    @PermitAll // 无需登录,安全由 PayDemoOrderService 内部校验实现
+    @PostMapping("/update-transferred")
+    @Operation(summary = "更新佣金提现的转账结果") // 由 pay-module 支付服务,进行回调,可见 PayNotifyJob
+    @PermitAll // 无需登录,安全由 BrokerageWithdrawService 内部校验实现
     public CommonResult<Boolean> updateBrokerageWithdrawTransferred(@RequestBody PayTransferNotifyReqDTO notifyReqDTO) {
         log.info("[updateAfterRefund][notifyReqDTO({})]", notifyReqDTO);
         brokerageWithdrawService.updateBrokerageWithdrawTransferred(

+ 0 - 68
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/vo/withdraw/BrokerageWithdrawBaseVO.java

@@ -1,68 +0,0 @@
-package cn.iocoder.yudao.module.trade.controller.admin.brokerage.vo.withdraw;
-
-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 BrokerageWithdrawBaseVO {
-
-    @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "11436")
-    @NotNull(message = "用户编号不能为空")
-    private Long userId;
-
-    @Schema(description = "提现金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "18781")
-    @NotNull(message = "提现金额不能为空")
-    private Integer price;
-
-    @Schema(description = "提现手续费", requiredMode = Schema.RequiredMode.REQUIRED, example = "11417")
-    @NotNull(message = "提现手续费不能为空")
-    private Integer feePrice;
-
-    @Schema(description = "当前总佣金", requiredMode = Schema.RequiredMode.REQUIRED, example = "18576")
-    @NotNull(message = "当前总佣金不能为空")
-    private Integer totalPrice;
-
-    @Schema(description = "提现类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
-    @NotNull(message = "提现类型不能为空")
-    private Integer type;
-
-    @Schema(description = "真实姓名", example = "赵六")
-    private String name;
-
-    @Schema(description = "账号", example = "88677912132")
-    private String accountNo;
-
-    @Schema(description = "银行名称", example = "1")
-    private String bankName;
-
-    @Schema(description = "开户地址", example = "海淀支行")
-    private String bankAddress;
-
-    @Schema(description = "收款码", example = "https://www.iocoder.cn")
-    private String accountQrCodeUrl;
-
-    @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
-    @NotNull(message = "状态不能为空")
-    private Integer status;
-
-    @Schema(description = "审核驳回原因", example = "不对")
-    private String auditReason;
-
-    @Schema(description = "审核时间")
-    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
-    private LocalDateTime auditTime;
-
-    @Schema(description = "备注", example = "随便")
-    private String remark;
-
-}

+ 2 - 2
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/vo/withdraw/BrokerageWithdrawPageReqVO.java

@@ -28,10 +28,10 @@ public class BrokerageWithdrawPageReqVO extends PageParam {
     private Integer type;
 
     @Schema(description = "真实姓名", example = "赵六")
-    private String name;
+    private String userName;
 
     @Schema(description = "账号", example = "886779132")
-    private String accountNo;
+    private String userAccount;
 
     @Schema(description = "银行名称", example = "1")
     private String bankName;

+ 51 - 7
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/vo/withdraw/BrokerageWithdrawRespVO.java

@@ -2,24 +2,68 @@ package cn.iocoder.yudao.module.trade.controller.admin.brokerage.vo.withdraw;
 
 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 BrokerageWithdrawRespVO extends BrokerageWithdrawBaseVO {
+public class BrokerageWithdrawRespVO {
 
     @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "7161")
     private Long id;
 
-    @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
-    private LocalDateTime createTime;
+    @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "11436")
+    private Long userId;
 
     @Schema(description = "用户昵称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋艿")
     private String userNickname;
 
+    @Schema(description = "提现金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "18781")
+    private Integer price;
+
+    @Schema(description = "提现手续费", requiredMode = Schema.RequiredMode.REQUIRED, example = "11417")
+    private Integer feePrice;
+
+    @Schema(description = "当前总佣金", requiredMode = Schema.RequiredMode.REQUIRED, example = "18576")
+    private Integer totalPrice;
+
+    @Schema(description = "提现类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    private Integer type;
+
+    @Schema(description = "真实姓名", example = "赵六")
+    private String userName;
+
+    @Schema(description = "收款账号", example = "88677912132")
+    private String userAccount;
+
+    @Schema(description = "银行名称", example = "1")
+    private String bankName;
+
+    @Schema(description = "开户地址", example = "海淀支行")
+    private String bankAddress;
+
+    @Schema(description = "收款码", example = "https://www.iocoder.cn")
+    private String qrCodeUrl;
+
+    @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    private Integer status;
+
+    @Schema(description = "审核驳回原因", example = "不对")
+    private String auditReason;
+
+    @Schema(description = "审核时间")
+    private LocalDateTime auditTime;
+
+    @Schema(description = "备注", example = "随便")
+    private String remark;
+
+    @Schema(description = "转账单编号", example = "1024")
+    private Long payTransferId;
+
+    @Schema(description = "转账错误提示", example = "余额不足")
+    private String transferErrorMsg;
+
+    @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
+    private LocalDateTime createTime;
+
 }

+ 8 - 4
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/brokerage/AppBrokerageRecordController.java

@@ -3,11 +3,13 @@ package cn.iocoder.yudao.module.trade.controller.app.brokerage;
 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.framework.dict.core.DictFrameworkUtils;
+import cn.iocoder.yudao.module.trade.controller.admin.brokerage.vo.record.BrokerageRecordPageReqVO;
 import cn.iocoder.yudao.module.trade.controller.app.brokerage.vo.record.AppBrokerageProductPriceRespVO;
 import cn.iocoder.yudao.module.trade.controller.app.brokerage.vo.record.AppBrokerageRecordPageReqVO;
 import cn.iocoder.yudao.module.trade.controller.app.brokerage.vo.record.AppBrokerageRecordRespVO;
-import cn.iocoder.yudao.module.trade.convert.brokerage.BrokerageRecordConvert;
 import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.BrokerageRecordDO;
+import cn.iocoder.yudao.module.trade.enums.DictTypeConstants;
 import cn.iocoder.yudao.module.trade.service.brokerage.BrokerageRecordService;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.tags.Tag;
@@ -30,6 +32,7 @@ import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.getLogi
 @Validated
 @Slf4j
 public class AppBrokerageRecordController {
+
     @Resource
     private BrokerageRecordService brokerageRecordService;
 
@@ -37,8 +40,9 @@ public class AppBrokerageRecordController {
     @Operation(summary = "获得分销记录分页")
     public CommonResult<PageResult<AppBrokerageRecordRespVO>> getBrokerageRecordPage(@Valid AppBrokerageRecordPageReqVO pageReqVO) {
         PageResult<BrokerageRecordDO> pageResult = brokerageRecordService.getBrokerageRecordPage(
-                BrokerageRecordConvert.INSTANCE.convert(pageReqVO, getLoginUserId()));
-        return success(BeanUtils.toBean(pageResult, AppBrokerageRecordRespVO.class));
+                BeanUtils.toBean(pageReqVO, BrokerageRecordPageReqVO.class).setUserId(getLoginUserId()));
+        return success(BeanUtils.toBean(pageResult, AppBrokerageRecordRespVO.class, recordVO ->
+                recordVO.setStatusName(DictFrameworkUtils.getDictDataLabel(DictTypeConstants.BROKERAGE_RECORD_STATUS, recordVO.getStatus()))));
     }
 
     @GetMapping("/get-product-brokerage-price")
@@ -47,4 +51,4 @@ public class AppBrokerageRecordController {
         return success(brokerageRecordService.calculateProductBrokeragePrice(getLoginUserId(), spuId));
     }
 
-}
+}

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

@@ -1,21 +1,31 @@
 package cn.iocoder.yudao.module.trade.controller.app.brokerage;
 
+import cn.hutool.core.util.ObjUtil;
 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.framework.dict.core.DictFrameworkUtils;
+import cn.iocoder.yudao.module.pay.api.transfer.PayTransferApi;
+import cn.iocoder.yudao.module.pay.api.transfer.dto.PayTransferRespDTO;
+import cn.iocoder.yudao.module.trade.controller.admin.brokerage.vo.withdraw.BrokerageWithdrawPageReqVO;
 import cn.iocoder.yudao.module.trade.controller.app.brokerage.vo.withdraw.AppBrokerageWithdrawCreateReqVO;
 import cn.iocoder.yudao.module.trade.controller.app.brokerage.vo.withdraw.AppBrokerageWithdrawPageReqVO;
 import cn.iocoder.yudao.module.trade.controller.app.brokerage.vo.withdraw.AppBrokerageWithdrawRespVO;
-import cn.iocoder.yudao.module.trade.convert.brokerage.BrokerageWithdrawConvert;
 import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.BrokerageWithdrawDO;
+import cn.iocoder.yudao.module.trade.enums.DictTypeConstants;
+import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageWithdrawStatusEnum;
+import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageWithdrawTypeEnum;
 import cn.iocoder.yudao.module.trade.service.brokerage.BrokerageWithdrawService;
 import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
 import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import jakarta.validation.Valid;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.*;
 
-import javax.annotation.Resource;
-import javax.validation.Valid;
+import java.util.Objects;
 
 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
 import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.getLoginUserId;
@@ -30,12 +40,39 @@ public class AppBrokerageWithdrawController {
     @Resource
     private BrokerageWithdrawService brokerageWithdrawService;
 
+    @Resource
+    private PayTransferApi payTransferApi;
+
     @GetMapping("/page")
     @Operation(summary = "获得分销提现分页")
     public CommonResult<PageResult<AppBrokerageWithdrawRespVO>> getBrokerageWithdrawPage(AppBrokerageWithdrawPageReqVO pageReqVO) {
         PageResult<BrokerageWithdrawDO> pageResult = brokerageWithdrawService.getBrokerageWithdrawPage(
-                BrokerageWithdrawConvert.INSTANCE.convert(pageReqVO, getLoginUserId()));
-        return success(BrokerageWithdrawConvert.INSTANCE.convertPage03(pageResult));
+                BeanUtils.toBean(pageReqVO, BrokerageWithdrawPageReqVO.class).setUserId(getLoginUserId()));
+        return success(BeanUtils.toBean(pageResult, AppBrokerageWithdrawRespVO.class, withdrawVO ->
+                withdrawVO.setTypeName(DictFrameworkUtils.getDictDataLabel(DictTypeConstants.BROKERAGE_WITHDRAW_TYPE, withdrawVO.getType()))
+                        .setStatusName(DictFrameworkUtils.getDictDataLabel(DictTypeConstants.BROKERAGE_WITHDRAW_STATUS, withdrawVO.getStatus()))));
+    }
+
+    @GetMapping("/get")
+    @Operation(summary = "获得佣金提现")
+    @Parameter(name = "id", description = "编号", required = true, example = "1024")
+    public CommonResult<AppBrokerageWithdrawRespVO> getBrokerageWithdraw(@RequestParam("id") Long id) {
+        BrokerageWithdrawDO withdraw = brokerageWithdrawService.getBrokerageWithdraw(id);
+        if (withdraw == null || ObjUtil.notEqual(withdraw.getUserId(), getLoginUserId())) {
+            return success(null);
+        }
+        // 审核中(转账中),并且是微信转账,需要返回 mchId 用于确认收款
+        AppBrokerageWithdrawRespVO withdrawVO = BeanUtils.toBean(withdraw, AppBrokerageWithdrawRespVO.class);
+        if (Objects.equals(withdraw.getStatus(), BrokerageWithdrawStatusEnum.AUDIT_SUCCESS.getStatus())
+                && Objects.equals(withdraw.getType(), BrokerageWithdrawTypeEnum.WECHAT_API.getType())
+                && withdraw.getPayTransferId() != null) {
+            PayTransferRespDTO transfer = payTransferApi.getTransfer(withdraw.getPayTransferId());
+            if (transfer != null) {
+                withdrawVO.setTransferChannelPackageInfo(transfer.getChannelPackageInfo())
+                        .setTransferChannelMchId(transfer.getChannelMchId());
+            }
+        }
+        return success(withdrawVO);
     }
 
     @PostMapping("/create")

+ 2 - 2
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/brokerage/vo/record/AppBrokerageRecordPageReqVO.java

@@ -20,11 +20,11 @@ public class AppBrokerageRecordPageReqVO extends PageParam {
     @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
     private LocalDateTime[] createTime;
 
-    @Schema(description = "业务类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @Schema(description = "业务类型", example = "1")
     @InEnum(value = BrokerageRecordBizTypeEnum.class, message = "业务类型必须是 {value}")
     private Integer bizType;
 
-    @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @Schema(description = "状态", example = "1")
     @InEnum(value = BrokerageRecordStatusEnum.class, message = "状态必须是 {value}")
     private Integer status;
 

+ 3 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/brokerage/vo/record/AppBrokerageRecordRespVO.java

@@ -24,6 +24,9 @@ public class AppBrokerageRecordRespVO {
     @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
     private Integer status;
 
+    @Schema(description = "状态名", requiredMode = Schema.RequiredMode.REQUIRED, example = "待结算")
+    private String statusName;
+
     @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
     private LocalDateTime createTime;
 

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

@@ -2,8 +2,10 @@ package cn.iocoder.yudao.module.trade.controller.app.brokerage.vo.withdraw;
 
 import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils;
 import cn.iocoder.yudao.framework.common.validation.InEnum;
+import cn.iocoder.yudao.module.pay.enums.PayChannelEnum;
 import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageWithdrawTypeEnum;
 import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.Min;
 import lombok.Data;
 import org.hibernate.validator.constraints.URL;
 
@@ -22,42 +24,49 @@ public class AppBrokerageWithdrawCreateReqVO {
 
     @Schema(description = "提现金额,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "1000")
     @PositiveOrZero(message = "提现金额不能小于 0")
+    @Min(value = 30, message = "微信提现金额不能小于 0.3", groups = {WechatApi.class})
     @NotNull(message = "提现金额不能为空")
     private Integer price;
 
-    // ========== 银行卡、微信、支付宝 提现相关字段 ==========
-
     @Schema(description = "提现账号", requiredMode = Schema.RequiredMode.REQUIRED, example = "123456789")
-    @NotBlank(message = "提现账号不能为空", groups = {Bank.class, Wechat.class, Alipay.class})
-    private String accountNo;
+    @NotBlank(message = "提现账号不能为空", groups = {Bank.class, WechatApi.class, AlipayApi.class})
+    private String userAccount;
 
-    // ========== 微信、支付宝 提现相关字段 ==========
+    @Schema(description = "提现姓名", example = "张三")
+    @NotBlank(message = "提现姓名不能为空", groups = {Bank.class, WechatApi.class, AlipayApi.class})
+    private String userName;
 
     @Schema(description = "收款码的图片", example = "https://www.iocoder.cn/1.png")
-    @URL(message = "收款码的图片,必须是一个 URL")
-    private String accountQrCodeUrl;
-
-    // ========== 银行卡 提现相关字段 ==========
+    @URL(message = "收款码的图片,必须是一个 URL", groups = {WechatQR.class, AlipayQR.class})
+    private String qrCodeUrl;
 
-    @Schema(description = "持卡人姓名", example = "张三")
-    @NotBlank(message = "持卡人姓名不能为空", groups = {Bank.class})
-    private String name;
     @Schema(description = "提现银行", example = "1")
     @NotNull(message = "提现银行不能为空", groups = {Bank.class})
     private String bankName;
     @Schema(description = "开户地址", example = "海淀支行")
     private String bankAddress;
 
+    @Schema(description = "转账渠道", example = "wx_lite")
+    @NotNull(message = "转账渠道不能为空", groups = {WechatApi.class})
+    @InEnum(PayChannelEnum.class)
+    private String transferChannelCode;
+
     public interface Wallet {
     }
 
     public interface Bank {
     }
 
-    public interface Wechat {
+    public interface WechatQR {
+    }
+
+    public interface WechatApi {
+    }
+
+    public interface AlipayQR {
     }
 
-    public interface Alipay {
+    public interface AlipayApi {
     }
 
     public void validate(Validator validator) {
@@ -65,10 +74,14 @@ public class AppBrokerageWithdrawCreateReqVO {
             ValidationUtils.validate(validator, this, Wallet.class);
         } else if (BrokerageWithdrawTypeEnum.BANK.getType().equals(type)) {
             ValidationUtils.validate(validator, this, Bank.class);
-        } else if (BrokerageWithdrawTypeEnum.WECHAT.getType().equals(type)) {
-            ValidationUtils.validate(validator, this, Wechat.class);
-        } else if (BrokerageWithdrawTypeEnum.ALIPAY.getType().equals(type)) {
-            ValidationUtils.validate(validator, this, Alipay.class);
+        } else if (BrokerageWithdrawTypeEnum.WECHAT_QR.getType().equals(type)) {
+            ValidationUtils.validate(validator, this, WechatQR.class);
+        } else if (BrokerageWithdrawTypeEnum.WECHAT_API.getType().equals(type)) {
+            ValidationUtils.validate(validator, this, WechatApi.class);
+        } else if (BrokerageWithdrawTypeEnum.ALIPAY_QR.getType().equals(type)) {
+            ValidationUtils.validate(validator, this, AlipayQR.class);
+        } else if (BrokerageWithdrawTypeEnum.ALIPAY_API.getType().equals(type)) {
+            ValidationUtils.validate(validator, this, AlipayApi.class);
         }
     }
 

+ 9 - 9
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/brokerage/vo/withdraw/AppBrokerageWithdrawPageReqVO.java

@@ -1,22 +1,22 @@
 package cn.iocoder.yudao.module.trade.controller.app.brokerage.vo.withdraw;
 
 import cn.iocoder.yudao.framework.common.pojo.PageParam;
-import cn.iocoder.yudao.framework.common.validation.InEnum;
-import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageWithdrawStatusEnum;
-import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageWithdrawTypeEnum;
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
+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 = "应用 App - 分销提现分页 Request VO")
 @Data
 public class AppBrokerageWithdrawPageReqVO extends PageParam {
 
-    @Schema(description = "类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
-    @InEnum(value = BrokerageWithdrawTypeEnum.class, message = "类型必须是 {value}")
-    private Integer type;
+    @Schema(description = "创建时间")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    private LocalDateTime[] createTime;
+
 
-    @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
-    @InEnum(value = BrokerageWithdrawStatusEnum.class, message = "状态必须是 {value}")
-    private Integer status;
 
 }

+ 17 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/brokerage/vo/withdraw/AppBrokerageWithdrawRespVO.java

@@ -12,6 +12,12 @@ public class AppBrokerageWithdrawRespVO {
     @Schema(description = "提现编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
     private Long id;
 
+    @Schema(description = "提现类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    private Integer type;
+
+    @Schema(description = "提现类型名", requiredMode = Schema.RequiredMode.REQUIRED, example = "微信")
+    private String typeName;
+
     @Schema(description = "提现状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
     private Integer status;
 
@@ -24,4 +30,15 @@ public class AppBrokerageWithdrawRespVO {
     @Schema(description = "提现时间", requiredMode = Schema.RequiredMode.REQUIRED)
     private LocalDateTime createTime;
 
+    // ========== 微信转账专属 ==========
+
+    @Schema(description = "转账单编号", example = "1024")
+    private Long payTransferId;
+
+    @Schema(description = "渠道 package 信息")
+    private String transferChannelPackageInfo;
+
+    @Schema(description = "渠道商户号")
+    private String transferChannelMchId;
+
 }

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

@@ -1,15 +1,11 @@
 package cn.iocoder.yudao.module.trade.convert.brokerage;
 
-import cn.hutool.core.math.Money;
 import cn.hutool.core.util.ObjectUtil;
 import cn.hutool.core.util.StrUtil;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.common.util.number.MoneyUtils;
 import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
-import cn.iocoder.yudao.module.trade.controller.admin.brokerage.vo.record.BrokerageRecordPageReqVO;
 import cn.iocoder.yudao.module.trade.controller.admin.brokerage.vo.record.BrokerageRecordRespVO;
-import cn.iocoder.yudao.module.trade.controller.app.brokerage.vo.record.AppBrokerageRecordPageReqVO;
-import cn.iocoder.yudao.module.trade.controller.app.brokerage.vo.record.AppBrokerageRecordRespVO;
 import cn.iocoder.yudao.module.trade.controller.app.brokerage.vo.user.AppBrokerageUserRankByPriceRespVO;
 import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.BrokerageRecordDO;
 import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.BrokerageUserDO;
@@ -68,8 +64,6 @@ public interface BrokerageRecordConvert {
         return result;
     }
 
-    BrokerageRecordPageReqVO convert(AppBrokerageRecordPageReqVO pageReqVO, Long userId);
-
     default PageResult<AppBrokerageUserRankByPriceRespVO> convertPage03(PageResult<AppBrokerageUserRankByPriceRespVO> pageResult, Map<Long, MemberUserRespDTO> userMap) {
         for (AppBrokerageUserRankByPriceRespVO vo : pageResult.getList()) {
             copyTo(userMap.get(vo.getId()), vo);

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

@@ -1,15 +1,9 @@
 package cn.iocoder.yudao.module.trade.convert.brokerage;
 
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.framework.dict.core.DictFrameworkUtils;
 import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
-import cn.iocoder.yudao.module.trade.controller.admin.brokerage.vo.withdraw.BrokerageWithdrawPageReqVO;
 import cn.iocoder.yudao.module.trade.controller.admin.brokerage.vo.withdraw.BrokerageWithdrawRespVO;
-import cn.iocoder.yudao.module.trade.controller.app.brokerage.vo.withdraw.AppBrokerageWithdrawCreateReqVO;
-import cn.iocoder.yudao.module.trade.controller.app.brokerage.vo.withdraw.AppBrokerageWithdrawPageReqVO;
-import cn.iocoder.yudao.module.trade.controller.app.brokerage.vo.withdraw.AppBrokerageWithdrawRespVO;
 import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.BrokerageWithdrawDO;
-import cn.iocoder.yudao.module.trade.enums.DictTypeConstants;
 import org.mapstruct.Mapper;
 import org.mapstruct.factory.Mappers;
 
@@ -27,8 +21,6 @@ public interface BrokerageWithdrawConvert {
 
     BrokerageWithdrawConvert INSTANCE = Mappers.getMapper(BrokerageWithdrawConvert.class);
 
-    BrokerageWithdrawDO convert(AppBrokerageWithdrawCreateReqVO createReqVO, Long userId, Integer feePrice);
-
     BrokerageWithdrawRespVO convert(BrokerageWithdrawDO bean);
 
     List<BrokerageWithdrawRespVO> convertList(List<BrokerageWithdrawDO> list);
@@ -43,15 +35,4 @@ public interface BrokerageWithdrawConvert {
         return result;
     }
 
-    PageResult<AppBrokerageWithdrawRespVO> convertPage02(PageResult<BrokerageWithdrawDO> pageResult);
-
-    default PageResult<AppBrokerageWithdrawRespVO> convertPage03(PageResult<BrokerageWithdrawDO> pageResult) {
-        PageResult<AppBrokerageWithdrawRespVO> result = convertPage02(pageResult);
-        for (AppBrokerageWithdrawRespVO vo : result.getList()) {
-            vo.setStatusName(DictFrameworkUtils.getDictDataLabel(DictTypeConstants.BROKERAGE_WITHDRAW_STATUS, vo.getStatus()));
-        }
-        return result;
-    }
-
-    BrokerageWithdrawPageReqVO convert(AppBrokerageWithdrawPageReqVO pageReqVO, Long userId);
 }

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

@@ -57,13 +57,25 @@ public class BrokerageWithdrawDO extends BaseDO {
     private Integer type;
 
     /**
-     * 真实姓名
+     * 提现姓名
+     * 1. {@link BrokerageWithdrawTypeEnum#BANK}:银行开户人
+     * 2. {@link BrokerageWithdrawTypeEnum#WECHAT_API}:微信真名
+     * 3. {@link BrokerageWithdrawTypeEnum#ALIPAY_API}:支付宝真名
      */
-    private String name;
+    private String userName;
     /**
-     * 账号
+     * 提现账号
+     * 1. {@link BrokerageWithdrawTypeEnum#BANK}:银行账号
+     * 2. {@link BrokerageWithdrawTypeEnum#WECHAT_API}:微信 openid
+     * 3. {@link BrokerageWithdrawTypeEnum#ALIPAY_API}:支付宝账号
      */
-    private String accountNo;
+    private String userAccount;
+
+    /**
+     * 收款码
+     */
+    private String qrCodeUrl;
+
     /**
      * 银行名称
      */
@@ -72,10 +84,7 @@ public class BrokerageWithdrawDO extends BaseDO {
      * 开户地址
      */
     private String bankAddress;
-    /**
-     * 收款码
-     */
-    private String accountQrCodeUrl;
+
     /**
      * 状态
      * <p>
@@ -95,4 +104,27 @@ public class BrokerageWithdrawDO extends BaseDO {
      */
     private String remark;
 
+    // ========== 转账相关字段 ==========
+
+    /**
+     * 转账单编号
+     *
+     * 关联 {@link cn.iocoder.yudao.module.pay.api.transfer.dto.PayTransferRespDTO#getId()}
+     */
+    private Long payTransferId;
+    /**
+     * 转账渠道
+     *
+     * 枚举 {@link cn.iocoder.yudao.module.pay.enums.PayChannelEnum}
+     */
+    private String transferChannelCode;
+    /**
+     * 转账成功时间
+     */
+    private LocalDateTime transferTime;
+    /**
+     * 转账错误提示
+     */
+    private String transferErrorMsg;
+
 }

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

@@ -27,18 +27,18 @@ public interface BrokerageWithdrawMapper extends BaseMapperX<BrokerageWithdrawDO
         return selectPage(reqVO, new LambdaQueryWrapperX<BrokerageWithdrawDO>()
                 .eqIfPresent(BrokerageWithdrawDO::getUserId, reqVO.getUserId())
                 .eqIfPresent(BrokerageWithdrawDO::getType, reqVO.getType())
-                .likeIfPresent(BrokerageWithdrawDO::getName, reqVO.getName())
-                .eqIfPresent(BrokerageWithdrawDO::getAccountNo, reqVO.getAccountNo())
+                .likeIfPresent(BrokerageWithdrawDO::getUserName, reqVO.getUserName())
+                .likeIfPresent(BrokerageWithdrawDO::getUserAccount, reqVO.getUserAccount())
                 .likeIfPresent(BrokerageWithdrawDO::getBankName, reqVO.getBankName())
                 .eqIfPresent(BrokerageWithdrawDO::getStatus, reqVO.getStatus())
                 .betweenIfPresent(BrokerageWithdrawDO::getCreateTime, reqVO.getCreateTime())
-                .orderByAsc(BrokerageWithdrawDO::getStatus).orderByDesc(BrokerageWithdrawDO::getId));
+                .orderByDesc(BrokerageWithdrawDO::getId));
     }
 
-    default int updateByIdAndStatus(Long id, Integer status, BrokerageWithdrawDO updateObj) {
+    default int updateByIdAndStatus(Long id, Integer whereStatus, BrokerageWithdrawDO updateObj) {
         return update(updateObj, new LambdaUpdateWrapper<BrokerageWithdrawDO>()
                 .eq(BrokerageWithdrawDO::getId, id)
-                .eq(BrokerageWithdrawDO::getStatus, status));
+                .eq(BrokerageWithdrawDO::getStatus, whereStatus));
     }
 
     default List<BrokerageWithdrawSummaryRespBO> selectCountAndSumPriceByUserIdAndStatus(Collection<Long> userIds,

+ 14 - 2
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/framework/aftersale/core/aop/AfterSaleLogAspect.java

@@ -1,18 +1,20 @@
 package cn.iocoder.yudao.module.trade.framework.aftersale.core.aop;
 
+import cn.hutool.core.util.ObjUtil;
 import cn.hutool.core.util.ObjectUtil;
 import cn.hutool.core.util.StrUtil;
 import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
 import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderLogDO;
+import cn.iocoder.yudao.module.trade.enums.aftersale.AfterSaleOperateTypeEnum;
 import cn.iocoder.yudao.module.trade.framework.aftersale.core.annotations.AfterSaleLog;
 import cn.iocoder.yudao.module.trade.service.aftersale.AfterSaleLogService;
 import cn.iocoder.yudao.module.trade.service.aftersale.bo.AfterSaleLogCreateReqBO;
+import jakarta.annotation.Resource;
 import lombok.extern.slf4j.Slf4j;
 import org.aspectj.lang.JoinPoint;
 import org.aspectj.lang.annotation.AfterReturning;
 import org.aspectj.lang.annotation.Aspect;
 
-import javax.annotation.Resource;
 import java.util.Map;
 
 import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
@@ -50,6 +52,10 @@ public class AfterSaleLogAspect {
      * 操作后的状态
      */
     private static final ThreadLocal<Integer> AFTER_STATUS = new ThreadLocal<>();
+    /**
+     * 操作类型(仅“动态场景”需要使用)
+     */
+    private static final ThreadLocal<AfterSaleOperateTypeEnum> OPERATE_TYPE = new ThreadLocal<>();
     /**
      * 拓展参数 Map,用于格式化操作内容
      */
@@ -69,6 +75,7 @@ public class AfterSaleLogAspect {
             if (afterSaleId == null) { // 如果未设置,只有注解,说明不需要记录日志
                 return;
             }
+            AfterSaleOperateTypeEnum operateType = ObjUtil.defaultIfNull(OPERATE_TYPE.get(), afterSaleLog.operateType());
             Integer beforeStatus = BEFORE_STATUS.get();
             Integer afterStatus = AFTER_STATUS.get();
             Map<String, Object> exts = ObjectUtil.defaultIfNull(EXTS.get(), emptyMap());
@@ -78,7 +85,7 @@ public class AfterSaleLogAspect {
             AfterSaleLogCreateReqBO createBO = new AfterSaleLogCreateReqBO()
                     .setUserId(userId).setUserType(userType)
                     .setAfterSaleId(afterSaleId).setBeforeStatus(beforeStatus).setAfterStatus(afterStatus)
-                    .setOperateType(afterSaleLog.operateType().getType()).setContent(content);
+                    .setOperateType(operateType.getType()).setContent(content);
             afterSaleLogService.createAfterSaleLog(createBO);
         } catch (Exception exception) {
             log.error("[doAfterReturning][afterSaleLog({}) 日志记录错误]", toJsonString(afterSaleLog), exception);
@@ -116,6 +123,10 @@ public class AfterSaleLogAspect {
         EXTS.set(exts);
     }
 
+    public static void setAfterSaleOperateType(AfterSaleOperateTypeEnum operateType) {
+        OPERATE_TYPE.set(operateType);
+    }
+
     public static void setUserInfo(Long userId, Integer userType) {
         USER_ID.set(userId);
         USER_TYPE.set(userType);
@@ -127,6 +138,7 @@ public class AfterSaleLogAspect {
         AFTER_SALE_ID.remove();
         BEFORE_STATUS.remove();
         AFTER_STATUS.remove();
+        OPERATE_TYPE.remove();
         EXTS.remove();
     }
 

+ 5 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/framework/aftersale/core/utils/AfterSaleLogUtils.java

@@ -1,6 +1,7 @@
 package cn.iocoder.yudao.module.trade.framework.aftersale.core.utils;
 
 
+import cn.iocoder.yudao.module.trade.enums.aftersale.AfterSaleOperateTypeEnum;
 import cn.iocoder.yudao.module.trade.framework.aftersale.core.aop.AfterSaleLogAspect;
 
 import java.util.Map;
@@ -13,6 +14,10 @@ import java.util.Map;
  */
 public class AfterSaleLogUtils {
 
+    public static void setAfterSaleOperateType(AfterSaleOperateTypeEnum operateType) {
+        AfterSaleLogAspect.setAfterSaleOperateType(operateType);
+    }
+
     public static void setAfterSaleInfo(Long id, Integer beforeStatus, Integer afterStatus) {
         setAfterSaleInfo(id, beforeStatus, afterStatus, null);
     }

+ 9 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/aftersale/AfterSaleService.java

@@ -108,6 +108,15 @@ public interface AfterSaleService {
      */
     void refundAfterSale(Long userId, String userIp, Long id);
 
+    /**
+     * 更新售后订单为已退款
+     *
+     * @param id          售后编号
+     * @param orderId     订单编号
+     * @param payRefundId 支付退款编号
+     */
+    void updateAfterSaleRefunded(Long id, Long orderId, Long payRefundId);
+
     /**
      * 【会员】取消售后
      *

+ 81 - 37
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/aftersale/AfterSaleServiceImpl.java

@@ -7,6 +7,8 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
 import cn.iocoder.yudao.module.pay.api.refund.PayRefundApi;
 import cn.iocoder.yudao.module.pay.api.refund.dto.PayRefundCreateReqDTO;
+import cn.iocoder.yudao.module.pay.api.refund.dto.PayRefundRespDTO;
+import cn.iocoder.yudao.module.pay.enums.refund.PayRefundStatusEnum;
 import cn.iocoder.yudao.module.promotion.api.combination.CombinationRecordApi;
 import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordRespDTO;
 import cn.iocoder.yudao.module.promotion.enums.combination.CombinationRecordStatusEnum;
@@ -40,14 +42,13 @@ import lombok.extern.slf4j.Slf4j;
 import org.springframework.context.annotation.Lazy;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
-import org.springframework.transaction.support.TransactionSynchronization;
-import org.springframework.transaction.support.TransactionSynchronizationManager;
 import org.springframework.validation.annotation.Validated;
 
 import javax.annotation.Resource;
 import java.time.LocalDateTime;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
 import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.*;
 
 /**
@@ -184,8 +185,6 @@ public class AfterSaleServiceImpl implements AfterSaleService {
         // 记录售后日志
         AfterSaleLogUtils.setAfterSaleInfo(afterSale.getId(), null,
                 AfterSaleStatusEnum.APPLY.getStatus());
-
-        // TODO 发送售后消息
         return afterSale;
     }
 
@@ -206,8 +205,6 @@ public class AfterSaleServiceImpl implements AfterSaleService {
 
         // 记录售后日志
         AfterSaleLogUtils.setAfterSaleInfo(afterSale.getId(), afterSale.getStatus(), newStatus);
-
-        // TODO 发送售后消息
     }
 
     @Override
@@ -226,8 +223,6 @@ public class AfterSaleServiceImpl implements AfterSaleService {
         // 记录售后日志
         AfterSaleLogUtils.setAfterSaleInfo(afterSale.getId(), afterSale.getStatus(), newStatus);
 
-        // TODO 发送售后消息
-
         // 更新交易订单项的售后状态为【未申请】
         tradeOrderUpdateService.updateOrderItemWhenAfterSaleCancel(afterSale.getOrderItemId());
     }
@@ -281,8 +276,6 @@ public class AfterSaleServiceImpl implements AfterSaleService {
                 AfterSaleStatusEnum.BUYER_DELIVERY.getStatus(),
                 MapUtil.<String, Object>builder().put("deliveryName", express.getName())
                         .put("logisticsNo", deliveryReqVO.getLogisticsNo()).build());
-
-        // TODO 发送售后消息
     }
 
     @Override
@@ -299,8 +292,6 @@ public class AfterSaleServiceImpl implements AfterSaleService {
         // 记录售后日志
         AfterSaleLogUtils.setAfterSaleInfo(afterSale.getId(), afterSale.getStatus(),
                 AfterSaleStatusEnum.WAIT_REFUND.getStatus());
-
-        // TODO 发送售后消息
     }
 
     @Override
@@ -326,8 +317,6 @@ public class AfterSaleServiceImpl implements AfterSaleService {
                 AfterSaleStatusEnum.SELLER_REFUSE.getStatus(),
                 MapUtil.of("reason", refuseReqVO.getRefuseMemo()));
 
-        // TODO 发送售后消息
-
         // 更新交易订单项的售后状态为【未申请】
         tradeOrderUpdateService.updateOrderItemWhenAfterSaleCancel(afterSale.getOrderItemId());
     }
@@ -365,33 +354,90 @@ public class AfterSaleServiceImpl implements AfterSaleService {
         // 发起退款单。注意,需要在事务提交后,再进行发起,避免重复发起
         createPayRefund(userIp, afterSale);
 
-        // 更新售后单的状态为【已完成】
-        updateAfterSaleStatus(afterSale.getId(), AfterSaleStatusEnum.WAIT_REFUND.getStatus(), new AfterSaleDO()
-                .setStatus(AfterSaleStatusEnum.COMPLETE.getStatus()).setRefundTime(LocalDateTime.now()));
-
         // 记录售后日志
         AfterSaleLogUtils.setAfterSaleInfo(afterSale.getId(), afterSale.getStatus(),
-                AfterSaleStatusEnum.COMPLETE.getStatus());
+                afterSale.getStatus()); // 特殊:这里状态不变,而是最终 updateAfterSaleRefunded 处理!!!
+    }
 
-        // TODO 发送售后消息
+    private void createPayRefund(String userIp, AfterSaleDO afterSale) {
+        // 创建退款单
+        PayRefundCreateReqDTO createReqDTO = AfterSaleConvert.INSTANCE.convert(userIp, afterSale, tradeOrderProperties)
+                .setReason(StrUtil.format("退款【{}】", afterSale.getSpuName()));
+        Long payRefundId = payRefundApi.createRefund(createReqDTO);
 
-        // 更新交易订单项的售后状态为【已完成】
-        tradeOrderUpdateService.updateOrderItemWhenAfterSaleSuccess(afterSale.getOrderItemId(), afterSale.getRefundPrice());
+        // 更新售后单的退款单号
+        tradeAfterSaleMapper.updateById(new AfterSaleDO().setId(afterSale.getId()).setPayRefundId(payRefundId));
     }
 
-    private void createPayRefund(String userIp, AfterSaleDO afterSale) {
-        TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
-
-            @Override
-            public void afterCommit() {
-                // 创建退款单
-                PayRefundCreateReqDTO createReqDTO = AfterSaleConvert.INSTANCE.convert(userIp, afterSale, tradeOrderProperties)
-                        .setReason(StrUtil.format("退款【{}】", afterSale.getSpuName()));
-                Long payRefundId = payRefundApi.createRefund(createReqDTO);
-                // 更新售后单的退款单号
-                tradeAfterSaleMapper.updateById(new AfterSaleDO().setId(afterSale.getId()).setPayRefundId(payRefundId));
-            }
-        });
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    @AfterSaleLog(operateType = AfterSaleOperateTypeEnum.SYSTEM_REFUND_SUCCESS)
+    public void updateAfterSaleRefunded(Long id, Long orderId, Long payRefundId) {
+        // 1. 校验售后单的状态,并状态待退款
+        AfterSaleDO afterSale = tradeAfterSaleMapper.selectById(id);
+        if (afterSale == null) {
+            throw exception(AFTER_SALE_NOT_FOUND);
+        }
+        if (ObjectUtil.notEqual(afterSale.getStatus(), AfterSaleStatusEnum.WAIT_REFUND.getStatus())) {
+            throw exception(AFTER_SALE_REFUND_FAIL_STATUS_NOT_WAIT_REFUND);
+        }
+
+        // 2. 校验退款单
+        PayRefundRespDTO payRefund = validatePayRefund(afterSale, payRefundId);
+
+        // 3. 处理退款结果
+        if (PayRefundStatusEnum.isSuccess(payRefund.getStatus())) {
+            // 【情况一:退款成功】
+            updateAfterSaleStatus(afterSale.getId(), AfterSaleStatusEnum.WAIT_REFUND.getStatus(), new AfterSaleDO()
+                .setStatus(AfterSaleStatusEnum.COMPLETE.getStatus()).setRefundTime(LocalDateTime.now()));
+
+            // 记录售后日志
+            AfterSaleLogUtils.setAfterSaleInfo(afterSale.getId(), afterSale.getStatus(), AfterSaleStatusEnum.COMPLETE.getStatus());
+
+            // 更新交易订单项的售后状态为【已完成】
+            tradeOrderUpdateService.updateOrderItemWhenAfterSaleSuccess(afterSale.getOrderItemId(), afterSale.getRefundPrice());
+            // 【情况二:退款失败】
+        } else if (PayRefundStatusEnum.isFailure(payRefund.getStatus())) {
+            // 记录售后日志
+            AfterSaleLogUtils.setAfterSaleOperateType(AfterSaleOperateTypeEnum.SYSTEM_REFUND_FAIL);
+            AfterSaleLogUtils.setAfterSaleInfo(afterSale.getId(), afterSale.getStatus(), afterSale.getStatus());
+        }
+    }
+
+    /**
+     * 校验退款单的合法性
+     *
+     * @param afterSale 售后单
+     * @param payRefundId 退款单编号
+     * @return 退款单
+     */
+    private PayRefundRespDTO validatePayRefund(AfterSaleDO afterSale, Long payRefundId) {
+        // 1. 校验退款单是否存在
+        PayRefundRespDTO payRefund = payRefundApi.getRefund(payRefundId);
+        if (payRefund == null) {
+            log.error("[validatePayRefund][afterSale({}) payRefund({}) 不存在,请进行处理!]", afterSale.getId(), payRefundId);
+            throw exception(AFTER_SALE_REFUND_FAIL_REFUND_NOT_FOUND);
+        }
+        // 2.1 校验退款单无退款结果(成功、失败)
+        if (!PayRefundStatusEnum.isSuccess(payRefund.getStatus())
+            && !PayRefundStatusEnum.isFailure(payRefund.getStatus())) {
+            log.error("[validatePayRefund][afterSale({}) payRefund({}) 无退款结果,请进行处理!payRefund 数据是:{}]",
+                    afterSale.getId(), payRefundId, toJsonString(payRefund));
+            throw exception(AFTER_SALE_REFUND_FAIL_REFUND_NOT_SUCCESS_OR_FAILURE);
+        }
+        // 2.2 校验退款金额一致
+        if (ObjectUtil.notEqual(payRefund.getRefundPrice(), afterSale.getRefundPrice())) {
+            log.error("[validatePayRefund][afterSale({}) payRefund({}) 退款金额不匹配,请进行处理!afterSale 数据是:{},payRefund 数据是:{}]",
+                    afterSale.getId(), payRefundId, toJsonString(afterSale), toJsonString(payRefund));
+            throw exception(AFTER_SALE_REFUND_FAIL_REFUND_PRICE_NOT_MATCH);
+        }
+        // 2.3 校验退款订单匹配(二次)
+        if (ObjectUtil.notEqual(payRefund.getMerchantRefundId(), afterSale.getId().toString())) {
+            log.error("[validatePayRefund][afterSale({}) 退款单不匹配({}),请进行处理!payRefund 数据是:{}]",
+                    afterSale.getId(), payRefundId, toJsonString(payRefund));
+            throw exception(AFTER_SALE_REFUND_FAIL_REFUND_ORDER_ID_ERROR);
+        }
+        return payRefund;
     }
 
     @Override
@@ -417,8 +463,6 @@ public class AfterSaleServiceImpl implements AfterSaleService {
         AfterSaleLogUtils.setAfterSaleInfo(afterSale.getId(), afterSale.getStatus(),
                 AfterSaleStatusEnum.BUYER_CANCEL.getStatus());
 
-        // TODO 发送售后消息
-
         // 更新交易订单项的售后状态为【未申请】
         tradeOrderUpdateService.updateOrderItemWhenAfterSaleCancel(afterSale.getOrderItemId());
     }

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

@@ -1,25 +1,24 @@
 package cn.iocoder.yudao.module.trade.service.brokerage;
 
 import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.lang.Assert;
 import cn.hutool.core.util.ObjectUtil;
 import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
 import cn.iocoder.yudao.framework.common.util.number.MoneyUtils;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
 import cn.iocoder.yudao.module.pay.api.transfer.PayTransferApi;
 import cn.iocoder.yudao.module.pay.api.transfer.dto.PayTransferCreateReqDTO;
+import cn.iocoder.yudao.module.pay.api.transfer.dto.PayTransferCreateRespDTO;
 import cn.iocoder.yudao.module.pay.api.transfer.dto.PayTransferRespDTO;
 import cn.iocoder.yudao.module.pay.api.wallet.PayWalletApi;
-import cn.iocoder.yudao.module.pay.api.wallet.dto.PayWalletAddBalanceReqDTO;
+import cn.iocoder.yudao.module.pay.api.wallet.dto.PayWalletRespDTO;
+import cn.iocoder.yudao.module.pay.enums.PayChannelEnum;
 import cn.iocoder.yudao.module.pay.enums.transfer.PayTransferStatusEnum;
-import cn.iocoder.yudao.module.pay.enums.transfer.PayTransferTypeEnum;
-import cn.iocoder.yudao.module.pay.enums.wallet.PayWalletBizTypeEnum;
-import cn.iocoder.yudao.module.system.api.notify.NotifyMessageSendApi;
-import cn.iocoder.yudao.module.system.api.social.SocialUserApi;
-import cn.iocoder.yudao.module.system.api.social.dto.SocialUserRespDTO;
-import cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum;
 import cn.iocoder.yudao.module.trade.controller.admin.brokerage.vo.withdraw.BrokerageWithdrawPageReqVO;
 import cn.iocoder.yudao.module.trade.controller.app.brokerage.vo.withdraw.AppBrokerageWithdrawCreateReqVO;
-import cn.iocoder.yudao.module.trade.convert.brokerage.BrokerageWithdrawConvert;
 import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.BrokerageWithdrawDO;
 import cn.iocoder.yudao.module.trade.dal.dataobject.config.TradeConfigDO;
 import cn.iocoder.yudao.module.trade.dal.mysql.brokerage.BrokerageWithdrawMapper;
@@ -29,21 +28,24 @@ import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageWithdrawTypeEnum;
 import cn.iocoder.yudao.module.trade.framework.order.config.TradeOrderProperties;
 import cn.iocoder.yudao.module.trade.service.brokerage.bo.BrokerageWithdrawSummaryRespBO;
 import cn.iocoder.yudao.module.trade.service.config.TradeConfigService;
+import com.google.common.base.Objects;
+import jakarta.annotation.Resource;
+import jakarta.validation.Validator;
 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 javax.validation.Validator;
 import java.time.LocalDateTime;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
+import java.util.Map;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
 import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getClientIP;
+import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.PAY_TRANSFER_NOT_FOUND;
 import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.*;
 
 /**
@@ -64,13 +66,9 @@ public class BrokerageWithdrawServiceImpl implements BrokerageWithdrawService {
     @Resource
     private TradeConfigService tradeConfigService;
 
-    @Resource
-    private NotifyMessageSendApi notifyMessageSendApi;
     @Resource
     private PayTransferApi payTransferApi;
     @Resource
-    private SocialUserApi socialUserApi;
-    @Resource
     private PayWalletApi payWalletApi;
 
     @Resource
@@ -84,15 +82,24 @@ public class BrokerageWithdrawServiceImpl implements BrokerageWithdrawService {
     public void auditBrokerageWithdraw(Long id, BrokerageWithdrawStatusEnum status, String auditReason, String userIp) {
         // 1.1 校验存在
         BrokerageWithdrawDO withdraw = validateBrokerageWithdrawExists(id);
+        // 1.2 特殊:【重新转账】如果是提现失败,并且状态是审核中,那么更新状态为审核中,并且清空 transferErrorMsg
+        if (BrokerageWithdrawStatusEnum.WITHDRAW_FAIL.getStatus().equals(withdraw.getStatus())) {
+            int updateCount = brokerageWithdrawMapper.updateByIdAndStatus(id, withdraw.getStatus(),
+                    new BrokerageWithdrawDO().setStatus(BrokerageWithdrawStatusEnum.AUDITING.getStatus()).setTransferErrorMsg(""));
+            if (updateCount == 0) {
+                throw exception(BROKERAGE_WITHDRAW_STATUS_NOT_AUDITING);
+            }
+            withdraw.setStatus(BrokerageWithdrawStatusEnum.AUDITING.getStatus()).setTransferErrorMsg("");
+        }
         // 1.2 校验状态为审核中
         if (ObjectUtil.notEqual(BrokerageWithdrawStatusEnum.AUDITING.getStatus(), withdraw.getStatus())) {
             throw exception(BROKERAGE_WITHDRAW_STATUS_NOT_AUDITING);
         }
 
         // 2. 更新状态
-        int rows = brokerageWithdrawMapper.updateByIdAndStatus(id, BrokerageWithdrawStatusEnum.AUDITING.getStatus(),
+        int updateCount = brokerageWithdrawMapper.updateByIdAndStatus(id, withdraw.getStatus(),
                 new BrokerageWithdrawDO().setStatus(status.getStatus()).setAuditReason(auditReason).setAuditTime(LocalDateTime.now()));
-        if (rows == 0) {
+        if (updateCount == 0) {
             throw exception(BROKERAGE_WITHDRAW_STATUS_NOT_AUDITING);
         }
 
@@ -109,44 +116,48 @@ public class BrokerageWithdrawServiceImpl implements BrokerageWithdrawService {
     }
 
     private void auditBrokerageWithdrawSuccess(BrokerageWithdrawDO withdraw) {
-        // 1.1 钱包
-        if (BrokerageWithdrawTypeEnum.WALLET.getType().equals(withdraw.getType())) {
-            payWalletApi.addWalletBalance(new PayWalletAddBalanceReqDTO()
-                    .setUserId(withdraw.getUserId()).setUserType(UserTypeEnum.MEMBER.getValue())
-                    .setBizType(PayWalletBizTypeEnum.BROKERAGE_WITHDRAW.getType()).setBizId(withdraw.getId().toString())
-                    .setPrice(withdraw.getPrice()));
-            // 1.2 微信 API
-        } else if (BrokerageWithdrawTypeEnum.WECHAT_API.getType().equals(withdraw.getType())) {
-            // TODO @luchi:这里,要加个转账单号的记录;另外,调用 API 转账,是立马成功,还是有延迟的哈?
-            Long payTransferId = createPayTransfer(withdraw);
-            // 1.3 剩余类型,都是手动打款,所以不处理
-        } else {
-            // TODO 可优化:未来可以考虑,接入支付宝、银联等 API 转账,实现自动打款
-            log.info("[auditBrokerageWithdrawSuccess][withdraw({}) 类型({}) 手动打款,无需处理]", withdraw.getId(), withdraw.getType());
+        // 情况一:通过 API 转账
+        if (BrokerageWithdrawTypeEnum.isApi(withdraw.getType())) {
+            createPayTransfer(withdraw);
+            return;
         }
 
-        // 2. 非支付 API,则直接体现成功
-        if (!BrokerageWithdrawTypeEnum.isApi(withdraw.getType())) {
-            brokerageWithdrawMapper.updateByIdAndStatus(withdraw.getId(), BrokerageWithdrawStatusEnum.AUDIT_SUCCESS.getStatus(),
-                    new BrokerageWithdrawDO().setStatus(BrokerageWithdrawStatusEnum.WITHDRAW_SUCCESS.getStatus()));
-        }
+        // 情况二:非 API 转账(手动打款)
+        brokerageWithdrawMapper.updateByIdAndStatus(withdraw.getId(), BrokerageWithdrawStatusEnum.AUDIT_SUCCESS.getStatus(),
+                new BrokerageWithdrawDO().setStatus(BrokerageWithdrawStatusEnum.WITHDRAW_SUCCESS.getStatus()));
     }
 
-    private Long createPayTransfer(BrokerageWithdrawDO withdraw) {
-        // 1.1 获取微信 openid
-        SocialUserRespDTO socialUser = socialUserApi.getSocialUserByUserId(
-                UserTypeEnum.MEMBER.getValue(), withdraw.getUserId(), SocialTypeEnum.WECHAT_MINI_PROGRAM.getType());
-        // TODO @luchi:这里,需要校验非空。如果空的话,要有业务异常哈;
+    private void createPayTransfer(BrokerageWithdrawDO withdraw) {
+        // 1.1 获取基础信息
+        String userAccount = withdraw.getUserAccount();
+        String userName = withdraw.getUserName();
+        String channelCode = null;
+        Map<String, String> channelExtras = null;
+        if (Objects.equal(withdraw.getType(), BrokerageWithdrawTypeEnum.ALIPAY_API.getType())) {
+            channelCode = PayChannelEnum.ALIPAY_PC.getCode();
+        } else if (Objects.equal(withdraw.getType(), BrokerageWithdrawTypeEnum.WECHAT_API.getType())) {
+            channelCode = withdraw.getTransferChannelCode();
+            userAccount = withdraw.getUserAccount();
+            // 特殊:微信需要有报备信息
+            channelExtras = PayTransferCreateReqDTO.buildWeiXinChannelExtra1000("佣金提现", "佣金提现");
+        } else if (Objects.equal(withdraw.getType(), BrokerageWithdrawTypeEnum.WALLET.getType())) {
+            PayWalletRespDTO wallet = payWalletApi.getOrCreateWallet(withdraw.getUserId(), UserTypeEnum.MEMBER.getValue());
+            Assert.notNull(wallet, "钱包不存在");
+            channelCode = PayChannelEnum.WALLET.getCode();
+            userAccount = wallet.getId().toString();
+        }
         // 1.2 构建请求
-        PayTransferCreateReqDTO payTransferCreateReqDTO = new PayTransferCreateReqDTO()
-                .setAppKey(tradeOrderProperties.getPayAppKey())
-                .setChannelCode("wx_lite").setType(PayTransferTypeEnum.WX_BALANCE.getType())
-                .setMerchantTransferId(withdraw.getId().toString())
-                .setPrice(withdraw.getPrice())
-                .setSubject("佣金提现")
-                .setOpenid(socialUser.getOpenid()).setUserIp(getClientIP());
-        // 2. 发起请求
-        return payTransferApi.createTransfer(payTransferCreateReqDTO);
+        PayTransferCreateReqDTO transferReqDTO = new PayTransferCreateReqDTO()
+                .setAppKey(tradeOrderProperties.getPayAppKey()).setChannelCode(channelCode)
+                .setMerchantTransferId(withdraw.getId().toString()).setSubject("佣金提现").setPrice(withdraw.getPrice())
+                .setUserAccount(userAccount).setUserName(userName).setUserIp(getClientIP())
+                .setChannelExtras(channelExtras);
+        // 1.3 发起请求
+        PayTransferCreateRespDTO transferRespDTO = payTransferApi.createTransfer(transferReqDTO);
+
+        // 2. 更新提现记录
+        brokerageWithdrawMapper.updateById(new BrokerageWithdrawDO().setId(withdraw.getId())
+                .setPayTransferId(transferRespDTO.getId()).setTransferChannelCode(channelCode));
     }
 
     private BrokerageWithdrawDO validateBrokerageWithdrawExists(Long id) {
@@ -178,7 +189,8 @@ public class BrokerageWithdrawServiceImpl implements BrokerageWithdrawService {
         // 2.1 计算手续费
         Integer feePrice = calculateFeePrice(createReqVO.getPrice(), tradeConfig.getBrokerageWithdrawFeePercent());
         // 2.2 创建佣金提现记录
-        BrokerageWithdrawDO withdraw = BrokerageWithdrawConvert.INSTANCE.convert(createReqVO, userId, feePrice);
+        BrokerageWithdrawDO withdraw = BeanUtils.toBean(createReqVO, BrokerageWithdrawDO.class)
+                .setUserId(userId).setFeePrice(feePrice);
         brokerageWithdrawMapper.insert(withdraw);
 
         // 3. 创建用户佣金记录
@@ -220,22 +232,71 @@ public class BrokerageWithdrawServiceImpl implements BrokerageWithdrawService {
     @Override
     @Transactional(rollbackFor = Exception.class)
     public void updateBrokerageWithdrawTransferred(Long id, Long payTransferId) {
-        BrokerageWithdrawDO withdraw = validateBrokerageWithdrawExists(id);
-        PayTransferRespDTO transfer = payTransferApi.getTransfer(payTransferId);
-        // TODO @luchi:建议参考支付那,即使成功的情况下,也要各种校验;金额是否匹配、转账单号是否匹配、是否重复调用;
-        if (PayTransferStatusEnum.isSuccess(transfer.getStatus())) {
-            withdraw.setStatus(BrokerageWithdrawStatusEnum.WITHDRAW_SUCCESS.getStatus());
-            // TODO @luchi:发送站内信
-        } else if (PayTransferStatusEnum.isPendingStatus(transfer.getStatus())) {
-            // TODO @luchi:这里,是不是不用更新哈?
-            withdraw.setStatus(BrokerageWithdrawStatusEnum.AUDIT_SUCCESS.getStatus());
-        } else {
-            withdraw.setStatus(BrokerageWithdrawStatusEnum.WITHDRAW_FAIL.getStatus());
-            // 3.2 驳回时需要退还用户佣金
-            brokerageRecordService.addBrokerage(withdraw.getUserId(), BrokerageRecordBizTypeEnum.WITHDRAW_REJECT,
-                    String.valueOf(withdraw.getId()), withdraw.getPrice(), BrokerageRecordBizTypeEnum.WITHDRAW_REJECT.getTitle());
+        // 1.1 校验提现单是否存在
+        BrokerageWithdrawDO withdraw = brokerageWithdrawMapper.selectById(id);
+        if (withdraw == null) {
+            log.error("[updateBrokerageWithdrawTransferred][withdraw({}) payTransfer({}) 不存在提现单,请进行处理!]", id, payTransferId);
+            throw exception(BROKERAGE_WITHDRAW_NOT_EXISTS);
+        }
+        // 1.2 校验提现单已经结束(成功或失败)
+        if (ObjectUtils.equalsAny(withdraw.getStatus(), BrokerageWithdrawStatusEnum.WITHDRAW_SUCCESS.getStatus(),
+                BrokerageWithdrawStatusEnum.WITHDRAW_FAIL.getStatus())) {
+            // 特殊:转账单编号相同,直接返回,说明重复回调
+            if (ObjectUtil.equal(withdraw.getPayTransferId(), payTransferId)) {
+                log.warn("[updateBrokerageWithdrawTransferred][withdraw({}) 已结束,且转账单编号相同({}),直接返回]", withdraw, payTransferId);
+                return;
+            }
+            // 异常:转账单编号不同,说明转账单编号错误
+            log.error("[updateBrokerageWithdrawTransferred][withdraw({}) 转账单不匹配({}),请进行处理!]", withdraw, payTransferId);
+            throw exception(BROKERAGE_WITHDRAW_UPDATE_STATUS_FAIL_PAY_TRANSFER_ID_ERROR);
+        }
+
+        // 2. 校验转账单的合法性
+        PayTransferRespDTO payTransfer = validateBrokerageTransferStatusCanUpdate(withdraw, payTransferId);
+
+        // 3. 更新提现单状态
+        Integer newStatus = PayTransferStatusEnum.isSuccess(payTransfer.getStatus()) ? BrokerageWithdrawStatusEnum.WITHDRAW_SUCCESS.getStatus() :
+                PayTransferStatusEnum.isClosed(payTransfer.getStatus()) ? BrokerageWithdrawStatusEnum.WITHDRAW_FAIL.getStatus() : null;
+        Assert.notNull(newStatus, "转账单状态({}) 不合法", payTransfer.getStatus());
+        brokerageWithdrawMapper.updateByIdAndStatus(withdraw.getId(), withdraw.getStatus(),
+                new BrokerageWithdrawDO().setStatus(newStatus)
+                        .setTransferTime(payTransfer.getSuccessTime())
+                        .setTransferErrorMsg(payTransfer.getChannelErrorMsg()));
+    }
+
+    private PayTransferRespDTO validateBrokerageTransferStatusCanUpdate(BrokerageWithdrawDO withdraw, Long payTransferId) {
+        // 1. 校验转账单是否存在
+        PayTransferRespDTO payTransfer = payTransferApi.getTransfer(payTransferId);
+        if (payTransfer == null) {
+            log.error("[validateBrokerageTransferStatusCanUpdate][withdraw({}) payTransfer({}) 不存在,请进行处理!]", withdraw.getId(), payTransferId);
+            throw exception(PAY_TRANSFER_NOT_FOUND);
+        }
+
+        // 2.1 校验转账单已成功或关闭
+        if (!PayTransferStatusEnum.isSuccessOrClosed(payTransfer.getStatus())) {
+            log.error("[validateBrokerageTransferStatusCanUpdate][withdraw({}) payTransfer({}) 未结束,请进行处理!payTransfer 数据是:{}]",
+                    withdraw.getId(), payTransferId, JsonUtils.toJsonString(payTransfer));
+            throw exception(BROKERAGE_WITHDRAW_UPDATE_STATUS_FAIL_PAY_TRANSFER_STATUS_NOT_SUCCESS_OR_CLOSED);
+        }
+        // 2.2 校验转账金额一致
+        if (ObjectUtil.notEqual(payTransfer.getPrice(), withdraw.getPrice())) {
+            log.error("[validateBrokerageTransferStatusCanUpdate][withdraw({}) payTransfer({}) 转账金额不匹配,请进行处理!withdraw 数据是:{},payTransfer 数据是:{}]",
+                    withdraw.getId(), payTransferId, JsonUtils.toJsonString(withdraw), JsonUtils.toJsonString(payTransfer));
+            throw exception(BROKERAGE_WITHDRAW_UPDATE_STATUS_FAIL_PAY_PRICE_NOT_MATCH);
+        }
+        // 2.3 校验转账订单匹配
+        if (ObjectUtil.notEqual(payTransfer.getMerchantTransferId(), withdraw.getId().toString())) {
+            log.error("[validateBrokerageTransferStatusCanUpdate][withdraw({}) 转账单不匹配({}),请进行处理!payTransfer 数据是:{}]",
+                    withdraw.getId(), payTransferId, JsonUtils.toJsonString(payTransfer));
+            throw exception(BROKERAGE_WITHDRAW_UPDATE_STATUS_FAIL_PAY_MERCHANT_EXISTS);
+        }
+        // 2.4 校验转账渠道一致
+        if (ObjectUtil.notEqual(payTransfer.getChannelCode(), withdraw.getTransferChannelCode())) {
+            log.error("[validateBrokerageTransferStatusCanUpdate][withdraw({}) payTransfer({}) 转账渠道不匹配,请进行处理!withdraw 数据是:{},payTransfer 数据是:{}]",
+                    withdraw.getId(), payTransferId, JsonUtils.toJsonString(withdraw), JsonUtils.toJsonString(payTransfer));
+            throw exception(BROKERAGE_WITHDRAW_UPDATE_STATUS_FAIL_PAY_CHANNEL_NOT_MATCH);
         }
-        brokerageWithdrawMapper.updateById(withdraw);
+        return payTransfer;
     }
 
     @Override

+ 8 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateService.java

@@ -209,6 +209,14 @@ public interface TradeOrderUpdateService {
      */
     void cancelPaidOrder(Long userId, Long orderId, Integer cancelType);
 
+    /**
+     * 取消支付订单的退款回调
+     *
+     * @param id               订单编号
+     * @param payRefundId      支付退款编号
+     */
+    void updatePaidOrderRefunded(Long id, Long payRefundId);
+
     /**
      * 更新下单赠送的优惠券编号到订单
      *

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

@@ -20,7 +20,9 @@ import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderCreateReqDTO;
 import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderRespDTO;
 import cn.iocoder.yudao.module.pay.api.refund.PayRefundApi;
 import cn.iocoder.yudao.module.pay.api.refund.dto.PayRefundCreateReqDTO;
+import cn.iocoder.yudao.module.pay.api.refund.dto.PayRefundRespDTO;
 import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum;
+import cn.iocoder.yudao.module.pay.enums.refund.PayRefundStatusEnum;
 import cn.iocoder.yudao.module.product.api.comment.ProductCommentApi;
 import cn.iocoder.yudao.module.product.api.comment.dto.ProductCommentCreateReqDTO;
 import cn.iocoder.yudao.module.system.api.social.SocialClientApi;
@@ -280,7 +282,7 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
         TradeOrderDO order = validateOrderExists(id);
         // 1.2 校验订单已支付
         if (!TradeOrderStatusEnum.isUnpaid(order.getStatus()) || order.getPayStatus()) {
-            // 特殊:如果订单已支付,且支付单号相同,直接返回,说明重复回调
+            // 特殊:支付单号相同,直接返回,说明重复回调
             if (ObjectUtil.equals(order.getPayOrderId(), payOrderId)) {
                 log.warn("[updateOrderPaid][order({}) 已支付,且支付单号相同({}),直接返回]", order, payOrderId);
                 return;
@@ -938,10 +940,24 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
                 .setAppKey(tradeOrderProperties.getPayAppKey())  // 支付应用
                 .setUserIp(NetUtil.getLocalhostStr()) // 使用本机 IP,因为是服务器发起退款的
                 .setMerchantOrderId(String.valueOf(order.getId())) // 支付单号
-                .setMerchantRefundId(String.valueOf(order.getId()))
+                // 特殊:因为订单支持 AfterSale 单个售后退款,也支持整单退款,所以需要通过 order- 进行下区分
+                //      具体可见 AfterSaleController 的 updateAfterSaleRefunded 方法
+                .setMerchantRefundId("order-" + order.getId())
                 .setReason(TradeOrderCancelTypeEnum.COMBINATION_CLOSE.getName()).setPrice(order.getPayPrice())); // 价格信息
     }
 
+    @Override
+    public void updatePaidOrderRefunded(Long id, Long payRefundId) {
+        PayRefundRespDTO payRefund = payRefundApi.getRefund(payRefundId);
+        if (payRefund == null) {
+            throw exception(ORDER_UPDATE_PAID_ORDER_REFUNDED_FAIL_REFUND_NOT_FOUND);
+        }
+        // 特殊:因为在 cancelPaidOrder 已经进行订单的取消,所以这里必须退款成功!!!
+        if (!PayRefundStatusEnum.isSuccess(payRefund.getStatus())) {
+            throw exception(ORDER_UPDATE_PAID_ORDER_REFUNDED_FAIL_REFUND_STATUS_NOT_SUCCESS);
+        }
+    }
+
     @Override
     public void updateOrderGiveCouponIds(Long userId, Long orderId, List<Long> giveCouponIds) {
         // 1. 检验订单存在

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

@@ -57,8 +57,8 @@ public class BrokerageWithdrawServiceImplTest extends BaseDbUnitTest {
         BrokerageWithdrawDO dbBrokerageWithdraw = randomPojo(BrokerageWithdrawDO.class, o -> { // 等会查询到
             o.setUserId(null);
             o.setType(null);
-            o.setName(null);
-            o.setAccountNo(null);
+            o.setUserName(null);
+            o.setUserAccount(null);
             o.setBankName(null);
             o.setStatus(null);
             o.setCreateTime(null);
@@ -69,9 +69,9 @@ public class BrokerageWithdrawServiceImplTest extends BaseDbUnitTest {
         // 测试 type 不匹配
         brokerageWithdrawMapper.insert(cloneIgnoreId(dbBrokerageWithdraw, o -> o.setType(null)));
         // 测试 name 不匹配
-        brokerageWithdrawMapper.insert(cloneIgnoreId(dbBrokerageWithdraw, o -> o.setName(null)));
+        brokerageWithdrawMapper.insert(cloneIgnoreId(dbBrokerageWithdraw, o -> o.setUserName(null)));
         // 测试 accountNo 不匹配
-        brokerageWithdrawMapper.insert(cloneIgnoreId(dbBrokerageWithdraw, o -> o.setAccountNo(null)));
+        brokerageWithdrawMapper.insert(cloneIgnoreId(dbBrokerageWithdraw, o -> o.setUserAccount(null)));
         // 测试 bankName 不匹配
         brokerageWithdrawMapper.insert(cloneIgnoreId(dbBrokerageWithdraw, o -> o.setBankName(null)));
         // 测试 status 不匹配
@@ -88,8 +88,8 @@ public class BrokerageWithdrawServiceImplTest extends BaseDbUnitTest {
         BrokerageWithdrawPageReqVO reqVO = new BrokerageWithdrawPageReqVO();
         reqVO.setUserId(null);
         reqVO.setType(null);
-        reqVO.setName(null);
-        reqVO.setAccountNo(null);
+        reqVO.setUserName(null);
+        reqVO.setUserAccount(null);
         reqVO.setBankName(null);
         reqVO.setStatus(null);
         reqVO.setCreateTime(buildBetweenTime(2023, 2, 1, 2023, 2, 28));

+ 6 - 1
yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/notify/dto/PayRefundNotifyReqDTO.java

@@ -1,6 +1,5 @@
 package cn.iocoder.yudao.module.pay.api.notify.dto;
 
-import cn.iocoder.yudao.module.pay.enums.refund.PayRefundStatusEnum;
 import lombok.AllArgsConstructor;
 import lombok.Builder;
 import lombok.Data;
@@ -26,6 +25,12 @@ public class PayRefundNotifyReqDTO {
     @NotEmpty(message = "商户退款单编号不能为空")
     private String merchantOrderId;
 
+    /**
+     * 商户退款编号
+     */
+    @NotEmpty(message = "商户退款编号不能为空")
+    private String merchantRefundId;
+
     /**
      * 支付退款编号
      */

+ 8 - 3
yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/notify/dto/PayTransferNotifyReqDTO.java

@@ -1,9 +1,12 @@
 package cn.iocoder.yudao.module.pay.api.notify.dto;
 
+import lombok.AllArgsConstructor;
+import lombok.Builder;
 import lombok.Data;
 
-import javax.validation.constraints.NotEmpty;
-import javax.validation.constraints.NotNull;
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.NotNull;
+import lombok.NoArgsConstructor;
 
 /**
  * 转账单的通知 Request DTO
@@ -11,9 +14,11 @@ import javax.validation.constraints.NotNull;
  * @author jason
  */
 @Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
 public class PayTransferNotifyReqDTO {
 
-    // TODO 芋艿:要不要改成 orderId 待定;
     /**
      * 商户转账单号
      */

+ 15 - 0
yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/refund/dto/PayRefundRespDTO.java

@@ -42,9 +42,24 @@ public class PayRefundRespDTO {
      * 商户订单编号
      */
     private String merchantOrderId;
+    /**
+     * 商户退款编号
+     */
+    private String merchantRefundId;
     /**
      * 退款成功时间
      */
     private LocalDateTime successTime;
 
+    // ========== 渠道相关字段 ==========
+
+    /**
+     * 调用渠道的错误码
+     */
+    private String channelErrorCode;
+    /**
+     * 调用渠道的错误提示
+     */
+    private String channelErrorMsg;
+
 }

+ 4 - 4
yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/transfer/PayTransferApi.java

@@ -1,9 +1,9 @@
 package cn.iocoder.yudao.module.pay.api.transfer;
 
 import cn.iocoder.yudao.module.pay.api.transfer.dto.PayTransferCreateReqDTO;
+import cn.iocoder.yudao.module.pay.api.transfer.dto.PayTransferCreateRespDTO;
 import cn.iocoder.yudao.module.pay.api.transfer.dto.PayTransferRespDTO;
-
-import javax.validation.Valid;
+import jakarta.validation.Valid;
 
 /**
  * 转账单 API 接口
@@ -16,9 +16,9 @@ public interface PayTransferApi {
      * 创建转账单
      *
      * @param reqDTO 创建请求
-     * @return 转账单编号
+     * @return 创建结果
      */
-    Long createTransfer(@Valid PayTransferCreateReqDTO reqDTO);
+    PayTransferCreateRespDTO createTransfer(@Valid PayTransferCreateReqDTO reqDTO);
 
     /**
      * 获得转账单

+ 63 - 20
yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/transfer/dto/PayTransferCreateReqDTO.java

@@ -1,13 +1,15 @@
 package cn.iocoder.yudao.module.pay.api.transfer.dto;
 
-import cn.iocoder.yudao.framework.common.validation.InEnum;
-import cn.iocoder.yudao.module.pay.enums.transfer.PayTransferTypeEnum;
+import cn.hutool.core.util.StrUtil;
+import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
+import jakarta.validation.constraints.Min;
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.NotNull;
 import lombok.Data;
 
-import javax.validation.constraints.Min;
-import javax.validation.constraints.NotBlank;
-import javax.validation.constraints.NotEmpty;
-import javax.validation.constraints.NotNull;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 
 /**
@@ -24,6 +26,9 @@ public class PayTransferCreateReqDTO {
     @NotNull(message = "应用标识不能为空")
     private String appKey;
 
+    /**
+     * 转账渠道
+     */
     @NotEmpty(message = "转账渠道不能为空")
     private String channelCode;
 
@@ -32,16 +37,11 @@ public class PayTransferCreateReqDTO {
      */
     private Map<String, String> channelExtras;
 
-    @NotEmpty(message = "用户 IP 不能为空")
-    private String userIp;
-
     /**
-     * 类型
+     * 用户 IP
      */
-    @NotNull(message = "转账类型不能为空")
-    @InEnum(PayTransferTypeEnum.class)
-    private Integer type;
-
+    @NotEmpty(message = "用户 IP 不能为空")
+    private String userIp;
 
     /**
      * 商户转账单编号
@@ -62,16 +62,59 @@ public class PayTransferCreateReqDTO {
     @NotEmpty(message = "转账标题不能为空")
     private String subject;
 
+    /**
+     * 收款人账号
+     *
+     * 微信场景下:openid
+     * 支付宝场景下:支付宝账号
+     */
+    @NotEmpty(message = "收款人账号不能为空")
+    private String userAccount;
     /**
      * 收款人姓名
      */
-    @NotBlank(message = "收款人姓名不能为空", groups = {PayTransferTypeEnum.Alipay.class})
     private String userName;
 
-    @NotBlank(message = "支付宝登录号不能为空", groups = {PayTransferTypeEnum.Alipay.class})
-    private String alipayLogonId;
+    /**
+     * 【微信】现金营销场景
+     *
+     * @param activityName 活动名称
+     * @param rewardDescription 奖励说明
+     * @return channelExtras
+     */
+    public static Map<String, String> buildWeiXinChannelExtra1000(String activityName, String rewardDescription) {
+        return buildWeiXinChannelExtra(1000,
+                "活动名称", activityName,
+                "奖励说明", rewardDescription);
+    }
+
+    /**
+     * 【微信】企业报销场景
+     *
+     * @param expenseType 报销类型
+     * @param expenseDescription 报销说明
+     * @return channelExtras
+     */
+    public static Map<String, String> buildWeiXinChannelExtra1006(String expenseType, String expenseDescription) {
+        return buildWeiXinChannelExtra(1006,
+                "报销类型", expenseType,
+                "报销说明", expenseDescription);
+    }
+
+    private static Map<String, String> buildWeiXinChannelExtra(Integer sceneId, String... values) {
+        Map<String, String> channelExtras = new HashMap<>();
+        // 构建场景报备信息列表
+        List<Map<String, String>> sceneReportInfos = new ArrayList<>();
+        for (int i = 0; i < values.length; i += 2) {
+            Map<String, String> info = new HashMap<>();
+            info.put("infoType", values[i]);
+            info.put("infoContent", values[i + 1]);
+            sceneReportInfos.add(info);
+        }
+        // 设置场景ID和场景报备信息
+        channelExtras.put("sceneId", StrUtil.toString(sceneId));
+        channelExtras.put("sceneReportInfos", JsonUtils.toJsonString(sceneReportInfos));
+        return channelExtras;
+    }
 
-    // ========== 微信转账相关字段 ==========
-    @NotBlank(message = "微信 openId 不能为空", groups = {PayTransferTypeEnum.WxPay.class})
-    private String openid;
 }

+ 28 - 0
yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/transfer/dto/PayTransferCreateRespDTO.java

@@ -0,0 +1,28 @@
+package cn.iocoder.yudao.module.pay.api.transfer.dto;
+
+import lombok.Data;
+
+/**
+ * 转账单创建 Response DTO
+ *
+ * @author 芋道源码
+ */
+@Data
+public class PayTransferCreateRespDTO {
+
+    /**
+     * 编号
+     */
+    private Long id;
+
+    // ========== 其它字段 ==========
+
+    /**
+     * 渠道 package 信息
+     *
+     * 特殊:目前只有微信转账有这个东西!!!
+     * @see <a href="https://pay.weixin.qq.com/doc/v3/merchant/4012716430">JSAPI 调起用户确认收款</a>
+     */
+    private String channelPackageInfo;
+
+}

+ 50 - 0
yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/transfer/dto/PayTransferRespDTO.java

@@ -1,8 +1,11 @@
 package cn.iocoder.yudao.module.pay.api.transfer.dto;
 
+import cn.iocoder.yudao.module.pay.enums.PayChannelEnum;
 import cn.iocoder.yudao.module.pay.enums.transfer.PayTransferStatusEnum;
 import lombok.Data;
 
+import java.time.LocalDateTime;
+
 @Data
 public class PayTransferRespDTO {
 
@@ -16,6 +19,22 @@ public class PayTransferRespDTO {
      */
     private String no;
 
+    /**
+     * 转账渠道编码
+     *
+     * 枚举 {@link PayChannelEnum}
+     */
+    private String channelCode;
+
+    // ========== 商户相关字段 ==========
+
+    /**
+     * 商户转账单编号
+     */
+    private String merchantTransferId;
+
+    // ========== 转账相关字段 ==========
+
     /**
      * 转账金额,单位:分
      */
@@ -28,4 +47,35 @@ public class PayTransferRespDTO {
      */
     private Integer status;
 
+    /**
+     * 订单转账成功时间
+     */
+    private LocalDateTime successTime;
+
+    // ========== 其它字段 ==========
+
+    /**
+     * 调用渠道的错误码
+     */
+    private String channelErrorCode;
+    /**
+     * 调用渠道的错误提示
+     */
+    private String channelErrorMsg;
+
+    /**
+     * 渠道 package 信息
+     *
+     * 特殊:目前只有微信转账有这个东西!!!
+     * @see <a href="https://pay.weixin.qq.com/doc/v3/merchant/4012716430">JSAPI 调起用户确认收款</a>
+     */
+    private String channelPackageInfo;
+    /**
+     * 渠道商户号
+     *
+     * 特殊:目前只有微信转账有这个东西!!!
+     * @see <a href="https://pay.weixin.qq.com/doc/v3/merchant/4012716430">JSAPI 调起用户确认收款</a>
+     */
+    private String channelMchId;
+
 }

+ 10 - 0
yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/wallet/PayWalletApi.java

@@ -1,6 +1,7 @@
 package cn.iocoder.yudao.module.pay.api.wallet;
 
 import cn.iocoder.yudao.module.pay.api.wallet.dto.PayWalletAddBalanceReqDTO;
+import cn.iocoder.yudao.module.pay.api.wallet.dto.PayWalletRespDTO;
 
 /**
  * 钱包 API 接口
@@ -16,4 +17,13 @@ public interface PayWalletApi {
      */
     void addWalletBalance(PayWalletAddBalanceReqDTO reqDTO);
 
+    /**
+     * 获取钱包信息
+     *
+     * @param userId 用户编号
+     * @param userType 用户类型
+     * @return 钱包信息
+     */
+    PayWalletRespDTO getOrCreateWallet(Long userId, Integer userType);
+
 }

+ 52 - 0
yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/wallet/dto/PayWalletRespDTO.java

@@ -0,0 +1,52 @@
+package cn.iocoder.yudao.module.pay.api.wallet.dto;
+
+import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
+import lombok.Data;
+
+/**
+ * 钱包 Response DTO
+ *
+ * @author jason
+ */
+@Data
+public class PayWalletRespDTO {
+
+    /**
+     * 编号
+     */
+    private Long id;
+
+    /**
+     * 用户 id
+     *
+     * 关联 MemberUserDO 的 id 编号
+     * 关联 AdminUserDO 的 id 编号
+     */
+    private Long userId;
+    /**
+     * 用户类型, 预留 多商户转帐可能需要用到
+     *
+     * 关联 {@link UserTypeEnum}
+     */
+    private Integer userType;
+
+    /**
+     * 余额,单位分
+     */
+    private Integer balance;
+
+    /**
+     * 冻结金额,单位分
+     */
+    private Integer freezePrice;
+
+    /**
+     * 累计支出,单位分
+     */
+    private Integer totalExpense;
+    /**
+     * 累计充值,单位分
+     */
+    private Integer totalRecharge;
+
+}

+ 2 - 0
yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/DictTypeConstants.java

@@ -15,4 +15,6 @@ public interface DictTypeConstants {
 
     String NOTIFY_STATUS = "pay_notify_status"; // 回调状态
 
+    String TRANSFER_STATUS = "pay_transfer_status"; // 转账状态
+
 }

+ 14 - 10
yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/ErrorCodeConstants.java

@@ -65,13 +65,12 @@ public interface ErrorCodeConstants {
     ErrorCode WALLET_RECHARGE_PACKAGE_NAME_EXISTS = new ErrorCode(1_007_008_013, "钱包充值套餐名称已存在");
 
     // ========== 转账模块 1-007-009-000 ==========
-    ErrorCode PAY_TRANSFER_SUBMIT_CHANNEL_ERROR = new ErrorCode(1_007_009_000, "发起转账报错,错误码:{},错误提示:{}");
     ErrorCode PAY_TRANSFER_NOT_FOUND = new ErrorCode(1_007_009_001, "转账单不存在");
-    ErrorCode PAY_SAME_MERCHANT_TRANSFER_TYPE_NOT_MATCH = new ErrorCode(1_007_009_002, "两次相同转账请求的类型不匹配");
-    ErrorCode PAY_SAME_MERCHANT_TRANSFER_PRICE_NOT_MATCH = new ErrorCode(1_007_009_003, "两次相同转账请求的金额不匹配");
-    ErrorCode PAY_MERCHANT_TRANSFER_EXISTS = new ErrorCode(1_007_009_004, "该笔业务的转账已经发起,请查询转账订单相关状态");
-    ErrorCode PAY_TRANSFER_STATUS_IS_NOT_WAITING = new ErrorCode(1_007_009_005, "转账单不处于待转账");
-    ErrorCode PAY_TRANSFER_STATUS_IS_NOT_PENDING = new ErrorCode(1_007_009_006, "转账单不处于待转账或转账中");
+    ErrorCode PAY_TRANSFER_CREATE_CHANNEL_NOT_MATCH = new ErrorCode(1_007_009_002, "转账发起失败,原因:两次相同转账请求的类型不匹配");
+    ErrorCode PAY_TRANSFER_CREATE_PRICE_NOT_MATCH = new ErrorCode(1_007_009_003, "转账发起失败,原因:两次相同转账请求的金额不匹配");
+    ErrorCode PAY_TRANSFER_CREATE_FAIL_STATUS_NOT_CLOSED = new ErrorCode(1_007_009_004, "转账发起失败,原因:已经存在相同的转账单,且状态不是已关闭");
+    ErrorCode PAY_TRANSFER_NOTIFY_FAIL_STATUS_IS_NOT_WAITING = new ErrorCode(1_007_009_006, "通知转账结果失败,原因:转账单不处于待转账");
+    ErrorCode PAY_TRANSFER_NOTIFY_FAIL_STATUS_NOT_WAITING_OR_PROCESSING = new ErrorCode(1_007_009_007, "通知转账结果失败,原因:转账单不处于待转账或转账中");
 
     // ========== 示例订单 1-007-900-000 ==========
     ErrorCode DEMO_ORDER_NOT_FOUND = new ErrorCode(1_007_900_000, "示例订单不存在");
@@ -86,8 +85,13 @@ public interface ErrorCodeConstants {
     ErrorCode DEMO_ORDER_REFUND_FAIL_REFUND_ORDER_ID_ERROR = new ErrorCode(1_007_900_009, "发起退款失败,退款单编号不匹配");
     ErrorCode DEMO_ORDER_REFUND_FAIL_REFUND_PRICE_NOT_MATCH = new ErrorCode(1_007_900_010, "发起退款失败,退款单金额不匹配");
 
-    // ========== 示例转账订单 1-007-901-001 ==========
-    ErrorCode DEMO_TRANSFER_NOT_FOUND = new ErrorCode(1_007_901_001, "示例转账单不存在");
-    ErrorCode DEMO_TRANSFER_FAIL_TRANSFER_ID_ERROR = new ErrorCode(1_007_901_002, "转账失败,转账单编号不匹配");
-    ErrorCode DEMO_TRANSFER_FAIL_PRICE_NOT_MATCH = new ErrorCode(1_007_901_003, "转账失败,转账单金额不匹配");
+    // ========== 示例提现单 1-007-901-000 ==========
+    ErrorCode DEMO_WITHDRAW_NOT_FOUND = new ErrorCode(1_007_901_000, "示例提现单不存在");
+    ErrorCode DEMO_WITHDRAW_UPDATE_STATUS_FAIL_PAY_TRANSFER_ID_ERROR = new ErrorCode(1_007_901_001, "更新示例提现单状态失败,支付转账单编号不匹配");
+    ErrorCode DEMO_WITHDRAW_UPDATE_STATUS_FAIL_PAY_TRANSFER_STATUS_NOT_SUCCESS_OR_CLOSED = new ErrorCode(1_007_901_002, "更新示例提现单状态失败,支付转账单状态不是【转账成功】或【转账失败】状态");
+    ErrorCode DEMO_WITHDRAW_UPDATE_STATUS_FAIL_PAY_PRICE_NOT_MATCH = new ErrorCode(1_007_901_003, "更新示例提现单状态失败,支付转账单金额不匹配");
+    ErrorCode DEMO_WITHDRAW_UPDATE_STATUS_FAIL_PAY_MERCHANT_EXISTS = new ErrorCode(1_007_901_004, "更新示例提现单状态失败,支付转账单商户订单号不匹配");
+    ErrorCode DEMO_WITHDRAW_UPDATE_STATUS_FAIL_PAY_CHANNEL_NOT_MATCH = new ErrorCode(1_007_901_005, "更新示例提现单状态失败,支付转账单渠道不匹配");
+    ErrorCode DEMO_WITHDRAW_TRANSFER_FAIL_STATUS_NOT_WAITING_OR_CLOSED = new ErrorCode(1_007_901_008, "发起转账失败,原因:示例提现单状态不是【等待提现】或【提现关闭】");
+
 }

+ 11 - 1
yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/PayChannelEnum.java

@@ -1,8 +1,11 @@
 package cn.iocoder.yudao.module.pay.enums;
 
+import cn.iocoder.yudao.framework.common.core.ArrayValuable;
 import lombok.AllArgsConstructor;
 import lombok.Getter;
 
+import java.util.Arrays;
+
 /**
  * 支付渠道的编码的枚举
  *
@@ -10,7 +13,7 @@ import lombok.Getter;
  */
 @Getter
 @AllArgsConstructor
-public enum PayChannelEnum {
+public enum PayChannelEnum implements ArrayValuable<String> {
 
     WX_PUB("wx_pub", "微信 JSAPI 支付"), // 公众号网页
     WX_LITE("wx_lite", "微信小程序支付"),
@@ -28,6 +31,8 @@ public enum PayChannelEnum {
 
     WALLET("wallet", "钱包支付");
 
+    public static final String[] ARRAYS = Arrays.stream(values()).map(PayChannelEnum::getCode).toArray(String[]::new);
+
     /**
      * 编码
      *
@@ -39,4 +44,9 @@ public enum PayChannelEnum {
      */
     private final String name;
 
+    @Override
+    public String[] array() {
+        return ARRAYS;
+    }
+
 }

+ 42 - 0
yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/demo/PayDemoWithdrawStatusEnum.java

@@ -0,0 +1,42 @@
+package cn.iocoder.yudao.module.pay.enums.demo;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Objects;
+
+/**
+ * 示例提现单的状态枚举
+ *
+ * @author jason
+ */
+@Getter
+@AllArgsConstructor
+public enum PayDemoWithdrawStatusEnum {
+
+    WAITING(0, "等待提现"),
+    SUCCESS(10, "提现成功"),
+    CLOSED(20, "提现关闭");
+
+    /**
+     * 状态
+     */
+    private final Integer status;
+    /**
+     * 状态名
+     */
+    private final String name;
+
+    public static boolean isSuccess(Integer status) {
+        return Objects.equals(status, SUCCESS.getStatus());
+    }
+
+    public static boolean isClosed(Integer status) {
+        return Objects.equals(status, CLOSED.getStatus());
+    }
+
+    public static boolean isWaiting(Integer status) {
+        return Objects.equals(status, WAITING.getStatus());
+    }
+
+}

+ 39 - 0
yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/demo/PayDemoWithdrawTypeEnum.java

@@ -0,0 +1,39 @@
+package cn.iocoder.yudao.module.pay.enums.demo;
+
+import cn.iocoder.yudao.framework.common.core.ArrayValuable;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Arrays;
+
+/**
+ * 示例提现单的类型枚举
+ *
+ * @author owen
+ */
+@AllArgsConstructor
+@Getter
+public enum PayDemoWithdrawTypeEnum implements ArrayValuable<Integer> {
+
+    WECHAT(2, "微信"),
+    ALIPAY(1, "支付宝"),
+    WALLET(3, "钱包"),
+    ;
+
+    public static final Integer[] ARRAYS = Arrays.stream(values()).map(PayDemoWithdrawTypeEnum::getType).toArray(Integer[]::new);
+
+    /**
+     * 类型
+     */
+    private final Integer type;
+    /**
+     * 名字
+     */
+    private final String name;
+
+    @Override
+    public Integer[] array() {
+        return ARRAYS;
+    }
+
+}

+ 22 - 14
yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/transfer/PayTransferStatusEnum.java

@@ -6,6 +6,8 @@ import lombok.Getter;
 import java.util.Objects;
 
 /**
+ * 渠道的转账状态枚举
+ *
  * @author jason
  */
 @Getter
@@ -13,16 +15,9 @@ import java.util.Objects;
 public enum PayTransferStatusEnum {
 
     WAITING(0, "等待转账"),
-    /**
-     * TODO 转账到银行卡. 会有T+0 T+1 到账的请情况。 还未实现
-     */
-    IN_PROGRESS(10, "转账进行中"),
-
-    SUCCESS(20, "转账成功"),
-    /**
-     * 转账关闭 (失败,或者其它情况) // TODO 改成 转账失败状态
-     */
-    CLOSED(30, "转账关闭");
+    PROCESSING(5, "转账进行中"),
+    SUCCESS(10, "转账成功"),
+    CLOSED(20, "转账关闭");
 
     /**
      * 状态
@@ -44,16 +39,29 @@ public enum PayTransferStatusEnum {
     public static boolean isWaiting(Integer status) {
         return Objects.equals(status, WAITING.getStatus());
     }
-    public static boolean isInProgress(Integer status) {
-        return Objects.equals(status, IN_PROGRESS.getStatus());
+
+    public static boolean isProgressing(Integer status) {
+        return Objects.equals(status, PROCESSING.getStatus());
     }
 
     /**
      * 是否处于待转账或者转账中的状态
+     *
+     * @param status 状态
+     * @return 是否
+     */
+    public static boolean isWaitingOrProcessing(Integer status) {
+        return isWaiting(status) || isProgressing(status);
+    }
+
+    /**
+     * 是否处于成功或者关闭中的状态
+     *
      * @param status 状态
+     * @return 是否
      */
-    public static boolean isPendingStatus(Integer status) {
-        return Objects.equals(status, WAITING.getStatus()) || Objects.equals(status, IN_PROGRESS.getStatus());
+    public static boolean isSuccessOrClosed(Integer status) {
+        return isSuccess(status) || isClosed(status);
     }
 
 }

+ 0 - 44
yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/transfer/PayTransferTypeEnum.java

@@ -1,44 +0,0 @@
-package cn.iocoder.yudao.module.pay.enums.transfer;
-
-import cn.hutool.core.util.ArrayUtil;
-import cn.iocoder.yudao.framework.common.core.ArrayValuable;
-import lombok.AllArgsConstructor;
-import lombok.Getter;
-
-import java.util.Arrays;
-
-/**
- * 转账类型枚举
- *
- * @author jason
- */
-@AllArgsConstructor
-@Getter
-public enum PayTransferTypeEnum implements ArrayValuable<Integer> {
-
-    ALIPAY_BALANCE(1, "支付宝余额"),
-    WX_BALANCE(2, "微信余额"),
-    BANK_CARD(3, "银行卡"),
-    WALLET_BALANCE(4, "钱包余额");
-
-    public interface WxPay {
-    }
-
-    public interface Alipay {
-    }
-
-    private final Integer type;
-    private final String name;
-
-    public static final Integer[] ARRAYS = Arrays.stream(values()).map(PayTransferTypeEnum::getType).toArray(Integer[]::new);
-
-    @Override
-    public Integer[] array() {
-        return ARRAYS;
-    }
-
-    public static PayTransferTypeEnum typeOf(Integer type) {
-        return ArrayUtil.firstMatch(item -> item.getType().equals(type), values());
-    }
-
-}

+ 1 - 1
yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/wallet/PayWalletBizTypeEnum.java

@@ -20,7 +20,7 @@ public enum PayWalletBizTypeEnum implements ArrayValuable<Integer> {
     PAYMENT(3, "支付"),
     PAYMENT_REFUND(4, "支付退款"),
     UPDATE_BALANCE(5, "更新余额"),
-    BROKERAGE_WITHDRAW(6, "分佣提现");
+    TRANSFER(6, "转账");
 
     /**
      * 业务分类

+ 6 - 5
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/api/refund/PayRefundApiImpl.java

@@ -1,14 +1,14 @@
 package cn.iocoder.yudao.module.pay.api.refund;
 
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
 import cn.iocoder.yudao.module.pay.api.refund.dto.PayRefundCreateReqDTO;
 import cn.iocoder.yudao.module.pay.api.refund.dto.PayRefundRespDTO;
-import cn.iocoder.yudao.module.pay.convert.refund.PayRefundConvert;
+import cn.iocoder.yudao.module.pay.dal.dataobject.refund.PayRefundDO;
 import cn.iocoder.yudao.module.pay.service.refund.PayRefundService;
+import jakarta.annotation.Resource;
 import org.springframework.stereotype.Service;
 import org.springframework.validation.annotation.Validated;
 
-import javax.annotation.Resource;
-
 /**
  * 退款单 API 实现类
  *
@@ -23,12 +23,13 @@ public class PayRefundApiImpl implements PayRefundApi {
 
     @Override
     public Long createRefund(PayRefundCreateReqDTO reqDTO) {
-        return payRefundService.createPayRefund(reqDTO);
+        return payRefundService.createRefund(reqDTO);
     }
 
     @Override
     public PayRefundRespDTO getRefund(Long id) {
-        return PayRefundConvert.INSTANCE.convert02(payRefundService.getRefund(id));
+        PayRefundDO refund = payRefundService.getRefund(id);
+        return BeanUtils.toBean(refund, PayRefundRespDTO.class);
     }
 
 }

+ 16 - 2
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/api/transfer/PayTransferApiImpl.java

@@ -1,9 +1,13 @@
 package cn.iocoder.yudao.module.pay.api.transfer;
 
 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.framework.pay.core.client.impl.weixin.WxPayClientConfig;
 import cn.iocoder.yudao.module.pay.api.transfer.dto.PayTransferCreateReqDTO;
+import cn.iocoder.yudao.module.pay.api.transfer.dto.PayTransferCreateRespDTO;
 import cn.iocoder.yudao.module.pay.api.transfer.dto.PayTransferRespDTO;
+import cn.iocoder.yudao.module.pay.dal.dataobject.channel.PayChannelDO;
 import cn.iocoder.yudao.module.pay.dal.dataobject.transfer.PayTransferDO;
+import cn.iocoder.yudao.module.pay.service.channel.PayChannelService;
 import cn.iocoder.yudao.module.pay.service.transfer.PayTransferService;
 import org.springframework.stereotype.Service;
 import org.springframework.validation.annotation.Validated;
@@ -21,16 +25,26 @@ public class PayTransferApiImpl implements PayTransferApi {
 
     @Resource
     private PayTransferService payTransferService;
+    @Resource
+    private PayChannelService payChannelService;
 
     @Override
-    public Long createTransfer(PayTransferCreateReqDTO reqDTO) {
+    public PayTransferCreateRespDTO createTransfer(PayTransferCreateReqDTO reqDTO) {
         return payTransferService.createTransfer(reqDTO);
     }
 
     @Override
     public PayTransferRespDTO getTransfer(Long id) {
         PayTransferDO transfer = payTransferService.getTransfer(id);
-        return BeanUtils.toBean(transfer, PayTransferRespDTO.class);
+        if (transfer == null) {
+            return null;
+        }
+        PayChannelDO channel = payChannelService.getChannel(transfer.getChannelId());
+        String mchId = null;
+        if (channel != null && channel.getConfig() instanceof WxPayClientConfig) {
+            mchId = ((WxPayClientConfig) channel.getConfig()).getMchId();
+        }
+        return BeanUtils.toBean(transfer, PayTransferRespDTO.class).setChannelMchId(mchId);
     }
 
 }

+ 8 - 0
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/api/wallet/PayWalletApiImpl.java

@@ -1,7 +1,9 @@
 package cn.iocoder.yudao.module.pay.api.wallet;
 
 import cn.hutool.core.lang.Assert;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
 import cn.iocoder.yudao.module.pay.api.wallet.dto.PayWalletAddBalanceReqDTO;
+import cn.iocoder.yudao.module.pay.api.wallet.dto.PayWalletRespDTO;
 import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletDO;
 import cn.iocoder.yudao.module.pay.enums.wallet.PayWalletBizTypeEnum;
 import cn.iocoder.yudao.module.pay.service.wallet.PayWalletService;
@@ -31,4 +33,10 @@ public class PayWalletApiImpl implements PayWalletApi {
         payWalletService.addWalletBalance(wallet.getId(), reqDTO.getBizId(), bizType, reqDTO.getPrice());
     }
 
+    @Override
+    public PayWalletRespDTO getOrCreateWallet(Long userId, Integer userType) {
+        PayWalletDO wallet = payWalletService.getOrCreateWallet(userId, userType);
+        return BeanUtils.toBean(wallet, PayWalletRespDTO.class);
+    }
+
 }

+ 9 - 8
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/PayDemoOrderController.java

@@ -3,28 +3,27 @@ package cn.iocoder.yudao.module.pay.controller.admin.demo;
 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
 import cn.iocoder.yudao.framework.common.pojo.PageParam;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
 import cn.iocoder.yudao.module.pay.api.notify.dto.PayOrderNotifyReqDTO;
 import cn.iocoder.yudao.module.pay.api.notify.dto.PayRefundNotifyReqDTO;
 import cn.iocoder.yudao.module.pay.controller.admin.demo.vo.order.PayDemoOrderCreateReqVO;
 import cn.iocoder.yudao.module.pay.controller.admin.demo.vo.order.PayDemoOrderRespVO;
-import cn.iocoder.yudao.module.pay.convert.demo.PayDemoOrderConvert;
 import cn.iocoder.yudao.module.pay.dal.dataobject.demo.PayDemoOrderDO;
 import cn.iocoder.yudao.module.pay.service.demo.PayDemoOrderService;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.Parameter;
 import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import jakarta.annotation.security.PermitAll;
+import jakarta.validation.Valid;
 import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.*;
 
-import javax.annotation.Resource;
-import javax.annotation.security.PermitAll;
-import javax.validation.Valid;
-
 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
 import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getClientIP;
 import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
 
-@Tag(name = "管理后台 - 示例订单")
+@Tag(name = "管理后台 - 示例订单") // 目的:演示支付、退款功能
 @RestController
 @RequestMapping("/pay/demo-order")
 @Validated
@@ -43,7 +42,7 @@ public class PayDemoOrderController {
     @Operation(summary = "获得示例订单分页")
     public CommonResult<PageResult<PayDemoOrderRespVO>> getDemoOrderPage(@Valid PageParam pageVO) {
         PageResult<PayDemoOrderDO> pageResult = payDemoOrderService.getDemoOrderPage(pageVO);
-        return success(PayDemoOrderConvert.INSTANCE.convertPage(pageResult));
+        return success(BeanUtils.toBean(pageResult, PayDemoOrderRespVO.class));
     }
 
     @PostMapping("/update-paid")
@@ -67,7 +66,9 @@ public class PayDemoOrderController {
     @Operation(summary = "更新示例订单为已退款") // 由 pay-module 支付服务,进行回调,可见 PayNotifyJob
     @PermitAll // 无需登录,安全由 PayDemoOrderService 内部校验实现
     public CommonResult<Boolean> updateDemoOrderRefunded(@RequestBody PayRefundNotifyReqDTO notifyReqDTO) {
-        payDemoOrderService.updateDemoOrderRefunded(Long.valueOf(notifyReqDTO.getMerchantOrderId()),
+        payDemoOrderService.updateDemoOrderRefunded(
+                Long.valueOf(notifyReqDTO.getMerchantOrderId()),
+                notifyReqDTO.getMerchantRefundId(),
                 notifyReqDTO.getPayRefundId());
         return success(true);
     }

+ 0 - 52
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/PayDemoTransferController.java

@@ -1,52 +0,0 @@
-package cn.iocoder.yudao.module.pay.controller.admin.demo;
-
-import cn.iocoder.yudao.framework.common.pojo.CommonResult;
-import cn.iocoder.yudao.framework.common.pojo.PageParam;
-import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.module.pay.api.notify.dto.PayTransferNotifyReqDTO;
-import cn.iocoder.yudao.module.pay.controller.admin.demo.vo.transfer.PayDemoTransferCreateReqVO;
-import cn.iocoder.yudao.module.pay.controller.admin.demo.vo.transfer.PayDemoTransferRespVO;
-import cn.iocoder.yudao.module.pay.convert.demo.PayDemoTransferConvert;
-import cn.iocoder.yudao.module.pay.dal.dataobject.demo.PayDemoTransferDO;
-import cn.iocoder.yudao.module.pay.service.demo.PayDemoTransferService;
-import io.swagger.v3.oas.annotations.Operation;
-import io.swagger.v3.oas.annotations.tags.Tag;
-import org.springframework.validation.annotation.Validated;
-import org.springframework.web.bind.annotation.*;
-
-import javax.annotation.Resource;
-import javax.annotation.security.PermitAll;
-import javax.validation.Valid;
-
-import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
-
-@Tag(name = "管理后台 - 示例转账单")
-@RestController
-@RequestMapping("/pay/demo-transfer")
-@Validated
-public class PayDemoTransferController {
-    @Resource
-    private PayDemoTransferService demoTransferService;
-
-    @PostMapping("/create")
-    @Operation(summary = "创建示例转账订单")
-    public CommonResult<Long> createDemoTransfer(@Valid @RequestBody PayDemoTransferCreateReqVO createReqVO) {
-        return success(demoTransferService.createDemoTransfer(createReqVO));
-    }
-
-    @GetMapping("/page")
-    @Operation(summary = "获得示例转账订单分页")
-    public CommonResult<PageResult<PayDemoTransferRespVO>> getDemoTransferPage(@Valid PageParam pageVO) {
-        PageResult<PayDemoTransferDO> pageResult = demoTransferService.getDemoTransferPage(pageVO);
-        return success(PayDemoTransferConvert.INSTANCE.convertPage(pageResult));
-    }
-
-    @PostMapping("/update-status")
-    @Operation(summary = "更新示例转账订单的转账状态") // 由 pay-module 转账服务,进行回调
-    @PermitAll // 无需登录,安全由 PayDemoTransferService 内部校验实现
-    public CommonResult<Boolean> updateDemoTransferStatus(@RequestBody PayTransferNotifyReqDTO notifyReqDTO) {
-        demoTransferService.updateDemoTransferStatus(Long.valueOf(notifyReqDTO.getMerchantTransferId()),
-                notifyReqDTO.getPayTransferId());
-        return success(true);
-    }
-}

+ 50 - 0
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/PayDemoWithdrawController.http

@@ -0,0 +1,50 @@
+### 请求 /pay/demo-withdraw/create 接口(支付宝)
+POST {{baseUrl}}/pay/demo-withdraw/create
+Authorization: Bearer {{token}}
+Content-Type: application/json
+tenant-id: {{adminTenantId}}
+
+{
+  "type": 1,
+  "subject": "测试转账",
+  "price": 10,
+  "userAccount": "oespxk7368@sandbox.com",
+  "userName": "oespxk7368"
+}
+
+### 请求 /pay/demo-withdraw/create 接口(微信余额)
+POST {{baseUrl}}/pay/demo-withdraw/create
+Authorization: Bearer {{token}}
+Content-Type: application/json
+tenant-id: {{adminTenantId}}
+
+{
+  "type": 2,
+  "subject": "测试转账",
+  "price": 1,
+  "userAccount": "oiSC85elO_OZogXODC5RoGyXamK4",
+  "userName": "芋艿"
+}
+
+### 请求 /pay/demo-withdraw/create 接口(钱包余额)
+POST {{baseUrl}}/pay/demo-withdraw/create
+Authorization: Bearer {{token}}
+Content-Type: application/json
+tenant-id: {{adminTenantId}}
+
+{
+  "type": 3,
+  "subject": "测试转账",
+  "price": 1,
+  "userAccount": "1"
+}
+
+### 请求 /pay/demo-withdraw/transfer 接口(发起转账)
+POST {{baseUrl}}/pay/demo-withdraw/transfer?id=1
+Authorization: Bearer {{token}}
+tenant-id: {{adminTenantId}}
+
+### 请求 /pay/demo-withdraw/page 接口(查询分页)
+GET {{baseUrl}}/pay/demo-withdraw/page?pageNo=1&pageSize=10
+Authorization: Bearer {{token}}
+tenant-id: {{adminTenantId}}

+ 63 - 0
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/PayDemoWithdrawController.java

@@ -0,0 +1,63 @@
+package cn.iocoder.yudao.module.pay.controller.admin.demo;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.pay.api.notify.dto.PayTransferNotifyReqDTO;
+import cn.iocoder.yudao.module.pay.controller.admin.demo.vo.withdraw.PayDemoWithdrawCreateReqVO;
+import cn.iocoder.yudao.module.pay.controller.admin.demo.vo.withdraw.PayDemoWithdrawRespVO;
+import cn.iocoder.yudao.module.pay.dal.dataobject.demo.PayDemoWithdrawDO;
+import cn.iocoder.yudao.module.pay.service.demo.PayDemoWithdrawService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import jakarta.annotation.security.PermitAll;
+import jakarta.validation.Valid;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+@Tag(name = "管理后台 - 示例提现订单") // 目的:演示转账功能
+@RestController
+@RequestMapping("/pay/demo-withdraw")
+@Validated
+public class PayDemoWithdrawController {
+
+    @Resource
+    private PayDemoWithdrawService demoWithdrawService;
+
+    @PostMapping("/create")
+    @Operation(summary = "创建示例提现单")
+    public CommonResult<Long> createDemoWithdraw(@Valid @RequestBody PayDemoWithdrawCreateReqVO createReqVO) {
+        Long id = demoWithdrawService.createDemoWithdraw(createReqVO);
+        return success(id);
+    }
+
+    @PostMapping("/transfer")
+    @Operation(summary = "提现单转账")
+    @Parameter(name = "id", required = true, description = "提现单编号", example = "1024")
+    public CommonResult<Long> transferDemoWithdraw(@RequestParam("id") Long id) {
+        Long payTransferId = demoWithdrawService.transferDemoWithdraw(id);
+        return success(payTransferId);
+    }
+
+    @GetMapping("/page")
+    @Operation(summary = "获得示例提现单分页")
+    public CommonResult<PageResult<PayDemoWithdrawRespVO>> getDemoWithdrawPage(@Valid PageParam pageVO) {
+        PageResult<PayDemoWithdrawDO> pageResult = demoWithdrawService.getDemoWithdrawPage(pageVO);
+        return success(BeanUtils.toBean(pageResult, PayDemoWithdrawRespVO.class));
+    }
+
+    @PostMapping("/update-transferred")
+    @Operation(summary = "更新示例提现单的转账状态") // 由 pay-module 转账服务,进行回调
+    @PermitAll // 无需登录,安全由 PayDemoTransferService 内部校验实现
+    public CommonResult<Boolean> updateDemoWithdrawTransferred(@RequestBody PayTransferNotifyReqDTO notifyReqDTO) {
+        demoWithdrawService.updateDemoWithdrawTransferred(Long.valueOf(notifyReqDTO.getMerchantTransferId()),
+                notifyReqDTO.getPayTransferId());
+        return success(true);
+    }
+
+}

+ 0 - 67
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/vo/transfer/PayDemoTransferCreateReqVO.java

@@ -1,67 +0,0 @@
-package cn.iocoder.yudao.module.pay.controller.admin.demo.vo.transfer;
-
-import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils;
-import cn.iocoder.yudao.framework.common.validation.InEnum;
-import cn.iocoder.yudao.framework.pay.core.enums.transfer.PayTransferTypeEnum;
-import io.swagger.v3.oas.annotations.media.Schema;
-import lombok.Data;
-
-import javax.validation.Validator;
-import javax.validation.constraints.Min;
-import javax.validation.constraints.NotBlank;
-import javax.validation.constraints.NotNull;
-
-import static cn.iocoder.yudao.module.pay.enums.transfer.PayTransferTypeEnum.*;
-
-/**
- * @author jason
- */
-@Schema(description = "管理后台 - 示例转账单创建 Request VO")
-@Data
-public class PayDemoTransferCreateReqVO {
-
-    @Schema(description = "转账类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
-    @NotNull(message = "转账类型不能为空")
-    @InEnum(PayTransferTypeEnum.class)
-    private Integer type;
-
-    @Schema(description = "转账金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
-    @NotNull(message = "转账金额不能为空")
-    @Min(value = 1, message = "转账金额必须大于零")
-    private Integer price;
-
-    @Schema(description = "收款人姓名", example = "test1")
-    @NotBlank(message = "收款人姓名不能为空", groups = {Alipay.class})
-    private String userName;
-
-    // ========== 支付宝转账相关字段 ==========
-    @Schema(description = "支付宝登录号,支持邮箱和手机号格式", example = "test1@@sandbox.com")
-    @NotBlank(message = "支付宝登录号不能为空", groups = {Alipay.class})
-    private String alipayLogonId;
-
-    // ========== 微信转账相关字段 ==========
-    @Schema(description = "微信 openId", example = "oLefc4g5Gxx")
-    @NotBlank(message = "微信 openId 不能为空", groups = {WxPay.class})
-    private String openid;
-
-
-    // ========== 转账到银行卡和钱包相关字段 待补充 ==========
-
-    public void validate(Validator validator) {
-        PayTransferTypeEnum transferType = PayTransferTypeEnum.typeOf(type);
-        switch (transferType) {
-            case ALIPAY_BALANCE: {
-                ValidationUtils.validate(validator, this, Alipay.class);
-                break;
-            }
-            case WX_BALANCE: {
-                ValidationUtils.validate(validator, this, WxPay.class);
-                break;
-            }
-            default: {
-                throw new UnsupportedOperationException("待实现");
-            }
-        }
-    }
-
-}

+ 0 - 47
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/vo/transfer/PayDemoTransferRespVO.java

@@ -1,47 +0,0 @@
-package cn.iocoder.yudao.module.pay.controller.admin.demo.vo.transfer;
-
-import io.swagger.v3.oas.annotations.media.Schema;
-import lombok.Data;
-
-import java.time.LocalDateTime;
-
-/**
- * 示例业务转账订单 Base VO,提供给添加、修改、详细的子 VO 使用
- * 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
- */
-@Data
-public class PayDemoTransferRespVO {
-
-    @Schema(description = "订单编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
-    private Long id;
-
-    @Schema(description = "应用编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
-    private Long appId;
-
-    @Schema(description = "转账金额,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "22338")
-    private Integer price;
-
-    @Schema(description = "转账类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
-    private Integer type;
-
-    @Schema(description = "收款人姓名",  example = "test")
-    private String userName;
-
-    @Schema(description = "支付宝登录号", example = "32167")
-    private String alipayLogonId;
-
-    @Schema(description = "微信 openId", example = "31139")
-    private String openid;
-
-    @Schema(description = "转账状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
-    private Integer transferStatus;
-
-    @Schema(description = "转账订单编号", example = "23695")
-    private Long payTransferId;
-
-    @Schema(description = "转账支付成功渠道")
-    private String payChannelCode;
-
-    @Schema(description = "转账支付时间")
-    private LocalDateTime transferTime;
-}

+ 42 - 0
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/vo/withdraw/PayDemoWithdrawCreateReqVO.java

@@ -0,0 +1,42 @@
+package cn.iocoder.yudao.module.pay.controller.admin.demo.vo.withdraw;
+
+import cn.hutool.core.util.ObjectUtil;
+import cn.iocoder.yudao.framework.common.validation.InEnum;
+import cn.iocoder.yudao.module.pay.enums.demo.PayDemoWithdrawTypeEnum;
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.*;
+import lombok.Data;
+
+@Schema(description = "管理后台 - 示例提现单创建 Request VO")
+@Data
+public class PayDemoWithdrawCreateReqVO {
+
+    @Schema(description = "提现标题", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋艿是一种菜")
+    @NotEmpty(message = "提现标题不能为空")
+    private String subject;
+
+    @Schema(description = "提现金额,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
+    @NotNull(message = "提现金额不能为空")
+    @Min(value = 1, message = "提现金额必须大于零")
+    private Integer price;
+
+    @Schema(description = "收款人账号", requiredMode= Schema.RequiredMode.REQUIRED, example = "test1")
+    @NotBlank(message = "收款人账号不能为空")
+    private String userAccount;
+
+    @Schema(description = "收款人姓名", example = "test1")
+    private String userName;
+
+    @Schema(description = "提现方式", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @NotNull(message = "提现方式不能为空")
+    @InEnum(PayDemoWithdrawTypeEnum.class)
+    private Integer type;
+
+    @AssertTrue(message = "收款人姓名")
+    public boolean isUserNameValid() {
+        // 特殊:支付宝必须填写用户名!!!
+        return ObjectUtil.notEqual(type, PayDemoWithdrawTypeEnum.ALIPAY.getType())
+                || ObjectUtil.isNotEmpty(userName);
+    }
+
+}

+ 47 - 0
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/vo/withdraw/PayDemoWithdrawRespVO.java

@@ -0,0 +1,47 @@
+package cn.iocoder.yudao.module.pay.controller.admin.demo.vo.withdraw;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+@Schema(description = "管理后台 - 示例转账单创建 Request VO")
+@Data
+public class PayDemoWithdrawRespVO {
+
+    @Schema(description = "转账单编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+    private Long id;
+
+    @Schema(description = "提现标题", requiredMode = Schema.RequiredMode.REQUIRED, example = "吃饭报销")
+    private String subject;
+
+    @Schema(description = "提现金额,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "22338")
+    private Integer price;
+
+    @Schema(description = "收款人姓名",  example = "test")
+    private String userName;
+
+    @Schema(description = "收款人账号", example = "32167")
+    private String userAccount;
+
+    @Schema(description = "提现类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    private Integer type;
+
+    @Schema(description = "提现状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
+    private Integer status;
+
+    // ========== 转账相关字段 ==========
+
+    @Schema(description = "转账单编号", example = "23695")
+    private Long payTransferId;
+
+    @Schema(description = "转账渠道", example = "wx_lite")
+    private String transferChannelCode;
+
+    @Schema(description = "转账成功时间")
+    private LocalDateTime transferTime;
+
+    @Schema(description = "转账失败原因", example = "IP 不正确")
+    private String transferErrorMsg;
+
+}

+ 16 - 4
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/notify/PayNotifyController.java

@@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.pay.controller.admin.notify;
 import cn.hutool.core.collection.CollUtil;
 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
 import cn.iocoder.yudao.framework.pay.core.client.PayClient;
 import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderRespDTO;
 import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundRespDTO;
@@ -11,7 +12,6 @@ import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
 import cn.iocoder.yudao.module.pay.controller.admin.notify.vo.PayNotifyTaskDetailRespVO;
 import cn.iocoder.yudao.module.pay.controller.admin.notify.vo.PayNotifyTaskPageReqVO;
 import cn.iocoder.yudao.module.pay.controller.admin.notify.vo.PayNotifyTaskRespVO;
-import cn.iocoder.yudao.module.pay.convert.notify.PayNotifyTaskConvert;
 import cn.iocoder.yudao.module.pay.dal.dataobject.app.PayAppDO;
 import cn.iocoder.yudao.module.pay.dal.dataobject.notify.PayNotifyLogDO;
 import cn.iocoder.yudao.module.pay.dal.dataobject.notify.PayNotifyTaskDO;
@@ -138,7 +138,12 @@ public class PayNotifyController {
         // 拼接返回
         PayAppDO app = appService.getApp(task.getAppId());
         List<PayNotifyLogDO> logs = notifyService.getNotifyLogList(id);
-        return success(PayNotifyTaskConvert.INSTANCE.convert(task, app, logs));
+        return success(BeanUtils.toBean(task, PayNotifyTaskDetailRespVO.class, respVO -> {
+            if (app != null) {
+                respVO.setAppName(app.getName());
+            }
+            respVO.setLogs(BeanUtils.toBean(logs, PayNotifyTaskDetailRespVO.Log.class));
+        }));
     }
 
     @GetMapping("/page")
@@ -150,8 +155,15 @@ public class PayNotifyController {
             return success(PageResult.empty());
         }
         // 拼接返回
-        Map<Long, PayAppDO> appMap = appService.getAppMap(convertList(pageResult.getList(), PayNotifyTaskDO::getAppId));
-        return success(PayNotifyTaskConvert.INSTANCE.convertPage(pageResult, appMap));
+        Map<Long, PayAppDO> apps = appService.getAppMap(convertList(pageResult.getList(), PayNotifyTaskDO::getAppId));
+        
+        // 转换对象
+        return success(BeanUtils.toBean(pageResult, PayNotifyTaskRespVO.class, order -> {
+            PayAppDO app = apps.get(order.getAppId());
+            if (app != null) {
+                order.setAppName(app.getName());
+            }
+        }));
     }
 
 }

+ 0 - 45
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/notify/vo/PayNotifyTaskBaseVO.java

@@ -1,45 +0,0 @@
-package cn.iocoder.yudao.module.pay.controller.admin.notify.vo;
-
-import io.swagger.v3.oas.annotations.media.Schema;
-import lombok.Data;
-
-import java.time.LocalDateTime;
-
-/**
- * 回调通知 Base VO,提供给添加、修改、详细的子 VO 使用
- * 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
- */
-@Data
-public class PayNotifyTaskBaseVO {
-
-    @Schema(description = "应用编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10636")
-    private Long appId;
-
-    @Schema(description = "通知类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
-    private Byte type;
-
-    @Schema(description = "数据编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "6722")
-    private Long dataId;
-
-    @Schema(description = "通知状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
-    private Byte status;
-
-    @Schema(description = "商户订单编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "26697")
-    private String merchantOrderId;
-
-    @Schema(description = "下一次通知时间", requiredMode = Schema.RequiredMode.REQUIRED)
-    private LocalDateTime nextNotifyTime;
-
-    @Schema(description = "最后一次执行时间", requiredMode = Schema.RequiredMode.REQUIRED)
-    private LocalDateTime lastExecuteTime;
-
-    @Schema(description = "当前通知次数", requiredMode = Schema.RequiredMode.REQUIRED)
-    private Byte notifyTimes;
-
-    @Schema(description = "最大可通知次数", requiredMode = Schema.RequiredMode.REQUIRED)
-    private Byte maxNotifyTimes;
-
-    @Schema(description = "异步通知地址", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn")
-    private String notifyUrl;
-
-}

+ 1 - 13
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/notify/vo/PayNotifyTaskDetailRespVO.java

@@ -13,19 +13,7 @@ import java.util.List;
 @Data
 @EqualsAndHashCode(callSuper = true)
 @ToString(callSuper = true)
-public class PayNotifyTaskDetailRespVO extends PayNotifyTaskBaseVO {
-
-    @Schema(description = "任务编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "3380")
-    private Long id;
-
-    @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
-    private LocalDateTime createTime;
-
-    @Schema(description = "更新时间", requiredMode = Schema.RequiredMode.REQUIRED)
-    private LocalDateTime updateTime;
-
-    @Schema(description = "应用名称", example = "wx_pay")
-    private String appName;
+public class PayNotifyTaskDetailRespVO extends PayNotifyTaskRespVO {
 
     @Schema(description = "回调日志列表")
     private List<Log> logs;

+ 6 - 0
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/notify/vo/PayNotifyTaskPageReqVO.java

@@ -32,6 +32,12 @@ public class PayNotifyTaskPageReqVO extends PageParam {
     @Schema(description = "商户订单编号", example = "26697")
     private String merchantOrderId;
 
+    @Schema(description = "商户退款编号", example = "26697")
+    private String merchantRefundId;
+
+    @Schema(description = "商户转账编号", example = "26697")
+    private String merchantTransferId;
+
     @Schema(description = "创建时间")
     @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
     private LocalDateTime[] createTime;

+ 39 - 5
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/notify/vo/PayNotifyTaskRespVO.java

@@ -6,17 +6,51 @@ import java.time.LocalDateTime;
 
 @Schema(description = "管理后台 - 回调通知 Response VO")
 @Data
-@EqualsAndHashCode(callSuper = true)
-@ToString(callSuper = true)
-public class PayNotifyTaskRespVO extends PayNotifyTaskBaseVO {
+public class PayNotifyTaskRespVO {
 
     @Schema(description = "任务编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "3380")
     private Long id;
 
-    @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
-    private LocalDateTime createTime;
+    @Schema(description = "应用编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10636")
+    private Long appId;
 
     @Schema(description = "应用名称", example = "wx_pay")
     private String  appName;
 
+    @Schema(description = "通知类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
+    private Byte type;
+
+    @Schema(description = "数据编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "6722")
+    private Long dataId;
+
+    @Schema(description = "商户订单编号", example = "26697")
+    private String merchantOrderId;
+
+    @Schema(description = "商户退款编号", example = "26697")
+    private String merchantRefundId;
+
+    @Schema(description = "商户转账编号", example = "26697")
+    private String merchantTransferId;
+
+    @Schema(description = "通知状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    private Byte status;
+
+    @Schema(description = "下一次通知时间", requiredMode = Schema.RequiredMode.REQUIRED)
+    private LocalDateTime nextNotifyTime;
+
+    @Schema(description = "最后一次执行时间", requiredMode = Schema.RequiredMode.REQUIRED)
+    private LocalDateTime lastExecuteTime;
+
+    @Schema(description = "当前通知次数", requiredMode = Schema.RequiredMode.REQUIRED)
+    private Byte notifyTimes;
+
+    @Schema(description = "最大可通知次数", requiredMode = Schema.RequiredMode.REQUIRED)
+    private Byte maxNotifyTimes;
+
+    @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
+    private LocalDateTime createTime;
+
+    @Schema(description = "更新时间", requiredMode = Schema.RequiredMode.REQUIRED)
+    private LocalDateTime updateTime;
+
 }

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

@@ -12,10 +12,12 @@ import cn.iocoder.yudao.module.pay.convert.order.PayOrderConvert;
 import cn.iocoder.yudao.module.pay.dal.dataobject.app.PayAppDO;
 import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderDO;
 import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderExtensionDO;
+import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletDO;
 import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum;
 import cn.iocoder.yudao.module.pay.framework.pay.core.WalletPayClient;
 import cn.iocoder.yudao.module.pay.service.app.PayAppService;
 import cn.iocoder.yudao.module.pay.service.order.PayOrderService;
+import cn.iocoder.yudao.module.pay.service.wallet.PayWalletService;
 import com.google.common.collect.Maps;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.Parameter;
@@ -51,6 +53,8 @@ public class PayOrderController {
     private PayOrderService orderService;
     @Resource
     private PayAppService appService;
+    @Resource
+    private PayWalletService payWalletService;
 
     @GetMapping("/get")
     @Operation(summary = "获得支付订单")
@@ -92,11 +96,11 @@ public class PayOrderController {
     public CommonResult<PayOrderSubmitRespVO> submitPayOrder(@RequestBody PayOrderSubmitReqVO reqVO) {
         // 1. 钱包支付事,需要额外传 user_id 和 user_type
         if (Objects.equals(reqVO.getChannelCode(), PayChannelEnum.WALLET.getCode())) {
-            Map<String, String> channelExtras = reqVO.getChannelExtras() == null ?
-                    Maps.newHashMapWithExpectedSize(2) : reqVO.getChannelExtras();
-            channelExtras.put(WalletPayClient.USER_ID_KEY, String.valueOf(getLoginUserId()));
-            channelExtras.put(WalletPayClient.USER_TYPE_KEY, String.valueOf(getLoginUserType()));
-            reqVO.setChannelExtras(channelExtras);
+            if (reqVO.getChannelExtras() == null) {
+                reqVO.setChannelExtras(Maps.newHashMapWithExpectedSize(1));
+            }
+            PayWalletDO wallet = payWalletService.getOrCreateWallet(getLoginUserId(), getLoginUserType());
+            reqVO.getChannelExtras().put(WalletPayClient.WALLET_ID_KEY, String.valueOf(wallet.getId()));
         }
 
         // 2. 提交支付

+ 1 - 7
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/refund/PayRefundController.java

@@ -25,7 +25,6 @@ import javax.annotation.Resource;
 import javax.servlet.http.HttpServletResponse;
 import javax.validation.Valid;
 import java.io.IOException;
-import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
 
@@ -78,13 +77,8 @@ public class PayRefundController {
     @PreAuthorize("@ss.hasPermission('pay:refund:export')")
     @ApiAccessLog(operateType = EXPORT)
     public void exportRefundExcel(@Valid PayRefundExportReqVO exportReqVO,
-            HttpServletResponse response) throws IOException {
+                                  HttpServletResponse response) throws IOException {
         List<PayRefundDO> list = refundService.getRefundList(exportReqVO);
-        if (CollectionUtil.isEmpty(list)) {
-            ExcelUtils.write(response, "退款订单.xls", "数据",
-                    PayRefundExcelVO.class, new ArrayList<>());
-            return;
-        }
 
         // 拼接返回
         Map<Long, PayAppDO> appMap = appService.getAppMap(convertList(list, PayRefundDO::getAppId));

+ 56 - 17
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/transfer/PayTransferController.java

@@ -1,22 +1,35 @@
 package cn.iocoder.yudao.module.pay.controller.admin.transfer;
 
+import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog;
 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.module.pay.controller.admin.transfer.vo.*;
-import cn.iocoder.yudao.module.pay.convert.transfer.PayTransferConvert;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
+import cn.iocoder.yudao.module.pay.controller.admin.transfer.vo.PayTransferPageReqVO;
+import cn.iocoder.yudao.module.pay.controller.admin.transfer.vo.PayTransferRespVO;
+import cn.iocoder.yudao.module.pay.dal.dataobject.app.PayAppDO;
 import cn.iocoder.yudao.module.pay.dal.dataobject.transfer.PayTransferDO;
+import cn.iocoder.yudao.module.pay.service.app.PayAppService;
 import cn.iocoder.yudao.module.pay.service.transfer.PayTransferService;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.validation.Valid;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.validation.annotation.Validated;
-import org.springframework.web.bind.annotation.*;
+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 java.io.IOException;
+import java.util.Map;
 
+import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
-import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getClientIP;
+import static cn.iocoder.yudao.framework.common.pojo.PageParam.PAGE_SIZE_NONE;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
 
 @Tag(name = "管理后台 - 转账单")
 @RestController
@@ -26,27 +39,53 @@ public class PayTransferController {
 
     @Resource
     private PayTransferService payTransferService;
-
-    @PostMapping("/create")
-    @Operation(summary = "创建转账单,发起转账")
-    @PreAuthorize("@ss.hasPermission('pay:transfer:create')")
-    public CommonResult<PayTransferCreateRespVO> createPayTransfer(@Valid @RequestBody PayTransferCreateReqVO reqVO) {
-        PayTransferDO payTransfer = payTransferService.createTransfer(reqVO, getClientIP());
-        return success(new PayTransferCreateRespVO().setId(payTransfer.getId()).setStatus(payTransfer.getStatus()));
-    }
+    @Resource
+    private PayAppService payAppService;
 
     @GetMapping("/get")
     @Operation(summary = "获得转账订单")
     @PreAuthorize("@ss.hasPermission('pay:transfer:query')")
     public CommonResult<PayTransferRespVO> getTransfer(@RequestParam("id") Long id) {
-        return success(PayTransferConvert.INSTANCE.convert(payTransferService.getTransfer(id)));
+        PayTransferDO transfer = payTransferService.getTransfer(id);
+        if (transfer == null) {
+            return success(new PayTransferRespVO());
+        }
+
+        // 拼接数据
+        PayAppDO app = payAppService.getApp(transfer.getAppId());
+        return success(BeanUtils.toBean(transfer, PayTransferRespVO.class, transferVO -> {
+            if (app != null) {
+                transferVO.setAppName(app.getName());
+            }
+        }));
     }
 
     @GetMapping("/page")
     @Operation(summary = "获得转账订单分页")
     @PreAuthorize("@ss.hasPermission('pay:transfer:query')")
-    public CommonResult<PageResult<PayTransferPageItemRespVO>> getTransferPage(@Valid PayTransferPageReqVO pageVO) {
+    public CommonResult<PageResult<PayTransferRespVO>> getTransferPage(@Valid PayTransferPageReqVO pageVO) {
         PageResult<PayTransferDO> pageResult = payTransferService.getTransferPage(pageVO);
-        return success(PayTransferConvert.INSTANCE.convertPage(pageResult));
+
+        // 拼接数据
+        Map<Long, PayAppDO> apps = payAppService.getAppMap(convertList(pageResult.getList(), PayTransferDO::getAppId));
+        return success(BeanUtils.toBean(pageResult, PayTransferRespVO.class, transferVO -> {
+            if (apps.containsKey(transferVO.getAppId())) {
+                transferVO.setAppName(apps.get(transferVO.getAppId()).getName());
+            }
+        }));
     }
+
+    @GetMapping("/export-excel")
+    @Operation(summary = "导出转账订单 Excel")
+    @PreAuthorize("@ss.hasPermission('pay:transfer:export')")
+    @ApiAccessLog(operateType = EXPORT)
+    public void exportTransfer(PayTransferPageReqVO pageReqVO,
+                               HttpServletResponse response) throws IOException {
+        pageReqVO.setPageSize(PAGE_SIZE_NONE);
+        PageResult<PayTransferRespVO> pageResult = getTransferPage(pageReqVO).getData();
+
+        // 导出 Excel
+        ExcelUtils.write(response, "转账订单.xls", "数据", PayTransferRespVO.class, pageResult.getList());
+    }
+
 }

+ 0 - 95
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/transfer/vo/PayTransferCreateReqVO.java

@@ -1,95 +0,0 @@
-package cn.iocoder.yudao.module.pay.controller.admin.transfer.vo;
-
-import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils;
-import cn.iocoder.yudao.framework.common.validation.InEnum;
-import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum;
-import cn.iocoder.yudao.module.pay.enums.transfer.PayTransferTypeEnum;
-import io.swagger.v3.oas.annotations.media.Schema;
-import lombok.Data;
-
-import javax.validation.Validator;
-import javax.validation.constraints.*;
-import java.util.Map;
-
-import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.NOT_IMPLEMENTED;
-import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
-import static cn.iocoder.yudao.module.pay.enums.transfer.PayTransferTypeEnum.*;
-
-@Schema(description = "管理后台 - 发起转账 Request VO")
-@Data
-public class PayTransferCreateReqVO {
-
-    @Schema(description = "应用编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
-    @NotNull(message = "应用编号不能为空")
-    private Long appId;
-
-    @Schema(description = "商户转账单编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
-    @NotNull(message = "商户转账单编号不能为空")
-    private String merchantTransferId;
-
-    @Schema(description = "转账类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
-    @NotNull(message = "转账类型不能为空")
-    @InEnum(PayTransferTypeEnum.class)
-    private Integer type;
-
-    @Schema(description = "转账渠道", requiredMode = Schema.RequiredMode.REQUIRED, example = "alipay_pc")
-    @NotEmpty(message = "转账渠道不能为空")
-    private String channelCode;
-
-    @Min(value = 1, message = "转账金额必须大于零")
-    @NotNull(message = "转账金额不能为空")
-    private Integer price;
-
-    @Schema(description = "转账标题", requiredMode = Schema.RequiredMode.REQUIRED, example = "示例转账")
-    @NotEmpty(message = "转账标题不能为空")
-    private String subject;
-
-    @Schema(description = "收款人姓名", example = "test1")
-    @NotBlank(message = "收款人姓名不能为空", groups = {Alipay.class})
-    private String userName;
-
-    @Schema(description = "支付宝登录号",  example = "test1@sandbox.com")
-    @NotBlank(message = "支付宝登录号不能为空", groups = {Alipay.class})
-    private String alipayLogonId;
-
-    @Schema(description = "微信 openId",  example = "oLefc4g5Gxx")
-    @NotBlank(message = "微信 openId 不能为空", groups = {WxPay.class})
-    private String openid;
-
-    @Schema(description = "转账渠道的额外参数")
-    private Map<String, String> channelExtras;
-
-    public void validate(Validator validator) {
-       PayTransferTypeEnum transferType = typeOf(type);
-        switch (transferType) {
-            case ALIPAY_BALANCE: {
-                ValidationUtils.validate(validator, this, Alipay.class);
-                break;
-            }
-            case WX_BALANCE: {
-                ValidationUtils.validate(validator, this, WxPay.class);
-                break;
-            }
-            default: {
-                throw new UnsupportedOperationException("待实现");
-            }
-        }
-    }
-
-    @AssertTrue(message = "转账类型和转账渠道不匹配")
-    public boolean isValidChannelCode() {
-        PayTransferTypeEnum transferType = typeOf(type);
-        switch (transferType) {
-            case ALIPAY_BALANCE: {
-                return PayChannelEnum.isAlipay(channelCode);
-            }
-            case WX_BALANCE:
-            case BANK_CARD:
-            case WALLET_BALANCE: {
-                throw exception(NOT_IMPLEMENTED);
-            }
-        }
-        return Boolean.FALSE;
-    }
-
-}

+ 0 - 62
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/transfer/vo/PayTransferPageItemRespVO.java

@@ -1,62 +0,0 @@
-package cn.iocoder.yudao.module.pay.controller.admin.transfer.vo;
-
-import io.swagger.v3.oas.annotations.media.Schema;
-import lombok.Data;
-
-import java.time.LocalDateTime;
-
-/**
- * @author jason
- */
-@Schema(description = "管理后台 - 转账单分页项 Response VO")
-@Data
-public class PayTransferPageItemRespVO {
-
-    @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2931")
-    private Long id;
-
-    @Schema(description = "转账单号", requiredMode = Schema.RequiredMode.REQUIRED)
-    private String no;
-
-    @Schema(description = "应用编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "12831")
-    private Long appId;
-
-    @Schema(description = "转账渠道编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "24833")
-    private Long channelId;
-
-    @Schema(description = "转账渠道编码", requiredMode = Schema.RequiredMode.REQUIRED)
-    private String channelCode;
-
-    @Schema(description = "商户转账单编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "17481")
-    private String merchantTransferId;
-
-    @Schema(description = "类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
-    private Integer type;
-
-    @Schema(description = "转账状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
-    private Integer status;
-
-    @Schema(description = "转账成功时间")
-    private LocalDateTime successTime;
-
-    @Schema(description = "转账金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "964")
-    private Integer price;
-
-    @Schema(description = "转账标题", requiredMode = Schema.RequiredMode.REQUIRED)
-    private String subject;
-
-    @Schema(description = "收款人姓名", example = "王五")
-    private String userName;
-
-    @Schema(description = "支付宝登录号", example = "29245")
-    private String alipayLogonId;
-
-    @Schema(description = "微信 openId", example = "26589")
-    private String openid;
-
-    @Schema(description = "渠道转账单号")
-    private String channelTransferNo;
-
-    @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
-    private LocalDateTime createTime;
-}

+ 4 - 4
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/transfer/vo/PayTransferPageReqVO.java

@@ -24,10 +24,7 @@ public class PayTransferPageReqVO extends PageParam {
     private String channelCode;
 
     @Schema(description = "商户转账单编号", example = "17481")
-    private String merchantTransferId;
-
-    @Schema(description = "类型", example = "2")
-    private Integer type;
+    private String merchantOrderId;
 
     @Schema(description = "转账状态", example = "2")
     private Integer status;
@@ -35,6 +32,9 @@ public class PayTransferPageReqVO extends PageParam {
     @Schema(description = "收款人姓名", example = "王五")
     private String userName;
 
+    @Schema(description = "收款人账号", example = "26589")
+    private String userAccount;
+
     @Schema(description = "渠道转账单号")
     private String channelTransferNo;
 

+ 31 - 9
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/transfer/vo/PayTransferRespVO.java

@@ -1,5 +1,12 @@
 package cn.iocoder.yudao.module.pay.controller.admin.transfer.vo;
 
+import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
+import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
+import cn.iocoder.yudao.framework.excel.core.convert.MoneyConvert;
+import cn.iocoder.yudao.module.pay.enums.DictTypeConstants;
+
+import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
+import com.alibaba.excel.annotation.ExcelProperty;
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.*;
 import java.time.LocalDateTime;
@@ -7,53 +14,65 @@ import java.util.Map;
 
 @Schema(description = "管理后台 - 转账单 Response VO")
 @Data
+@ExcelIgnoreUnannotated
 public class PayTransferRespVO {
 
+    @ExcelProperty("转账单编号")
     @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2931")
     private Long id;
 
+    @ExcelProperty("转账单号")
     @Schema(description = "转账单号", requiredMode = Schema.RequiredMode.REQUIRED)
     private String no;
 
     @Schema(description = "应用编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "12831")
     private Long appId;
 
+    @ExcelProperty("应用名称")
+    @Schema(description = "应用名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋艿")
+    private String appName;
+
     @Schema(description = "转账渠道编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "24833")
     private Long channelId;
 
+    @ExcelProperty(value = "转账渠道", converter = DictConvert.class)
+    @DictFormat(DictTypeConstants.CHANNEL_CODE)
     @Schema(description = "转账渠道编码", requiredMode = Schema.RequiredMode.REQUIRED)
     private String channelCode;
 
+    @ExcelProperty("商户转账单编号")
     @Schema(description = "商户转账单编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "17481")
     private String merchantTransferId;
 
-    @Schema(description = "类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
-    private Integer type;
-
+    @ExcelProperty(value = "转账状态", converter = DictConvert.class)
+    @DictFormat(DictTypeConstants.TRANSFER_STATUS)
     @Schema(description = "转账状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
     private Integer status;
 
+    @ExcelProperty("转账成功时间")
     @Schema(description = "转账成功时间")
     private LocalDateTime successTime;
 
     @Schema(description = "转账金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "964")
+    @ExcelProperty(value = "转账金额", converter = MoneyConvert.class)
     private Integer price;
 
-    @Schema(description = "转账标题", requiredMode = Schema.RequiredMode.REQUIRED)
+    @ExcelProperty("转账标题")
+    @Schema(description = "转账标题", requiredMode = Schema.RequiredMode.REQUIRED, example = "冲冲冲!")
     private String subject;
 
     @Schema(description = "收款人姓名", example = "王五")
+    @ExcelProperty("收款人姓名")
     private String userName;
 
-    @Schema(description = "支付宝登录号", example = "29245")
-    private String alipayLogonId;
-
-    @Schema(description = "微信 openId", example = "26589")
-    private String openid;
+    @Schema(description = "收款人账号", requiredMode = Schema.RequiredMode.REQUIRED, example = "26589")
+    @ExcelProperty("收款人账号")
+    private String userAccount;
 
     @Schema(description = "异步通知商户地址", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn")
     private String notifyUrl;
 
+    @ExcelProperty("用户 IP")
     @Schema(description = "用户 IP", requiredMode = Schema.RequiredMode.REQUIRED)
     private String userIp;
 
@@ -61,11 +80,13 @@ public class PayTransferRespVO {
     private Map<String, String> channelExtras;
 
     @Schema(description = "渠道转账单号")
+    @ExcelProperty("渠道转账单号")
     private String channelTransferNo;
 
     @Schema(description = "调用渠道的错误码")
     private String channelErrorCode;
 
+    @ExcelProperty("渠道错误提示")
     @Schema(description = "调用渠道的错误提示")
     private String channelErrorMsg;
 
@@ -73,6 +94,7 @@ public class PayTransferRespVO {
     private String channelNotifyData;
 
     @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
+    @ExcelProperty("创建时间")
     private LocalDateTime createTime;
 
 }

+ 4 - 3
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/PayWalletRechargeController.java

@@ -37,8 +37,7 @@ public class PayWalletRechargeController {
         return success(true);
     }
 
-    // TODO @jason:发起退款,要 post 操作哈;
-    @GetMapping("/refund")
+    @PostMapping("/refund")
     @Operation(summary = "发起钱包充值退款")
     @Parameter(name = "id", description = "编号", required = true, example = "1024")
     public CommonResult<Boolean> refundWalletRecharge(@RequestParam("id") Long id) {
@@ -51,7 +50,9 @@ public class PayWalletRechargeController {
     @PermitAll // 无需登录, 内部校验实现
     public CommonResult<Boolean> updateWalletRechargeRefunded(@RequestBody PayRefundNotifyReqDTO notifyReqDTO) {
         walletRechargeService.updateWalletRechargeRefunded(
-                Long.valueOf(notifyReqDTO.getMerchantOrderId()), notifyReqDTO.getPayRefundId());
+                Long.valueOf(notifyReqDTO.getMerchantOrderId()),
+                Long.valueOf(notifyReqDTO.getMerchantRefundId()),
+                notifyReqDTO.getPayRefundId());
         return success(true);
     }
 

+ 10 - 7
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/order/AppPayOrderController.java

@@ -9,20 +9,21 @@ import cn.iocoder.yudao.module.pay.controller.app.order.vo.AppPayOrderSubmitReqV
 import cn.iocoder.yudao.module.pay.controller.app.order.vo.AppPayOrderSubmitRespVO;
 import cn.iocoder.yudao.module.pay.convert.order.PayOrderConvert;
 import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderDO;
+import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletDO;
 import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum;
 import cn.iocoder.yudao.module.pay.framework.pay.core.WalletPayClient;
 import cn.iocoder.yudao.module.pay.service.order.PayOrderService;
+import cn.iocoder.yudao.module.pay.service.wallet.PayWalletService;
 import com.google.common.collect.Maps;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.Parameter;
 import io.swagger.v3.oas.annotations.Parameters;
 import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.*;
 
-import javax.annotation.Resource;
-import java.util.Map;
 import java.util.Objects;
 
 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@@ -39,6 +40,8 @@ public class AppPayOrderController {
 
     @Resource
     private PayOrderService payOrderService;
+    @Resource
+    private PayWalletService payWalletService;
 
     @GetMapping("/get")
     @Operation(summary = "获得支付订单")
@@ -63,11 +66,11 @@ public class AppPayOrderController {
     public CommonResult<AppPayOrderSubmitRespVO> submitPayOrder(@RequestBody AppPayOrderSubmitReqVO reqVO) {
         // 1. 钱包支付事,需要额外传 user_id 和 user_type
         if (Objects.equals(reqVO.getChannelCode(), PayChannelEnum.WALLET.getCode())) {
-            Map<String, String> channelExtras = reqVO.getChannelExtras() == null ?
-                    Maps.newHashMapWithExpectedSize(2) : reqVO.getChannelExtras();
-            channelExtras.put(WalletPayClient.USER_ID_KEY, String.valueOf(getLoginUserId()));
-            channelExtras.put(WalletPayClient.USER_TYPE_KEY, String.valueOf(getLoginUserType()));
-            reqVO.setChannelExtras(channelExtras);
+            if (reqVO.getChannelExtras() == null) {
+                reqVO.setChannelExtras(Maps.newHashMapWithExpectedSize(1));
+            }
+            PayWalletDO wallet = payWalletService.getOrCreateWallet(getLoginUserId(), getLoginUserType());
+            reqVO.getChannelExtras().put(WalletPayClient.WALLET_ID_KEY, String.valueOf(wallet.getId()));
         }
 
         // 2. 提交支付

+ 36 - 0
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/transfer/AppPayTransferController.java

@@ -0,0 +1,36 @@
+package cn.iocoder.yudao.module.pay.controller.app.transfer;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.module.pay.service.transfer.PayTransferService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
+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 static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+@Tag(name = "用户 APP - 转账单")
+@RestController
+@RequestMapping("/pay/transfer")
+@Validated
+@Slf4j
+public class AppPayTransferController {
+
+    @Resource
+    private PayTransferService transferService;
+
+    @GetMapping("/sync")
+    @Operation(summary = "同步转账单") // 目的:解决微信转账的异步回调可能有延迟的问题
+    @Parameter(name = "id", description = "编号", required = true, example = "1024")
+    public CommonResult<Boolean> syncTransfer(@RequestParam("id") Long id) {
+        transferService.syncTransfer(id);
+        return success(true);
+    }
+
+}

+ 0 - 26
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/convert/demo/PayDemoOrderConvert.java

@@ -1,26 +0,0 @@
-package cn.iocoder.yudao.module.pay.convert.demo;
-
-import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.module.pay.controller.admin.demo.vo.order.PayDemoOrderCreateReqVO;
-import cn.iocoder.yudao.module.pay.controller.admin.demo.vo.order.PayDemoOrderRespVO;
-import cn.iocoder.yudao.module.pay.dal.dataobject.demo.PayDemoOrderDO;
-import org.mapstruct.Mapper;
-import org.mapstruct.factory.Mappers;
-
-/**
- * 示例订单 Convert
- *
- * @author 芋道源码
- */
-@Mapper
-public interface PayDemoOrderConvert {
-
-    PayDemoOrderConvert INSTANCE = Mappers.getMapper(PayDemoOrderConvert.class);
-
-    PayDemoOrderDO convert(PayDemoOrderCreateReqVO bean);
-
-    PayDemoOrderRespVO convert(PayDemoOrderDO bean);
-
-    PageResult<PayDemoOrderRespVO> convertPage(PageResult<PayDemoOrderDO> page);
-
-}

+ 0 - 21
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/convert/demo/PayDemoTransferConvert.java

@@ -1,21 +0,0 @@
-package cn.iocoder.yudao.module.pay.convert.demo;
-
-import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.module.pay.controller.admin.demo.vo.transfer.PayDemoTransferCreateReqVO;
-import cn.iocoder.yudao.module.pay.controller.admin.demo.vo.transfer.PayDemoTransferRespVO;
-import cn.iocoder.yudao.module.pay.dal.dataobject.demo.PayDemoTransferDO;
-import org.mapstruct.Mapper;
-import org.mapstruct.factory.Mappers;
-
-/**
- * @author jason
- */
-@Mapper
-public interface PayDemoTransferConvert {
-
-    PayDemoTransferConvert INSTANCE = Mappers.getMapper(PayDemoTransferConvert.class);
-
-    PayDemoTransferDO convert(PayDemoTransferCreateReqVO bean);
-
-    PageResult<PayDemoTransferRespVO> convertPage(PageResult<PayDemoTransferDO> pageResult);
-}

+ 0 - 43
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/convert/notify/PayNotifyTaskConvert.java

@@ -1,43 +0,0 @@
-package cn.iocoder.yudao.module.pay.convert.notify;
-
-import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
-import cn.iocoder.yudao.module.pay.controller.admin.notify.vo.PayNotifyTaskDetailRespVO;
-import cn.iocoder.yudao.module.pay.controller.admin.notify.vo.PayNotifyTaskRespVO;
-import cn.iocoder.yudao.module.pay.dal.dataobject.app.PayAppDO;
-import cn.iocoder.yudao.module.pay.dal.dataobject.notify.PayNotifyLogDO;
-import cn.iocoder.yudao.module.pay.dal.dataobject.notify.PayNotifyTaskDO;
-import org.mapstruct.Mapper;
-import org.mapstruct.factory.Mappers;
-
-import java.util.List;
-import java.util.Map;
-
-/**
- * 支付通知 Convert
- *
- * @author 芋道源码
- */
-@Mapper
-public interface PayNotifyTaskConvert {
-
-    PayNotifyTaskConvert INSTANCE = Mappers.getMapper(PayNotifyTaskConvert.class);
-
-    PayNotifyTaskRespVO convert(PayNotifyTaskDO bean);
-
-    default PageResult<PayNotifyTaskRespVO> convertPage(PageResult<PayNotifyTaskDO> page, Map<Long, PayAppDO> appMap){
-        PageResult<PayNotifyTaskRespVO> result = convertPage(page);
-        result.getList().forEach(order -> MapUtils.findAndThen(appMap, order.getAppId(), app -> order.setAppName(app.getName())));
-        return result;
-    }
-    PageResult<PayNotifyTaskRespVO> convertPage(PageResult<PayNotifyTaskDO> page);
-
-    default PayNotifyTaskDetailRespVO convert(PayNotifyTaskDO task, PayAppDO app, List<PayNotifyLogDO> logs) {
-        PayNotifyTaskDetailRespVO respVO = convert(task, logs);
-        if (app != null) {
-            respVO.setAppName(app.getName());
-        }
-        return respVO;
-    }
-    PayNotifyTaskDetailRespVO convert(PayNotifyTaskDO task, List<PayNotifyLogDO> logs);
-}

+ 0 - 3
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/convert/refund/PayRefundConvert.java

@@ -4,7 +4,6 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
 import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
 import cn.iocoder.yudao.module.pay.api.refund.dto.PayRefundCreateReqDTO;
-import cn.iocoder.yudao.module.pay.api.refund.dto.PayRefundRespDTO;
 import cn.iocoder.yudao.module.pay.controller.admin.refund.vo.PayRefundDetailsRespVO;
 import cn.iocoder.yudao.module.pay.controller.admin.refund.vo.PayRefundExcelVO;
 import cn.iocoder.yudao.module.pay.controller.admin.refund.vo.PayRefundPageItemRespVO;
@@ -42,8 +41,6 @@ public interface PayRefundConvert {
 
     PayRefundDO convert(PayRefundCreateReqDTO bean);
 
-    PayRefundRespDTO convert02(PayRefundDO bean);
-
     default List<PayRefundExcelVO> convertList(List<PayRefundDO> list, Map<Long, PayAppDO> appMap) {
         return CollectionUtils.convertList(list, order -> {
             PayRefundExcelVO excelVO = convertExcel(order);

+ 0 - 31
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/convert/transfer/PayTransferConvert.java

@@ -1,31 +0,0 @@
-package cn.iocoder.yudao.module.pay.convert.transfer;
-
-import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.PayTransferUnifiedReqDTO;
-import cn.iocoder.yudao.module.pay.api.transfer.dto.PayTransferCreateReqDTO;
-import cn.iocoder.yudao.module.pay.controller.admin.demo.vo.transfer.PayDemoTransferCreateReqVO;
-import cn.iocoder.yudao.module.pay.controller.admin.transfer.vo.PayTransferCreateReqVO;
-import cn.iocoder.yudao.module.pay.controller.admin.transfer.vo.PayTransferPageItemRespVO;
-import cn.iocoder.yudao.module.pay.controller.admin.transfer.vo.PayTransferRespVO;
-import cn.iocoder.yudao.module.pay.dal.dataobject.transfer.PayTransferDO;
-import org.mapstruct.Mapper;
-import org.mapstruct.factory.Mappers;
-
-@Mapper
-public interface PayTransferConvert {
-
-    PayTransferConvert INSTANCE = Mappers.getMapper(PayTransferConvert.class);
-
-    PayTransferDO convert(PayTransferCreateReqDTO dto);
-
-    PayTransferUnifiedReqDTO convert2(PayTransferDO dto);
-
-    PayTransferCreateReqDTO convert(PayTransferCreateReqVO vo);
-
-    PayTransferCreateReqDTO convert(PayDemoTransferCreateReqVO vo);
-
-    PayTransferRespVO convert(PayTransferDO bean);
-
-    PageResult<PayTransferPageItemRespVO> convertPage(PageResult<PayTransferDO> pageResult);
-
-}

+ 0 - 84
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/demo/PayDemoTransferDO.java

@@ -1,84 +0,0 @@
-package cn.iocoder.yudao.module.pay.dal.dataobject.demo;
-
-import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
-import cn.iocoder.yudao.framework.pay.core.enums.transfer.PayTransferTypeEnum;
-import cn.iocoder.yudao.module.pay.dal.dataobject.app.PayAppDO;
-import com.baomidou.mybatisplus.annotation.KeySequence;
-import com.baomidou.mybatisplus.annotation.TableId;
-import com.baomidou.mybatisplus.annotation.TableName;
-import lombok.Data;
-
-import java.time.LocalDateTime;
-
-// TODO 芋艿:需要详细 review
-/**
- * 示例转账订单
- *
- * 演示业务系统的转账业务
- */
-@TableName(value ="pay_demo_transfer", autoResultMap = true)
-@KeySequence("pay_demo_transfer_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
-@Data
-public class PayDemoTransferDO extends BaseDO {
-
-    /**
-     * 订单编号
-     */
-    @TableId
-    private Long id;
-
-    /**
-     * 应用编号
-     *
-     * 关联 {@link PayAppDO#getId()}
-     */
-    private Long appId;
-
-    /**
-     * 转账类型
-     * <p>
-     * 枚举 {@link PayTransferTypeEnum}
-     */
-    private Integer type;
-
-    /**
-     * 转账金额,单位:分
-     */
-    private Integer price;
-
-    /**
-     * 收款人姓名
-     */
-    private String userName;
-
-    /**
-     * 支付宝登录号
-     */
-    private String alipayLogonId;
-
-    /**
-     * 微信 openId
-     */
-    private String openid;
-
-    /**
-     * 转账状态
-     */
-    private Integer transferStatus;
-
-    /**
-     * 转账单编号
-     */
-    private Long payTransferId;
-
-    /**
-     * 转账支付成功渠道
-     */
-    private String payChannelCode;
-
-    /**
-     * 转账支付时间
-     */
-    private LocalDateTime transferTime;
-
-}

+ 84 - 0
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/demo/PayDemoWithdrawDO.java

@@ -0,0 +1,84 @@
+package cn.iocoder.yudao.module.pay.dal.dataobject.demo;
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import cn.iocoder.yudao.module.pay.dal.dataobject.transfer.PayTransferDO;
+import cn.iocoder.yudao.module.pay.enums.demo.PayDemoWithdrawStatusEnum;
+import cn.iocoder.yudao.module.pay.enums.demo.PayDemoWithdrawTypeEnum;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+/**
+ * 示例提现订单
+ *
+ * 演示业务系统的转账业务
+ */
+@TableName(value ="pay_demo_withdraw", autoResultMap = true)
+@KeySequence("pay_demo_withdraw_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@Data
+public class PayDemoWithdrawDO extends BaseDO {
+
+    /**
+     * 提现单编号,自增
+     */
+    @TableId
+    private Long id;
+
+    /**
+     * 提现标题
+     */
+    private String subject;
+    /**
+     * 提现金额,单位:分
+     */
+    private Integer price;
+
+    /**
+     * 收款人账号
+     */
+    private String userAccount;
+    /**
+     * 收款人姓名
+     */
+    private String userName;
+
+    /**
+     * 提现方式
+     *
+     * 枚举 {@link PayDemoWithdrawTypeEnum}
+     */
+    private Integer type;
+    /**
+     * 提现状态
+     *
+     * 枚举 {@link PayDemoWithdrawStatusEnum}
+     */
+    private Integer status;
+
+    // ========== 转账相关字段 ==========
+
+    /**
+     * 转账单编号
+     *
+     * 关联 {@link PayTransferDO#getId()}
+     */
+    private Long payTransferId;
+    /**
+     * 转账渠道
+     *
+     * 枚举 {@link cn.iocoder.yudao.module.pay.enums.PayChannelEnum}
+     */
+    private String transferChannelCode;
+    /**
+     * 转账成功时间
+     */
+    private LocalDateTime transferTime;
+    /**
+     * 转账错误提示
+     */
+    private String transferErrorMsg;
+
+}

+ 7 - 1
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/notify/PayNotifyTaskDO.java

@@ -4,6 +4,7 @@ import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO;
 import cn.iocoder.yudao.module.pay.dal.dataobject.app.PayAppDO;
 import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderDO;
 import cn.iocoder.yudao.module.pay.dal.dataobject.refund.PayRefundDO;
+import cn.iocoder.yudao.module.pay.dal.dataobject.transfer.PayTransferDO;
 import cn.iocoder.yudao.module.pay.enums.notify.PayNotifyStatusEnum;
 import cn.iocoder.yudao.module.pay.enums.notify.PayNotifyTypeEnum;
 import com.baomidou.mybatisplus.annotation.KeySequence;
@@ -60,6 +61,7 @@ public class PayNotifyTaskDO extends TenantBaseDO {
      *
      * 1. {@link PayNotifyTypeEnum#ORDER} 时,关联 {@link PayOrderDO#getId()}
      * 2. {@link PayNotifyTypeEnum#REFUND} 时,关联 {@link PayRefundDO#getId()}
+     * 3. {@link PayNotifyTypeEnum#TRANSFER} 时,关联 {@link PayTransferDO#getId()}
      */
     private Long dataId;
     /**
@@ -67,7 +69,11 @@ public class PayNotifyTaskDO extends TenantBaseDO {
      */
     private String merchantOrderId;
     /**
-     * 商户转账单编号
+     * 商户退款编号
+     */
+    private String merchantRefundId;
+    /**
+     * 商户转账编号
      */
     private String merchantTransferId;
     /**

+ 12 - 24
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/transfer/PayTransferDO.java

@@ -3,7 +3,6 @@ package cn.iocoder.yudao.module.pay.dal.dataobject.transfer;
 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
 import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum;
 import cn.iocoder.yudao.framework.pay.core.enums.transfer.PayTransferStatusRespEnum;
-import cn.iocoder.yudao.framework.pay.core.enums.transfer.PayTransferTypeEnum;
 import cn.iocoder.yudao.module.pay.dal.dataobject.app.PayAppDO;
 import cn.iocoder.yudao.module.pay.dal.dataobject.channel.PayChannelDO;
 import com.baomidou.mybatisplus.annotation.KeySequence;
@@ -16,7 +15,6 @@ import lombok.Data;
 import java.time.LocalDateTime;
 import java.util.Map;
 
-// TODO 芋艿:需要详细 review
 /**
  * 转账单 DO
  *
@@ -35,7 +33,6 @@ public class PayTransferDO extends BaseDO {
 
     /**
      * 转账单号
-     *
      */
     private String no;
 
@@ -70,13 +67,6 @@ public class PayTransferDO extends BaseDO {
 
     // ========== 转账相关字段 ==========
 
-    /**
-     * 类型
-     *
-     * 枚举 {@link PayTransferTypeEnum}
-     */
-    private Integer type;
-
     /**
      * 转账标题
      */
@@ -87,6 +77,10 @@ public class PayTransferDO extends BaseDO {
      */
     private Integer price;
 
+    /**
+     * 收款人账号
+     */
+    private String userAccount;
     /**
      * 收款人姓名
      */
@@ -104,19 +98,6 @@ public class PayTransferDO extends BaseDO {
      */
     private LocalDateTime successTime;
 
-    // ========== 支付宝转账相关字段 ==========
-    /**
-     * 支付宝登录号
-     */
-    private String alipayLogonId;
-
-
-    // ========== 微信转账相关字段 ==========
-    /**
-     * 微信 openId
-     */
-    private String openid;
-
     // ========== 其它字段 ==========
 
     /**
@@ -151,8 +132,15 @@ public class PayTransferDO extends BaseDO {
 
     /**
      * 渠道的同步/异步通知的内容
-     *
      */
     private String channelNotifyData;
 
+    /**
+     * 渠道 package 信息
+     *
+     * 特殊:目前只有微信转账有这个东西!!!
+     * @see <a href="https://pay.weixin.qq.com/doc/v3/merchant/4012716430">JSAPI 调起用户确认收款</a>
+     */
+    private String channelPackageInfo;
+
 }

+ 0 - 17
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/demo/PayDemoTransferMapper.java

@@ -1,17 +0,0 @@
-package cn.iocoder.yudao.module.pay.dal.mysql.demo;
-
-import cn.iocoder.yudao.framework.common.pojo.PageParam;
-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.pay.dal.dataobject.demo.PayDemoTransferDO;
-import org.apache.ibatis.annotations.Mapper;
-
-@Mapper
-public interface PayDemoTransferMapper extends BaseMapperX<PayDemoTransferDO> {
-
-    default  PageResult<PayDemoTransferDO> selectPage(PageParam pageParam){
-        return selectPage(pageParam, new LambdaQueryWrapperX<PayDemoTransferDO>()
-                .orderByDesc(PayDemoTransferDO::getId));
-    }
-}

+ 24 - 0
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/demo/PayDemoWithdrawMapper.java

@@ -0,0 +1,24 @@
+package cn.iocoder.yudao.module.pay.dal.mysql.demo;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+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.pay.dal.dataobject.demo.PayDemoWithdrawDO;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface PayDemoWithdrawMapper extends BaseMapperX<PayDemoWithdrawDO> {
+
+    default  PageResult<PayDemoWithdrawDO> selectPage(PageParam pageParam){
+        return selectPage(pageParam, new LambdaQueryWrapperX<PayDemoWithdrawDO>()
+                .orderByDesc(PayDemoWithdrawDO::getId));
+    }
+
+    default int updateByIdAndStatus(Long id, Integer whereStatus, PayDemoWithdrawDO updateObj) {
+        return update(updateObj, new LambdaQueryWrapperX<PayDemoWithdrawDO>()
+                .eq(PayDemoWithdrawDO::getId, id)
+                .eq(PayDemoWithdrawDO::getStatus, whereStatus));
+    }
+
+}

+ 2 - 0
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/notify/PayNotifyTaskMapper.java

@@ -37,6 +37,8 @@ public interface PayNotifyTaskMapper extends BaseMapperX<PayNotifyTaskDO> {
                 .eqIfPresent(PayNotifyTaskDO::getDataId, reqVO.getDataId())
                 .eqIfPresent(PayNotifyTaskDO::getStatus, reqVO.getStatus())
                 .eqIfPresent(PayNotifyTaskDO::getMerchantOrderId, reqVO.getMerchantOrderId())
+                .eqIfPresent(PayNotifyTaskDO::getMerchantRefundId, reqVO.getMerchantRefundId())
+                .eqIfPresent(PayNotifyTaskDO::getMerchantTransferId, reqVO.getMerchantTransferId())
                 .betweenIfPresent(PayNotifyTaskDO::getCreateTime, reqVO.getCreateTime())
                 .orderByDesc(PayNotifyTaskDO::getId));
     }

+ 20 - 8
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/transfer/PayTransferMapper.java

@@ -8,19 +8,27 @@ import cn.iocoder.yudao.module.pay.dal.dataobject.transfer.PayTransferDO;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import org.apache.ibatis.annotations.Mapper;
 
+import java.util.Collection;
 import java.util.List;
 
 @Mapper
 public interface PayTransferMapper extends BaseMapperX<PayTransferDO> {
 
-    default int updateByIdAndStatus(Long id, List<Integer> status, PayTransferDO updateObj) {
+    default int updateByIdAndStatus(Long id, List<Integer> whereStatuses, PayTransferDO updateObj) {
         return update(updateObj, new LambdaQueryWrapper<PayTransferDO>()
-                .eq(PayTransferDO::getId, id).in(PayTransferDO::getStatus, status));
+                .eq(PayTransferDO::getId, id)
+                .in(PayTransferDO::getStatus, whereStatuses));
     }
 
-    default PayTransferDO selectByAppIdAndMerchantTransferId(Long appId, String merchantTransferId){
+    default int updateByIdAndStatus(Long id, Integer whereStatus, PayTransferDO updateObj) {
+        return update(updateObj, new LambdaQueryWrapper<PayTransferDO>()
+                .eq(PayTransferDO::getId, id)
+                .eq(PayTransferDO::getStatus, whereStatus));
+    }
+
+    default PayTransferDO selectByAppIdAndMerchantOrderId(Long appId, String merchantOrderId) {
         return selectOne(PayTransferDO::getAppId, appId,
-                    PayTransferDO::getMerchantTransferId, merchantTransferId);
+                    PayTransferDO::getMerchantTransferId, merchantOrderId);
     }
 
     default PageResult<PayTransferDO> selectPage(PayTransferPageReqVO reqVO) {
@@ -28,17 +36,17 @@ public interface PayTransferMapper extends BaseMapperX<PayTransferDO> {
                 .eqIfPresent(PayTransferDO::getNo, reqVO.getNo())
                 .eqIfPresent(PayTransferDO::getAppId, reqVO.getAppId())
                 .eqIfPresent(PayTransferDO::getChannelCode, reqVO.getChannelCode())
-                .eqIfPresent(PayTransferDO::getMerchantTransferId, reqVO.getMerchantTransferId())
-                .eqIfPresent(PayTransferDO::getType, reqVO.getType())
+                .eqIfPresent(PayTransferDO::getMerchantTransferId, reqVO.getMerchantOrderId())
                 .eqIfPresent(PayTransferDO::getStatus, reqVO.getStatus())
                 .likeIfPresent(PayTransferDO::getUserName, reqVO.getUserName())
+                .likeIfPresent(PayTransferDO::getUserAccount, reqVO.getUserAccount())
                 .eqIfPresent(PayTransferDO::getChannelTransferNo, reqVO.getChannelTransferNo())
                 .betweenIfPresent(PayTransferDO::getCreateTime, reqVO.getCreateTime())
                 .orderByDesc(PayTransferDO::getId));
     }
 
-    default List<PayTransferDO> selectListByStatus(Integer status) {
-        return selectList(PayTransferDO::getStatus, status);
+    default List<PayTransferDO> selectListByStatus(Collection<Integer> statuses) {
+        return selectList(PayTransferDO::getStatus, statuses);
     }
 
     default PayTransferDO selectByAppIdAndNo(Long appId, String no) {
@@ -46,6 +54,10 @@ public interface PayTransferMapper extends BaseMapperX<PayTransferDO> {
                 PayTransferDO::getNo, no);
     }
 
+    default PayTransferDO selectByNo(String no) {
+        return selectOne(PayTransferDO::getNo, no);
+    }
+
 }
 
 

+ 74 - 18
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/WalletPayClient.java

@@ -14,14 +14,16 @@ 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 cn.iocoder.yudao.framework.pay.core.enums.refund.PayRefundStatusRespEnum;
-import cn.iocoder.yudao.framework.pay.core.enums.transfer.PayTransferTypeEnum;
+import cn.iocoder.yudao.framework.pay.core.enums.transfer.PayTransferStatusRespEnum;
 import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderExtensionDO;
 import cn.iocoder.yudao.module.pay.dal.dataobject.refund.PayRefundDO;
+import cn.iocoder.yudao.module.pay.dal.dataobject.transfer.PayTransferDO;
 import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletTransactionDO;
-import cn.iocoder.yudao.module.pay.enums.wallet.PayWalletBizTypeEnum;
 import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum;
+import cn.iocoder.yudao.module.pay.enums.wallet.PayWalletBizTypeEnum;
 import cn.iocoder.yudao.module.pay.service.order.PayOrderService;
 import cn.iocoder.yudao.module.pay.service.refund.PayRefundService;
+import cn.iocoder.yudao.module.pay.service.transfer.PayTransferService;
 import cn.iocoder.yudao.module.pay.service.wallet.PayWalletService;
 import cn.iocoder.yudao.module.pay.service.wallet.PayWalletTransactionService;
 import lombok.extern.slf4j.Slf4j;
@@ -40,13 +42,14 @@ import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.REFUND_NOT_FO
 @Slf4j
 public class WalletPayClient extends AbstractPayClient<NonePayClientConfig> {
 
-    public static final String USER_ID_KEY = "user_id";
-    public static final String USER_TYPE_KEY = "user_type";
+    public static final String WALLET_ID_KEY = "walletId";
 
     private PayWalletService wallService;
     private PayWalletTransactionService walletTransactionService;
+
     private PayOrderService orderService;
     private PayRefundService refundService;
+    private PayTransferService transferService;
 
     public WalletPayClient(Long channelId,  NonePayClientConfig config) {
         super(channelId, PayChannelEnum.WALLET.getCode(), config);
@@ -63,19 +66,18 @@ public class WalletPayClient extends AbstractPayClient<NonePayClientConfig> {
     }
 
     @Override
+    @SuppressWarnings("PatternVariableCanBeUsed")
     protected PayOrderRespDTO doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) {
         try {
-            Long userId = MapUtil.getLong(reqDTO.getChannelExtras(), USER_ID_KEY);
-            Integer userType = MapUtil.getInt(reqDTO.getChannelExtras(), USER_TYPE_KEY);
-            Assert.notNull(userId, "用户 id 不能为空");
-            Assert.notNull(userType, "用户类型不能为空");
-            PayWalletTransactionDO transaction = wallService.orderPay(userId, userType, reqDTO.getOutTradeNo(),
-                    reqDTO.getPrice());
+            Long walletId = MapUtil.getLong(reqDTO.getChannelExtras(), WALLET_ID_KEY);
+            Assert.notNull(walletId, "钱包编号");
+            PayWalletTransactionDO transaction = wallService.orderPay(walletId,
+                    reqDTO.getOutTradeNo(), reqDTO.getPrice());
             return PayOrderRespDTO.successOf(transaction.getNo(), transaction.getCreator(),
                     transaction.getCreateTime(),
                     reqDTO.getOutTradeNo(), transaction);
         } catch (Throwable ex) {
-            log.error("[doUnifiedOrder] 失败", ex);
+            log.error("[doUnifiedOrder][reqDTO({}) 异常]", reqDTO, ex);
             Integer errorCode = INTERNAL_SERVER_ERROR.getCode();
             String errorMsg = INTERNAL_SERVER_ERROR.getMsg();
             if (ex instanceof ServiceException) {
@@ -123,6 +125,7 @@ public class WalletPayClient extends AbstractPayClient<NonePayClientConfig> {
     }
 
     @Override
+    @SuppressWarnings("PatternVariableCanBeUsed")
     protected PayRefundRespDTO doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) {
         try {
             PayWalletTransactionDO payWalletTransaction = wallService.orderRefund(reqDTO.getOutRefundNo(),
@@ -130,7 +133,7 @@ public class WalletPayClient extends AbstractPayClient<NonePayClientConfig> {
             return PayRefundRespDTO.successOf(payWalletTransaction.getNo(), payWalletTransaction.getCreateTime(),
                     reqDTO.getOutRefundNo(), payWalletTransaction);
         } catch (Throwable ex) {
-            log.error("[doUnifiedRefund] 失败", ex);
+            log.error("[doUnifiedRefund][reqDOT({}) 异常]", reqDTO, ex);
             Integer errorCode = INTERNAL_SERVER_ERROR.getCode();
             String errorMsg = INTERNAL_SERVER_ERROR.getMsg();
             if (ex instanceof ServiceException) {
@@ -178,18 +181,71 @@ public class WalletPayClient extends AbstractPayClient<NonePayClientConfig> {
     }
 
     @Override
-    protected PayTransferRespDTO doParseTransferNotify(Map<String, String> params, String body, Map<String, String> headers) {
-        throw new UnsupportedOperationException("未实现");
+    @SuppressWarnings("PatternVariableCanBeUsed")
+    public PayTransferRespDTO doUnifiedTransfer(PayTransferUnifiedReqDTO reqDTO) {
+        try {
+            Long walletId = Long.parseLong(reqDTO.getUserAccount());
+            PayWalletTransactionDO transaction = wallService.addWalletBalance(walletId, String.valueOf(reqDTO.getOutTransferNo()),
+                    PayWalletBizTypeEnum.TRANSFER, reqDTO.getPrice());
+            return PayTransferRespDTO.successOf(transaction.getNo(), transaction.getCreateTime(),
+                    reqDTO.getOutTransferNo(), transaction);
+        } catch (Throwable ex) {
+            log.error("[doUnifiedTransfer][reqDTO({}) 异常]", reqDTO, ex);
+            Integer errorCode = INTERNAL_SERVER_ERROR.getCode();
+            String errorMsg = INTERNAL_SERVER_ERROR.getMsg();
+            if (ex instanceof ServiceException) {
+                ServiceException serviceException = (ServiceException) ex;
+                errorCode = serviceException.getCode();
+                errorMsg = serviceException.getMessage();
+            }
+            return PayTransferRespDTO.closedOf(String.valueOf(errorCode), errorMsg,
+                    reqDTO.getOutTransferNo(), "");
+        }
     }
 
     @Override
-    public PayTransferRespDTO doUnifiedTransfer(PayTransferUnifiedReqDTO reqDTO) {
-        throw new UnsupportedOperationException("待实现");
+    protected PayTransferRespDTO doParseTransferNotify(Map<String, String> params, String body, Map<String, String> headers) {
+        throw new UnsupportedOperationException("钱包支付无转账回调");
     }
 
     @Override
-    protected PayTransferRespDTO doGetTransfer(String outTradeNo, PayTransferTypeEnum type) {
-        throw new UnsupportedOperationException("待实现");
+    protected PayTransferRespDTO doGetTransfer(String outTradeNo) {
+        if (transferService == null) {
+            transferService = SpringUtil.getBean(PayTransferService.class);
+        }
+        // 获取转账单
+        PayTransferDO transfer = transferService.getTransferByNo(outTradeNo);
+        // 转账单不存在,返回关闭状态
+        if (transfer == null) {
+            return PayTransferRespDTO.closedOf(String.valueOf(PAY_ORDER_EXTENSION_NOT_FOUND.getCode()),
+                    PAY_ORDER_EXTENSION_NOT_FOUND.getMsg(), outTradeNo, "");
+        }
+        // 关闭状态
+        if (PayTransferStatusRespEnum.isClosed(transfer.getStatus())) {
+            return PayTransferRespDTO.closedOf(transfer.getChannelErrorCode(),
+                    transfer.getChannelErrorMsg(), outTradeNo, "");
+        }
+        // 成功状态
+        if (PayTransferStatusRespEnum.isSuccess(transfer.getStatus())) {
+            PayWalletTransactionDO walletTransaction = walletTransactionService.getWalletTransaction(
+                    String.valueOf(transfer.getId()), PayWalletBizTypeEnum.TRANSFER);
+            Assert.notNull(walletTransaction, "转账单 {} 钱包流水不能为空", outTradeNo);
+            return PayTransferRespDTO.successOf(walletTransaction.getNo(), walletTransaction.getCreateTime(),
+                    outTradeNo, walletTransaction);
+        }
+        // 处理中状态
+        if (PayTransferStatusRespEnum.isProcessing(transfer.getStatus())) {
+            return PayTransferRespDTO.processingOf(transfer.getChannelTransferNo(),
+                    outTradeNo, transfer);
+        }
+        // 等待状态
+        if (transfer.getStatus().equals(PayTransferStatusRespEnum.WAITING.getStatus())) {
+            return PayTransferRespDTO.waitingOf(transfer.getChannelTransferNo(),
+                    outTradeNo, transfer);
+        }
+        // 其它状态为无效状态
+        log.error("[doGetTransfer] 转账单 {} 的状态不正确", outTradeNo);
+        throw new IllegalStateException(String.format("转账单[%s] 状态不正确", outTradeNo));
     }
 
 }

+ 5 - 0
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/app/PayAppServiceImpl.java

@@ -1,5 +1,6 @@
 package cn.iocoder.yudao.module.pay.service.app;
 
+import cn.hutool.core.collection.CollUtil;
 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.module.pay.controller.admin.app.vo.PayAppCreateReqVO;
@@ -17,6 +18,7 @@ import org.springframework.validation.annotation.Validated;
 
 import javax.annotation.Resource;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.List;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
@@ -116,6 +118,9 @@ public class PayAppServiceImpl implements PayAppService {
 
     @Override
     public List<PayAppDO> getAppList(Collection<Long> ids) {
+        if (CollUtil.isEmpty(ids)) {
+            return Collections.emptyList();
+        }
         return appMapper.selectBatchIds(ids);
     }
 

Nem az összes módosított fájl került megjelenítésre, mert túl sok fájl változott