瀏覽代碼

!66 新增支付管理的管理后台:商户信息、应用信息、支付订单、退款订单等功能
Merge pull request !66 from 芋道源码/pay_extension

芋道源码 3 年之前
父節點
當前提交
4be5b73dbc
共有 100 個文件被更改,包括 7891 次插入97 次删除
  1. 1 0
      .gitignore
  2. 8 0
      sql/change_db.sql
  3. 163 0
      yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/app/PayAppController.java
  4. 37 0
      yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/app/vo/PayAppBaseVO.java
  5. 14 0
      yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/app/vo/PayAppCreateReqVO.java
  6. 41 0
      yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/app/vo/PayAppExcelVO.java
  7. 41 0
      yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/app/vo/PayAppExportReqVO.java
  8. 50 0
      yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/app/vo/PayAppPageItemRespVO.java
  9. 43 0
      yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/app/vo/PayAppPageReqVO.java
  10. 19 0
      yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/app/vo/PayAppRespVO.java
  11. 18 0
      yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/app/vo/PayAppUpdateReqVO.java
  12. 21 0
      yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/app/vo/PayAppUpdateStatusReqVO.java
  13. 129 0
      yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/channel/PayChannelController.java
  14. 39 0
      yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/channel/vo/PayChannelBaseVO.java
  15. 21 0
      yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/channel/vo/PayChannelCreateReqVO.java
  16. 50 0
      yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/channel/vo/PayChannelExcelVO.java
  17. 44 0
      yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/channel/vo/PayChannelExportReqVO.java
  18. 46 0
      yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/channel/vo/PayChannelPageReqVO.java
  19. 21 0
      yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/channel/vo/PayChannelRespVO.java
  20. 21 0
      yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/channel/vo/PayChannelUpdateReqVO.java
  21. 116 0
      yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/merchant/PayMerchantController.java
  22. 30 0
      yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/merchant/vo/PayMerchantBaseVO.java
  23. 14 0
      yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/merchant/vo/PayMerchantCreateReqVO.java
  24. 40 0
      yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/merchant/vo/PayMerchantExcelVO.java
  25. 38 0
      yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/merchant/vo/PayMerchantExportReqVO.java
  26. 40 0
      yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/merchant/vo/PayMerchantPageReqVO.java
  27. 30 0
      yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/merchant/vo/PayMerchantRespVO.java
  28. 18 0
      yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/merchant/vo/PayMerchantUpdateReqVO.java
  29. 21 0
      yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/merchant/vo/PayMerchantUpdateStatusReqVO.java
  30. 177 0
      yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/order/PayOrderController.java
  31. 174 0
      yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/order/PayRefundController.java
  32. 108 0
      yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/order/vo/order/PayOrderBaseVO.java
  33. 53 0
      yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/order/vo/order/PayOrderDetailsRespVO.java
  34. 91 0
      yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/order/vo/order/PayOrderExcelVO.java
  35. 112 0
      yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/order/vo/order/PayOrderExportReqVO.java
  36. 41 0
      yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/order/vo/order/PayOrderPageItemRespVO.java
  37. 118 0
      yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/order/vo/order/PayOrderPageReqVO.java
  38. 28 0
      yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/order/vo/order/PayOrderRespVO.java
  39. 110 0
      yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/order/vo/refund/vo/PayRefundBaseVO.java
  40. 12 0
      yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/order/vo/refund/vo/PayRefundCreateReqVO.java
  41. 44 0
      yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/order/vo/refund/vo/PayRefundDetailsRespVO.java
  42. 88 0
      yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/order/vo/refund/vo/PayRefundExcelVO.java
  43. 111 0
      yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/order/vo/refund/vo/PayRefundExportReqVO.java
  44. 36 0
      yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/order/vo/refund/vo/PayRefundPageItemRespVO.java
  45. 116 0
      yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/order/vo/refund/vo/PayRefundPageReqVO.java
  46. 23 0
      yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/order/vo/refund/vo/PayRefundRespVO.java
  47. 17 0
      yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/order/vo/refund/vo/PayRefundUpdateReqVO.java
  48. 41 0
      yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/convert/app/PayAppConvert.java
  49. 42 0
      yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/convert/channel/PayChannelConvert.java
  50. 34 0
      yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/convert/merchant/PayMerchantConvert.java
  51. 104 0
      yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/convert/order/PayOrderConvert.java
  52. 90 0
      yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/convert/refund/PayRefundConvert.java
  53. 83 0
      yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/dal/mysql/app/PayAppMapper.java
  54. 91 0
      yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/dal/mysql/channel/PayChannelMapper.java
  55. 53 0
      yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/dal/mysql/merchant/PayMerchantMapper.java
  56. 16 0
      yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/dal/mysql/order/PayOrderExtensionMapper.java
  57. 79 0
      yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/dal/mysql/order/PayOrderMapper.java
  58. 61 0
      yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/dal/mysql/order/PayRefundMapper.java
  59. 1 0
      yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/enums/PayErrorCodeConstants.java
  60. 103 0
      yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/service/app/PayAppService.java
  61. 189 0
      yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/service/app/impl/PayAppServiceImpl.java
  62. 107 0
      yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/service/channel/PayChannelService.java
  63. 161 0
      yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/service/channel/impl/PayChannelServiceImpl.java
  64. 104 0
      yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/service/merchant/PayMerchantService.java
  65. 150 0
      yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/service/merchant/impl/PayMerchantServiceImpl.java
  66. 48 0
      yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/service/order/PayOrderExtensionService.java
  67. 67 0
      yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/service/order/PayOrderService.java
  68. 41 0
      yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/service/order/PayRefundService.java
  69. 35 0
      yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/service/order/impl/PayOrderExtensionServiceImpl.java
  70. 54 0
      yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/service/order/impl/PayOrderServiceImpl.java
  71. 42 0
      yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/service/order/impl/PayRefundServiceImpl.java
  72. 25 0
      yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/system/enums/SysDictTypeConstants.java
  73. 1 0
      yudao-admin-server/src/main/resources/application-local.yaml
  74. 1 1
      yudao-admin-server/src/test/java/cn/iocoder/yudao/adminserver/modules/bpm/framework/activiti/core/behavior/BpmUserTaskActivitiBehaviorTest.java
  75. 245 0
      yudao-admin-server/src/test/java/cn/iocoder/yudao/adminserver/modules/pay/service/app/PayAppServiceTest.java
  76. 20 0
      yudao-admin-server/src/test/java/cn/iocoder/yudao/adminserver/modules/pay/service/channel/PayChannelConfig.java
  77. 404 0
      yudao-admin-server/src/test/java/cn/iocoder/yudao/adminserver/modules/pay/service/channel/PayChannelServiceTest.java
  78. 193 0
      yudao-admin-server/src/test/java/cn/iocoder/yudao/adminserver/modules/pay/service/merchant/PayMerchantServiceTest.java
  79. 196 0
      yudao-admin-server/src/test/java/cn/iocoder/yudao/adminserver/modules/pay/service/order/PayOrderServiceTest.java
  80. 183 0
      yudao-admin-server/src/test/java/cn/iocoder/yudao/adminserver/modules/pay/service/refund/PayRefundServiceTest.java
  81. 7 0
      yudao-admin-server/src/test/resources/sql/clean.sql
  82. 125 0
      yudao-admin-server/src/test/resources/sql/create_tables.sql
  83. 1 1
      yudao-admin-ui/package.json
  84. 78 0
      yudao-admin-ui/src/api/pay/app.js
  85. 71 0
      yudao-admin-ui/src/api/pay/channel.js
  86. 77 0
      yudao-admin-ui/src/api/pay/merchant.js
  87. 54 0
      yudao-admin-ui/src/api/pay/order.js
  88. 54 0
      yudao-admin-ui/src/api/pay/refund.js
  89. 1 0
      yudao-admin-ui/src/assets/icons/svg/config.svg
  90. 2 0
      yudao-admin-ui/src/assets/icons/svg/merchant.svg
  91. 1 0
      yudao-admin-ui/src/assets/icons/svg/order.svg
  92. 2 0
      yudao-admin-ui/src/assets/icons/svg/pay.svg
  93. 2 0
      yudao-admin-ui/src/assets/icons/svg/percentSign.svg
  94. 136 1
      yudao-admin-ui/src/utils/constants.js
  95. 23 2
      yudao-admin-ui/src/utils/dict.js
  96. 115 92
      yudao-admin-ui/src/utils/ruoyi.js
  97. 358 0
      yudao-admin-ui/src/views/pay/app/components/aliPayChannelForm.vue
  98. 299 0
      yudao-admin-ui/src/views/pay/app/components/wechatChannelForm.vue
  99. 492 0
      yudao-admin-ui/src/views/pay/app/index.vue
  100. 296 0
      yudao-admin-ui/src/views/pay/merchant/index.vue

+ 1 - 0
.gitignore

@@ -42,3 +42,4 @@ nbdist/
 !*/build/*.java
 !*/build/*.html
 !*/build/*.xml
+

+ 8 - 0
sql/change_db.sql

@@ -0,0 +1,8 @@
+ALTER TABLE `ruoyi-vue-pro`.`pay_order_extension`
+CHANGE COLUMN `channel_notify_data` `channel_notify_data` VARCHAR(2048) CHARACTER SET 'utf8mb4' NULL DEFAULT NULL COMMENT '支付渠道异步通知的内容' ;
+
+ALTER TABLE `ruoyi-vue-pro`.`pay_refund`
+CHANGE COLUMN `req_no` `req_no` VARCHAR(64) NULL COMMENT '退款单请求号' ;
+
+ALTER TABLE `ruoyi-vue-pro`.`pay_refund`
+DROP COLUMN `req_no`;

+ 163 - 0
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/app/PayAppController.java

@@ -0,0 +1,163 @@
+package cn.iocoder.yudao.adminserver.modules.pay.controller.app;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.iocoder.yudao.adminserver.modules.pay.controller.app.vo.*;
+import cn.iocoder.yudao.adminserver.modules.pay.convert.app.PayAppConvert;
+import cn.iocoder.yudao.adminserver.modules.pay.service.app.PayAppService;
+import cn.iocoder.yudao.adminserver.modules.pay.service.channel.PayChannelService;
+import cn.iocoder.yudao.adminserver.modules.pay.service.merchant.PayMerchantService;
+import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayAppDO;
+import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayChannelDO;
+import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayMerchantDO;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
+import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
+import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
+import cn.iocoder.yudao.framework.pay.core.enums.PayChannelEnum;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiImplicitParam;
+import io.swagger.annotations.ApiOperation;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+import javax.servlet.http.HttpServletResponse;
+import javax.validation.Valid;
+import java.io.IOException;
+import java.util.*;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT;
+
+@Slf4j
+@Api(tags = "支付应用信息")
+@RestController
+@RequestMapping("/pay/app")
+@Validated
+public class PayAppController {
+
+    @Resource
+    private PayAppService appService;
+    @Resource
+    private PayChannelService channelService;
+    @Resource
+    private PayMerchantService merchantService;
+
+    @PostMapping("/create")
+    @ApiOperation("创建支付应用信息")
+    @PreAuthorize("@ss.hasPermission('pay:app:create')")
+    public CommonResult<Long> createApp(@Valid @RequestBody PayAppCreateReqVO createReqVO) {
+        return success(appService.createApp(createReqVO));
+    }
+
+    @PutMapping("/update")
+    @ApiOperation("更新支付应用信息")
+    @PreAuthorize("@ss.hasPermission('pay:app:update')")
+    public CommonResult<Boolean> updateApp(@Valid @RequestBody PayAppUpdateReqVO updateReqVO) {
+        appService.updateApp(updateReqVO);
+        return success(true);
+    }
+
+    @PutMapping("/update-status")
+    @ApiOperation("更新支付应用状态")
+    @PreAuthorize("@ss.hasPermission('pay:app:update')")
+    public CommonResult<Boolean> updateAppStatus(@Valid @RequestBody PayAppUpdateStatusReqVO updateReqVO) {
+        appService.updateAppStatus(updateReqVO.getId(), updateReqVO.getStatus());
+        return success(true);
+    }
+
+    @DeleteMapping("/delete")
+    @ApiOperation("删除支付应用信息")
+    @ApiImplicitParam(name = "id", value = "编号", required = true)
+    @PreAuthorize("@ss.hasPermission('pay:app:delete')")
+    public CommonResult<Boolean> deleteApp(@RequestParam("id") Long id) {
+        appService.deleteApp(id);
+        return success(true);
+    }
+
+    @GetMapping("/get")
+    @ApiOperation("获得支付应用信息")
+    @ApiImplicitParam(name = "id", value = "编号", required = true, example = "1024", dataTypeClass = Long.class)
+    @PreAuthorize("@ss.hasPermission('pay:app:query')")
+    public CommonResult<PayAppRespVO> getApp(@RequestParam("id") Long id) {
+        PayAppDO app = appService.getApp(id);
+        return success(PayAppConvert.INSTANCE.convert(app));
+    }
+
+    @GetMapping("/list")
+    @ApiOperation("获得支付应用信息列表")
+    @ApiImplicitParam(name = "ids", value = "编号列表", required = true, example = "1024,2048", dataTypeClass = List.class)
+    @PreAuthorize("@ss.hasPermission('pay:app:query')")
+    public CommonResult<List<PayAppRespVO>> getAppList(@RequestParam("ids") Collection<Long> ids) {
+        List<PayAppDO> list = appService.getAppList(ids);
+        return success(PayAppConvert.INSTANCE.convertList(list));
+    }
+
+    @GetMapping("/page")
+    @ApiOperation("获得支付应用信息分页")
+    @PreAuthorize("@ss.hasPermission('pay:app:query')")
+    public CommonResult<PageResult<PayAppPageItemRespVO>> getAppPage(@Valid PayAppPageReqVO pageVO) {
+        // 得到应用分页列表
+        PageResult<PayAppDO> pageResult = appService.getAppPage(pageVO);
+        if (CollUtil.isEmpty(pageResult.getList())) {
+            return success(new PageResult<>(pageResult.getTotal()));
+        }
+
+        // 得到所有的应用编号,查出所有的通道
+        Collection<Long> payAppIds = CollectionUtils.convertList(pageResult.getList(), PayAppDO::getId);
+        List<PayChannelDO> channels = channelService.getChannelListByAppIds(payAppIds);
+        // TODO @aquan:可以基于 appId 简历一个 multiMap。这样下面,直接 get 到之后,CollUtil buildSet 即可
+        Iterator<PayChannelDO> iterator = channels.iterator();
+
+        // 得到所有的商户信息
+        Collection<Long> merchantIds = CollectionUtils.convertList(pageResult.getList(), PayAppDO::getMerchantId);
+        Map<Long, PayMerchantDO> deptMap = merchantService.getMerchantMap(merchantIds);
+
+        // 利用反射将通道数据复制到返回的数据结构中去
+        List<PayAppPageItemRespVO> appList = new ArrayList<>(pageResult.getList().size());
+        pageResult.getList().forEach(app -> {
+            // 写入应用信息的数据
+            PayAppPageItemRespVO respVO = PayAppConvert.INSTANCE.pageConvert(app);
+            // 写入商户的数据
+            respVO.setPayMerchant(PayAppConvert.INSTANCE.convert(deptMap.get(app.getMerchantId())));
+            // 写入支付渠道信息的数据
+            Set<String> channelCodes = new HashSet<>(PayChannelEnum.values().length);
+            while (iterator.hasNext()) {
+                PayChannelDO channelDO = iterator.next();
+                if (channelDO.getAppId().equals(app.getId())) {
+                    channelCodes.add(channelDO.getCode());
+                    iterator.remove();
+                }
+            }
+            respVO.setChannelCodes(channelCodes);
+            appList.add(respVO);
+        });
+
+        return success(new PageResult<>(appList, pageResult.getTotal()));
+    }
+
+    @GetMapping("/export-excel")
+    @ApiOperation("导出支付应用信息 Excel")
+    @PreAuthorize("@ss.hasPermission('pay:app:export')")
+    @OperateLog(type = EXPORT)
+    public void exportAppExcel(@Valid PayAppExportReqVO exportReqVO,
+                               HttpServletResponse response) throws IOException {
+        List<PayAppDO> list = appService.getAppList(exportReqVO);
+        // 导出 Excel
+        List<PayAppExcelVO> datas = PayAppConvert.INSTANCE.convertList02(list);
+        ExcelUtils.write(response, "支付应用信息.xls", "数据", PayAppExcelVO.class, datas);
+    }
+
+    @GetMapping("/list-merchant-id")
+    @ApiOperation("根据商户 ID 查询支付应用信息")
+    @ApiImplicitParam(name = "merchantId", value = "商户ID", required = true, example = "1", dataTypeClass = Long.class)
+    @PreAuthorize("@ss.hasPermission('pay:merchant:query')")
+    public CommonResult<List<PayAppRespVO>> getMerchantListByName(@RequestParam String merchantId) {
+        List<PayAppDO> appListDO = appService.getListByMerchantId(merchantId);
+        return success(PayAppConvert.INSTANCE.convertList(appListDO));
+    }
+
+}

+ 37 - 0
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/app/vo/PayAppBaseVO.java

@@ -0,0 +1,37 @@
+package cn.iocoder.yudao.adminserver.modules.pay.controller.app.vo;
+
+import lombok.*;
+import io.swagger.annotations.*;
+import javax.validation.constraints.*;
+
+/**
+* 支付应用信息 Base VO,提供给添加、修改、详细的子 VO 使用
+* 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
+*/
+@Data
+public class PayAppBaseVO {
+
+    @ApiModelProperty(value = "应用名", required = true)
+    @NotNull(message = "应用名不能为空")
+    private String name;
+
+    @ApiModelProperty(value = "开启状态", required = true)
+    @NotNull(message = "开启状态不能为空")
+    private Integer status;
+
+    @ApiModelProperty(value = "备注")
+    private String remark;
+
+    @ApiModelProperty(value = "支付结果的回调地址", required = true)
+    @NotNull(message = "支付结果的回调地址不能为空")
+    private String payNotifyUrl;
+
+    @ApiModelProperty(value = "退款结果的回调地址", required = true)
+    @NotNull(message = "退款结果的回调地址不能为空")
+    private String refundNotifyUrl;
+
+    @ApiModelProperty(value = "商户编号", required = true)
+    @NotNull(message = "商户编号不能为空")
+    private Long merchantId;
+
+}

+ 14 - 0
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/app/vo/PayAppCreateReqVO.java

@@ -0,0 +1,14 @@
+package cn.iocoder.yudao.adminserver.modules.pay.controller.app.vo;
+
+import lombok.*;
+import java.util.*;
+import io.swagger.annotations.*;
+import javax.validation.constraints.*;
+
+@ApiModel("支付应用信息创建 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class PayAppCreateReqVO extends PayAppBaseVO {
+
+}

+ 41 - 0
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/app/vo/PayAppExcelVO.java

@@ -0,0 +1,41 @@
+package cn.iocoder.yudao.adminserver.modules.pay.controller.app.vo;
+
+import lombok.*;
+import java.util.*;
+import io.swagger.annotations.*;
+
+import com.alibaba.excel.annotation.ExcelProperty;
+
+/**
+ * 支付应用信息 Excel VO
+ *
+ * @author 芋艿
+ */
+@Data
+public class PayAppExcelVO {
+
+    @ExcelProperty("应用编号")
+    private Long id;
+
+    @ExcelProperty("应用名")
+    private String name;
+
+    @ExcelProperty("开启状态")
+    private Integer status;
+
+    @ExcelProperty("备注")
+    private String remark;
+
+    @ExcelProperty("支付结果的回调地址")
+    private String payNotifyUrl;
+
+    @ExcelProperty("退款结果的回调地址")
+    private String refundNotifyUrl;
+
+    @ExcelProperty("商户编号")
+    private Long merchantId;
+
+    @ExcelProperty("创建时间")
+    private Date createTime;
+
+}

+ 41 - 0
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/app/vo/PayAppExportReqVO.java

@@ -0,0 +1,41 @@
+package cn.iocoder.yudao.adminserver.modules.pay.controller.app.vo;
+
+import lombok.*;
+import java.util.*;
+import io.swagger.annotations.*;
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
+@ApiModel(value = "支付应用信息 Excel 导出 Request VO", description = "参数和 PayAppPageReqVO 是一致的")
+@Data
+public class PayAppExportReqVO {
+
+    @ApiModelProperty(value = "应用名")
+    private String name;
+
+    @ApiModelProperty(value = "开启状态")
+    private Integer status;
+
+    @ApiModelProperty(value = "备注")
+    private String remark;
+
+    @ApiModelProperty(value = "支付结果的回调地址")
+    private String payNotifyUrl;
+
+    @ApiModelProperty(value = "退款结果的回调地址")
+    private String refundNotifyUrl;
+
+    @ApiModelProperty(value = "商户名称")
+    private String merchantName;
+
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    @ApiModelProperty(value = "开始创建时间")
+    private Date beginCreateTime;
+
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    @ApiModelProperty(value = "结束创建时间")
+    private Date endCreateTime;
+
+}

+ 50 - 0
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/app/vo/PayAppPageItemRespVO.java

@@ -0,0 +1,50 @@
+package cn.iocoder.yudao.adminserver.modules.pay.controller.app.vo;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+import java.util.Date;
+import java.util.Set;
+
+/**
+ * 支付应用信息分页查询 Response VO
+ *
+ * @author aquan
+ */
+@ApiModel(value = "支付应用信息分页查询 Response VO", description = "相比于支付信息,还会多出应用渠道的开关信息")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class PayAppPageItemRespVO extends PayAppBaseVO {
+
+    @ApiModelProperty(value = "应用编号", required = true)
+    private Long id;
+
+    @ApiModelProperty(value = "创建时间", required = true)
+    private Date createTime;
+
+    /**
+     * 所属商户
+     */
+    private PayMerchant payMerchant;
+
+    @ApiModel("商户")
+    @Data
+    public static class PayMerchant {
+
+        @ApiModelProperty(value = "商户编号", required = true, example = "1")
+        private Long id;
+
+        @ApiModelProperty(value = "商户名称", required = true, example = "研发部")
+        private String name;
+
+    }
+
+    @ApiModelProperty(value = "渠道编码集合", required = true, example = "alipay_pc,alipay_wap...")
+    private Set<String> channelCodes;
+
+
+}

+ 43 - 0
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/app/vo/PayAppPageReqVO.java

@@ -0,0 +1,43 @@
+package cn.iocoder.yudao.adminserver.modules.pay.controller.app.vo;
+
+import lombok.*;
+import java.util.*;
+import io.swagger.annotations.*;
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
+@ApiModel("支付应用信息分页 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class PayAppPageReqVO extends PageParam {
+
+    @ApiModelProperty(value = "应用名")
+    private String name;
+
+    @ApiModelProperty(value = "开启状态")
+    private Integer status;
+
+    @ApiModelProperty(value = "备注")
+    private String remark;
+
+    @ApiModelProperty(value = "支付结果的回调地址")
+    private String payNotifyUrl;
+
+    @ApiModelProperty(value = "退款结果的回调地址")
+    private String refundNotifyUrl;
+
+    @ApiModelProperty(value = "商户名称")
+    private String merchantName;
+
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    @ApiModelProperty(value = "开始创建时间")
+    private Date beginCreateTime;
+
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    @ApiModelProperty(value = "结束创建时间")
+    private Date endCreateTime;
+
+}

+ 19 - 0
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/app/vo/PayAppRespVO.java

@@ -0,0 +1,19 @@
+package cn.iocoder.yudao.adminserver.modules.pay.controller.app.vo;
+
+import lombok.*;
+import java.util.*;
+import io.swagger.annotations.*;
+
+@ApiModel("支付应用信息 Response VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class PayAppRespVO extends PayAppBaseVO {
+
+    @ApiModelProperty(value = "应用编号", required = true)
+    private Long id;
+
+    @ApiModelProperty(value = "创建时间", required = true)
+    private Date createTime;
+
+}

+ 18 - 0
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/app/vo/PayAppUpdateReqVO.java

@@ -0,0 +1,18 @@
+package cn.iocoder.yudao.adminserver.modules.pay.controller.app.vo;
+
+import lombok.*;
+import java.util.*;
+import io.swagger.annotations.*;
+import javax.validation.constraints.*;
+
+@ApiModel("支付应用信息更新 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class PayAppUpdateReqVO extends PayAppBaseVO {
+
+    @ApiModelProperty(value = "应用编号", required = true)
+    @NotNull(message = "应用编号不能为空")
+    private Long id;
+
+}

+ 21 - 0
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/app/vo/PayAppUpdateStatusReqVO.java

@@ -0,0 +1,21 @@
+package cn.iocoder.yudao.adminserver.modules.pay.controller.app.vo;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import javax.validation.constraints.NotNull;
+
+@ApiModel("应用更新状态 Request VO")
+@Data
+public class PayAppUpdateStatusReqVO {
+
+    @ApiModelProperty(value = "商户编号", required = true, example = "1024")
+    @NotNull(message = "商户编号不能为空")
+    private Long id;
+
+    @ApiModelProperty(value = "状态", required = true, example = "1", notes = "见 SysCommonStatusEnum 枚举")
+    @NotNull(message = "状态不能为空")
+    private Integer status;
+
+}

+ 129 - 0
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/channel/PayChannelController.java

@@ -0,0 +1,129 @@
+package cn.iocoder.yudao.adminserver.modules.pay.controller.channel;
+
+import cn.iocoder.yudao.adminserver.modules.pay.controller.channel.vo.*;
+import cn.iocoder.yudao.adminserver.modules.pay.convert.channel.PayChannelConvert;
+import cn.iocoder.yudao.adminserver.modules.pay.service.channel.PayChannelService;
+import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayChannelDO;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
+import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiImplicitParam;
+import io.swagger.annotations.ApiImplicitParams;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+import javax.servlet.http.HttpServletResponse;
+import javax.validation.Valid;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT;
+
+/**
+ * 支付渠道 controller 组件
+ * @author aquan
+ */
+@Api(tags = "支付渠道")
+@RestController
+@RequestMapping("/pay/channel")
+@Validated
+public class PayChannelController {
+
+    @Resource
+    private PayChannelService channelService;
+
+
+    @PostMapping("/create")
+    @ApiOperation("创建支付渠道 ")
+    @PreAuthorize("@ss.hasPermission('pay:channel:create')")
+    public CommonResult<Long> createChannel(@Valid @RequestBody PayChannelCreateReqVO createReqVO) {
+        return success(channelService.createChannel(createReqVO));
+    }
+
+    @PutMapping("/update")
+    @ApiOperation("更新支付渠道 ")
+    @PreAuthorize("@ss.hasPermission('pay:channel:update')")
+    public CommonResult<Boolean> updateChannel(@Valid @RequestBody PayChannelUpdateReqVO updateReqVO) {
+        channelService.updateChannel(updateReqVO);
+        return success(true);
+    }
+
+    @DeleteMapping("/delete")
+    @ApiOperation("删除支付渠道 ")
+    @ApiImplicitParam(name = "id", value = "编号", required = true)
+    @PreAuthorize("@ss.hasPermission('pay:channel:delete')")
+    public CommonResult<Boolean> deleteChannel(@RequestParam("id") Long id) {
+        channelService.deleteChannel(id);
+        return success(true);
+    }
+
+    @GetMapping("/get")
+    @ApiOperation("获得支付渠道 ")
+    @ApiImplicitParam(name = "id", value = "编号", required = true, example = "1024", dataTypeClass = Long.class)
+    @PreAuthorize("@ss.hasPermission('pay:channel:query')")
+    public CommonResult<PayChannelRespVO> getChannel(@RequestParam("id") Long id) {
+        PayChannelDO channel = channelService.getChannel(id);
+        return success(PayChannelConvert.INSTANCE.convert(channel));
+    }
+
+    @GetMapping("/list")
+    @ApiOperation("获得支付渠道列表")
+    @ApiImplicitParam(name = "ids", value = "编号列表",
+            required = true, example = "1024,2048", dataTypeClass = List.class)
+    @PreAuthorize("@ss.hasPermission('pay:channel:query')")
+    public CommonResult<List<PayChannelRespVO>> getChannelList(@RequestParam("ids") Collection<Long> ids) {
+        List<PayChannelDO> list = channelService.getChannelList(ids);
+        return success(PayChannelConvert.INSTANCE.convertList(list));
+    }
+
+    @GetMapping("/page")
+    @ApiOperation("获得支付渠道分页")
+    @PreAuthorize("@ss.hasPermission('pay:channel:query')")
+    public CommonResult<PageResult<PayChannelRespVO>> getChannelPage(@Valid PayChannelPageReqVO pageVO) {
+        PageResult<PayChannelDO> pageResult = channelService.getChannelPage(pageVO);
+        return success(PayChannelConvert.INSTANCE.convertPage(pageResult));
+    }
+
+    @GetMapping("/export-excel")
+    @ApiOperation("导出支付渠道Excel")
+    @PreAuthorize("@ss.hasPermission('pay:channel:export')")
+    @OperateLog(type = EXPORT)
+    public void exportChannelExcel(@Valid PayChannelExportReqVO exportReqVO,
+                                   HttpServletResponse response) throws IOException {
+        List<PayChannelDO> list = channelService.getChannelList(exportReqVO);
+        // 导出 Excel
+        List<PayChannelExcelVO> datas = PayChannelConvert.INSTANCE.convertList02(list);
+        ExcelUtils.write(response, "支付渠道.xls", "数据", PayChannelExcelVO.class, datas);
+    }
+
+    @GetMapping("/get-channel")
+    @ApiOperation("根据条件查询微信支付渠道")
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "merchantId", value = "商户编号",
+                    required = true, example = "1", dataTypeClass = Long.class),
+            @ApiImplicitParam(name = "appId", value = "应用编号",
+                    required = true, example = "1", dataTypeClass = Long.class),
+            @ApiImplicitParam(name = "code", value = "支付渠道编码",
+                    required = true, example = "wx_pub", dataTypeClass = String.class)
+    })
+    @PreAuthorize("@ss.hasPermission('pay:channel:query')")
+    public CommonResult<PayChannelRespVO> getChannel(
+            @RequestParam Long merchantId, @RequestParam Long appId, @RequestParam String code) {
+
+        // 獲取渠道
+        PayChannelDO channel = channelService.getChannelByConditions(merchantId, appId, code);
+        if (channel == null) {
+            return success(new PayChannelRespVO());
+        }
+        // 拼凑数据
+        PayChannelRespVO respVo = PayChannelConvert.INSTANCE.convert(channel);
+        return success(respVo);
+    }
+}

+ 39 - 0
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/channel/vo/PayChannelBaseVO.java

@@ -0,0 +1,39 @@
+package cn.iocoder.yudao.adminserver.modules.pay.controller.channel.vo;
+
+import lombok.*;
+import java.util.*;
+import io.swagger.annotations.*;
+import javax.validation.constraints.*;
+
+/**
+* 支付渠道
+ Base VO,提供给添加、修改、详细的子 VO 使用
+* 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
+*/
+@Data
+public class PayChannelBaseVO {
+
+    @ApiModelProperty(value = "渠道编码", required = true)
+    @NotNull(message = "渠道编码不能为空")
+    private String code;
+
+    @ApiModelProperty(value = "开启状态", required = true)
+    @NotNull(message = "开启状态不能为空")
+    private Integer status;
+
+    @ApiModelProperty(value = "备注")
+    private String remark;
+
+    @ApiModelProperty(value = "渠道费率,单位:百分比", required = true)
+    @NotNull(message = "渠道费率,单位:百分比不能为空")
+    private Double feeRate;
+
+    @ApiModelProperty(value = "商户编号", required = true)
+    @NotNull(message = "商户编号不能为空")
+    private Long merchantId;
+
+    @ApiModelProperty(value = "应用编号", required = true)
+    @NotNull(message = "应用编号不能为空")
+    private Long appId;
+
+}

+ 21 - 0
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/channel/vo/PayChannelCreateReqVO.java

@@ -0,0 +1,21 @@
+package cn.iocoder.yudao.adminserver.modules.pay.controller.channel.vo;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+import javax.validation.constraints.NotBlank;
+
+@ApiModel("支付渠道 创建 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class PayChannelCreateReqVO extends PayChannelBaseVO {
+
+    @ApiModelProperty(value = "通道配置的 json 字符串")
+    @NotBlank(message = "通道配置不能为空")
+    private String config;
+
+}

+ 50 - 0
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/channel/vo/PayChannelExcelVO.java

@@ -0,0 +1,50 @@
+package cn.iocoder.yudao.adminserver.modules.pay.controller.channel.vo;
+
+import lombok.*;
+import java.util.*;
+import io.swagger.annotations.*;
+
+import com.alibaba.excel.annotation.ExcelProperty;
+
+/**
+ * 支付渠道
+ Excel VO
+ *
+ * @author 芋艿
+ */
+@Data
+public class PayChannelExcelVO {
+
+    @ExcelProperty("商户编号")
+    private Long id;
+
+    @ExcelProperty("渠道编码")
+    private String code;
+
+    @ExcelProperty("开启状态")
+    private Integer status;
+
+    @ExcelProperty("备注")
+    private String remark;
+
+    @ExcelProperty("渠道费率,单位:百分比")
+    private Double feeRate;
+
+    @ExcelProperty("商户编号")
+    private Long merchantId;
+
+    @ExcelProperty("应用编号")
+    private Long appId;
+
+    /**
+     * todo @芋艿 mapStruct 存在转换问题
+     * java: Can't map property "PayClientConfig payChannelDO.config" to "String payChannelExcelVO.config".
+     * Consider to declare/implement a mapping method: "String map(PayClientConfig value)".
+     */
+    /// @ExcelProperty("支付渠道配置")
+    /// private String config;
+
+    @ExcelProperty("创建时间")
+    private Date createTime;
+
+}

+ 44 - 0
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/channel/vo/PayChannelExportReqVO.java

@@ -0,0 +1,44 @@
+package cn.iocoder.yudao.adminserver.modules.pay.controller.channel.vo;
+
+import lombok.*;
+import java.util.*;
+import io.swagger.annotations.*;
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
+@ApiModel(value = "支付渠道 Excel 导出 Request VO", description = "参数和 PayChannelPageReqVO 是一致的")
+@Data
+public class PayChannelExportReqVO {
+
+    @ApiModelProperty(value = "渠道编码")
+    private String code;
+
+    @ApiModelProperty(value = "开启状态")
+    private Integer status;
+
+    @ApiModelProperty(value = "备注")
+    private String remark;
+
+    @ApiModelProperty(value = "渠道费率,单位:百分比")
+    private Double feeRate;
+
+    @ApiModelProperty(value = "商户编号")
+    private Long merchantId;
+
+    @ApiModelProperty(value = "应用编号")
+    private Long appId;
+
+    @ApiModelProperty(value = "支付渠道配置")
+    private String config;
+
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    @ApiModelProperty(value = "开始创建时间")
+    private Date beginCreateTime;
+
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    @ApiModelProperty(value = "结束创建时间")
+    private Date endCreateTime;
+
+}

+ 46 - 0
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/channel/vo/PayChannelPageReqVO.java

@@ -0,0 +1,46 @@
+package cn.iocoder.yudao.adminserver.modules.pay.controller.channel.vo;
+
+import lombok.*;
+import java.util.*;
+import io.swagger.annotations.*;
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
+@ApiModel("支付渠道 分页 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class PayChannelPageReqVO extends PageParam {
+
+    @ApiModelProperty(value = "渠道编码")
+    private String code;
+
+    @ApiModelProperty(value = "开启状态")
+    private Integer status;
+
+    @ApiModelProperty(value = "备注")
+    private String remark;
+
+    @ApiModelProperty(value = "渠道费率,单位:百分比")
+    private Double feeRate;
+
+    @ApiModelProperty(value = "商户编号")
+    private Long merchantId;
+
+    @ApiModelProperty(value = "应用编号")
+    private Long appId;
+
+    @ApiModelProperty(value = "支付渠道配置")
+    private String config;
+
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    @ApiModelProperty(value = "开始创建时间")
+    private Date beginCreateTime;
+
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    @ApiModelProperty(value = "结束创建时间")
+    private Date endCreateTime;
+
+}

+ 21 - 0
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/channel/vo/PayChannelRespVO.java

@@ -0,0 +1,21 @@
+package cn.iocoder.yudao.adminserver.modules.pay.controller.channel.vo;
+
+import lombok.*;
+import java.util.*;
+import io.swagger.annotations.*;
+
+@ApiModel("支付渠道 Response VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class PayChannelRespVO extends PayChannelBaseVO {
+
+    @ApiModelProperty(value = "商户编号", required = true)
+    private Long id;
+
+    @ApiModelProperty(value = "创建时间", required = true)
+    private Date createTime;
+
+    @ApiModelProperty(value = "配置", required = true)
+    private String config;
+}

+ 21 - 0
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/channel/vo/PayChannelUpdateReqVO.java

@@ -0,0 +1,21 @@
+package cn.iocoder.yudao.adminserver.modules.pay.controller.channel.vo;
+
+import lombok.*;
+import java.util.*;
+import io.swagger.annotations.*;
+import javax.validation.constraints.*;
+
+@ApiModel("支付渠道 更新 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class PayChannelUpdateReqVO extends PayChannelBaseVO {
+
+    @ApiModelProperty(value = "商户编号", required = true)
+    @NotNull(message = "商户编号不能为空")
+    private Long id;
+
+    @ApiModelProperty(value = "通道配置的json字符串")
+    @NotBlank(message = "通道配置不能为空")
+    private String config;
+}

+ 116 - 0
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/merchant/PayMerchantController.java

@@ -0,0 +1,116 @@
+package cn.iocoder.yudao.adminserver.modules.pay.controller.merchant;
+
+import cn.iocoder.yudao.adminserver.modules.pay.controller.merchant.vo.*;
+import cn.iocoder.yudao.adminserver.modules.pay.convert.merchant.PayMerchantConvert;
+import cn.iocoder.yudao.adminserver.modules.pay.service.merchant.PayMerchantService;
+import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayMerchantDO;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
+import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiImplicitParam;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+import javax.servlet.http.HttpServletResponse;
+import javax.validation.Valid;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT;
+
+@Api(tags = "支付商户信息")
+@RestController
+@RequestMapping("/pay/merchant")
+@Validated
+public class PayMerchantController {
+
+    @Resource
+    private PayMerchantService merchantService;
+
+    @PostMapping("/create")
+    @ApiOperation("创建支付商户信息")
+    @PreAuthorize("@ss.hasPermission('pay:merchant:create')")
+    public CommonResult<Long> createMerchant(@Valid @RequestBody PayMerchantCreateReqVO createReqVO) {
+        return success(merchantService.createMerchant(createReqVO));
+    }
+
+    @PutMapping("/update")
+    @ApiOperation("更新支付商户信息")
+    @PreAuthorize("@ss.hasPermission('pay:merchant:update')")
+    public CommonResult<Boolean> updateMerchant(@Valid @RequestBody PayMerchantUpdateReqVO updateReqVO) {
+        merchantService.updateMerchant(updateReqVO);
+        return success(true);
+    }
+
+    @PutMapping("/update-status")
+    @ApiOperation("修改支付商户状态")
+    @PreAuthorize("@ss.hasPermission('pay:merchant:update')")
+    public CommonResult<Boolean> updateMerchantStatus(@Valid @RequestBody PayMerchantUpdateStatusReqVO reqVO) {
+        merchantService.updateMerchantStatus(reqVO.getId(), reqVO.getStatus());
+        return success(true);
+    }
+
+    @DeleteMapping("/delete")
+    @ApiOperation("删除支付商户信息")
+    @ApiImplicitParam(name = "id", value = "编号", required = true)
+    @PreAuthorize("@ss.hasPermission('pay:merchant:delete')")
+    public CommonResult<Boolean> deleteMerchant(@RequestParam("id") Long id) {
+        merchantService.deleteMerchant(id);
+        return success(true);
+    }
+
+    @GetMapping("/get")
+    @ApiOperation("获得支付商户信息")
+    @ApiImplicitParam(name = "id", value = "编号", required = true, example = "1024", dataTypeClass = Long.class)
+    @PreAuthorize("@ss.hasPermission('pay:merchant:query')")
+    public CommonResult<PayMerchantRespVO> getMerchant(@RequestParam("id") Long id) {
+        PayMerchantDO merchant = merchantService.getMerchant(id);
+        return success(PayMerchantConvert.INSTANCE.convert(merchant));
+    }
+
+    @GetMapping("/list-by-name")
+    @ApiOperation("根据商户名称获得支付商户信息列表")
+    @ApiImplicitParam(name = "name", value = "商户名称", example = "芋道", dataTypeClass = Long.class)
+    @PreAuthorize("@ss.hasPermission('pay:merchant:query')")
+    public CommonResult<List<PayMerchantRespVO>> getMerchantListByName(@RequestParam(required = false) String name) {
+        List<PayMerchantDO> merchantListDO = merchantService.getMerchantListByName(name);
+        return success(PayMerchantConvert.INSTANCE.convertList(merchantListDO));
+    }
+
+    @GetMapping("/list")
+    @ApiOperation("获得支付商户信息列表")
+    @ApiImplicitParam(name = "ids", value = "编号列表", required = true, example = "1024,2048", dataTypeClass = List.class)
+    @PreAuthorize("@ss.hasPermission('pay:merchant:query')")
+    public CommonResult<List<PayMerchantRespVO>> getMerchantList(@RequestParam("ids") Collection<Long> ids) {
+        List<PayMerchantDO> list = merchantService.getMerchantList(ids);
+        return success(PayMerchantConvert.INSTANCE.convertList(list));
+    }
+
+    @GetMapping("/page")
+    @ApiOperation("获得支付商户信息分页")
+    @PreAuthorize("@ss.hasPermission('pay:merchant:query')")
+    public CommonResult<PageResult<PayMerchantRespVO>> getMerchantPage(@Valid PayMerchantPageReqVO pageVO) {
+        PageResult<PayMerchantDO> pageResult = merchantService.getMerchantPage(pageVO);
+        return success(PayMerchantConvert.INSTANCE.convertPage(pageResult));
+    }
+
+    @GetMapping("/export-excel")
+    @ApiOperation("导出支付商户信息 Excel")
+    @PreAuthorize("@ss.hasPermission('pay:merchant:export')")
+    @OperateLog(type = EXPORT)
+    public void exportMerchantExcel(@Valid PayMerchantExportReqVO exportReqVO,
+              HttpServletResponse response) throws IOException {
+        List<PayMerchantDO> list = merchantService.getMerchantList(exportReqVO);
+        // 导出 Excel
+        List<PayMerchantExcelVO> datas = PayMerchantConvert.INSTANCE.convertList02(list);
+        ExcelUtils.write(response, "支付商户信息.xls", "数据", PayMerchantExcelVO.class, datas);
+    }
+
+}

+ 30 - 0
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/merchant/vo/PayMerchantBaseVO.java

@@ -0,0 +1,30 @@
+package cn.iocoder.yudao.adminserver.modules.pay.controller.merchant.vo;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import javax.validation.constraints.NotNull;
+
+/**
+* 支付商户信息 Base VO,提供给添加、修改、详细的子 VO 使用
+* 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
+*/
+@Data
+public class PayMerchantBaseVO {
+
+    @ApiModelProperty(value = "商户全称", required = true)
+    @NotNull(message = "商户全称不能为空")
+    private String name;
+
+    @ApiModelProperty(value = "商户简称", required = true)
+    @NotNull(message = "商户简称不能为空")
+    private String shortName;
+
+    @ApiModelProperty(value = "开启状态", required = true)
+    @NotNull(message = "开启状态不能为空")
+    private Integer status;
+
+    @ApiModelProperty(value = "备注")
+    private String remark;
+
+}

+ 14 - 0
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/merchant/vo/PayMerchantCreateReqVO.java

@@ -0,0 +1,14 @@
+package cn.iocoder.yudao.adminserver.modules.pay.controller.merchant.vo;
+
+import lombok.*;
+import java.util.*;
+import io.swagger.annotations.*;
+import javax.validation.constraints.*;
+
+@ApiModel("支付商户信息创建 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class PayMerchantCreateReqVO extends PayMerchantBaseVO {
+
+}

+ 40 - 0
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/merchant/vo/PayMerchantExcelVO.java

@@ -0,0 +1,40 @@
+package cn.iocoder.yudao.adminserver.modules.pay.controller.merchant.vo;
+
+import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
+import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
+import com.alibaba.excel.annotation.ExcelProperty;
+import lombok.Data;
+
+import java.util.Date;
+
+/**
+ * 支付商户信息 Excel VO
+ *
+ * @author 芋艿
+ */
+@Data
+public class PayMerchantExcelVO {
+
+    @ExcelProperty("商户编号")
+    private Long id;
+
+    @ExcelProperty("商户号")
+    private String no;
+
+    @ExcelProperty("商户全称")
+    private String name;
+
+    @ExcelProperty("商户简称")
+    private String shortName;
+
+    @ExcelProperty(value = "开启状态",converter = DictConvert.class)
+    @DictFormat("sys_common_status")
+    private Integer status;
+
+    @ExcelProperty("备注")
+    private String remark;
+
+    @ExcelProperty("创建时间")
+    private Date createTime;
+
+}

+ 38 - 0
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/merchant/vo/PayMerchantExportReqVO.java

@@ -0,0 +1,38 @@
+package cn.iocoder.yudao.adminserver.modules.pay.controller.merchant.vo;
+
+import lombok.*;
+import java.util.*;
+import io.swagger.annotations.*;
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
+@ApiModel(value = "支付商户信息 Excel 导出 Request VO", description = "参数和 PayMerchantPageReqVO 是一致的")
+@Data
+public class PayMerchantExportReqVO {
+
+    @ApiModelProperty(value = "商户号")
+    private String no;
+
+    @ApiModelProperty(value = "商户全称")
+    private String name;
+
+    @ApiModelProperty(value = "商户简称")
+    private String shortName;
+
+    @ApiModelProperty(value = "开启状态")
+    private Integer status;
+
+    @ApiModelProperty(value = "备注")
+    private String remark;
+
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    @ApiModelProperty(value = "开始创建时间")
+    private Date beginCreateTime;
+
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    @ApiModelProperty(value = "结束创建时间")
+    private Date endCreateTime;
+
+}

+ 40 - 0
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/merchant/vo/PayMerchantPageReqVO.java

@@ -0,0 +1,40 @@
+package cn.iocoder.yudao.adminserver.modules.pay.controller.merchant.vo;
+
+import lombok.*;
+import java.util.*;
+import io.swagger.annotations.*;
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
+@ApiModel("支付商户信息分页 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class PayMerchantPageReqVO extends PageParam {
+
+    @ApiModelProperty(value = "商户号")
+    private String no;
+
+    @ApiModelProperty(value = "商户全称")
+    private String name;
+
+    @ApiModelProperty(value = "商户简称")
+    private String shortName;
+
+    @ApiModelProperty(value = "开启状态")
+    private Integer status;
+
+    @ApiModelProperty(value = "备注")
+    private String remark;
+
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    @ApiModelProperty(value = "开始创建时间")
+    private Date beginCreateTime;
+
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    @ApiModelProperty(value = "结束创建时间")
+    private Date endCreateTime;
+
+}

+ 30 - 0
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/merchant/vo/PayMerchantRespVO.java

@@ -0,0 +1,30 @@
+package cn.iocoder.yudao.adminserver.modules.pay.controller.merchant.vo;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+import java.util.Date;
+
+@ApiModel("支付商户信息 Response VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class PayMerchantRespVO extends PayMerchantBaseVO {
+
+    @ApiModelProperty(value = "商户编号", required = true)
+    private Long id;
+
+    @ApiModelProperty(value = "创建时间", required = true)
+    private Date createTime;
+
+    /**
+     * 商户号
+     * 例如说,M233666999
+     * 只有新增时插入,不允许修改
+     */
+    private String no;
+
+}

+ 18 - 0
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/merchant/vo/PayMerchantUpdateReqVO.java

@@ -0,0 +1,18 @@
+package cn.iocoder.yudao.adminserver.modules.pay.controller.merchant.vo;
+
+import lombok.*;
+import java.util.*;
+import io.swagger.annotations.*;
+import javax.validation.constraints.*;
+
+@ApiModel("支付商户信息更新 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class PayMerchantUpdateReqVO extends PayMerchantBaseVO {
+
+    @ApiModelProperty(value = "商户编号", required = true)
+    @NotNull(message = "商户编号不能为空")
+    private Long id;
+
+}

+ 21 - 0
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/merchant/vo/PayMerchantUpdateStatusReqVO.java

@@ -0,0 +1,21 @@
+package cn.iocoder.yudao.adminserver.modules.pay.controller.merchant.vo;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import javax.validation.constraints.NotNull;
+
+@ApiModel("商户更新状态 Request VO")
+@Data
+public class PayMerchantUpdateStatusReqVO {
+
+    @ApiModelProperty(value = "商户编号", required = true, example = "1024")
+    @NotNull(message = "商户编号不能为空")
+    private Long id;
+
+    @ApiModelProperty(value = "状态", required = true, example = "1", notes = "见 SysCommonStatusEnum 枚举")
+    @NotNull(message = "状态不能为空")
+    private Integer status;
+
+}

+ 177 - 0
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/order/PayOrderController.java

@@ -0,0 +1,177 @@
+package cn.iocoder.yudao.adminserver.modules.pay.controller.order;
+
+import cn.hutool.core.collection.CollectionUtil;
+import cn.hutool.core.util.ObjectUtil;
+import cn.iocoder.yudao.adminserver.modules.pay.controller.order.vo.order.*;
+import cn.iocoder.yudao.adminserver.modules.pay.convert.order.PayOrderConvert;
+import cn.iocoder.yudao.adminserver.modules.pay.service.app.PayAppService;
+import cn.iocoder.yudao.adminserver.modules.pay.service.merchant.PayMerchantService;
+import cn.iocoder.yudao.adminserver.modules.pay.service.order.PayOrderExtensionService;
+import cn.iocoder.yudao.adminserver.modules.pay.service.order.PayOrderService;
+import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayAppDO;
+import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayMerchantDO;
+import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayOrderDO;
+import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayOrderExtensionDO;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
+import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
+import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
+import cn.iocoder.yudao.framework.pay.core.enums.PayChannelEnum;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiImplicitParam;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+import javax.servlet.http.HttpServletResponse;
+import javax.validation.Valid;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT;
+
+/**
+ * 支付订单 controller 组件
+ *
+ * @author aquan
+ */
+@Api(tags = "支付订单")
+@RestController
+@RequestMapping("/pay/order")
+@Validated
+public class PayOrderController {
+
+    /**
+     * 订单 service 组件
+     */
+    @Resource
+    private PayOrderService orderService;
+
+    /**
+     * 订单扩展 service 组件
+     */
+    @Resource
+    private PayOrderExtensionService orderExtensionService;
+
+    /**
+     * 商户 service 组件
+     */
+    @Resource
+    private PayMerchantService merchantService;
+
+    /**
+     * 应用 service 组件
+     */
+    @Resource
+    private PayAppService appService;
+
+    @GetMapping("/get")
+    @ApiOperation("获得支付订单")
+    @ApiImplicitParam(name = "id", value = "编号", required = true, example = "1024", dataTypeClass = Long.class)
+    @PreAuthorize("@ss.hasPermission('pay:order:query')")
+    public CommonResult<PayOrderDetailsRespVO> getOrder(@RequestParam("id") Long id) {
+        PayOrderDO order = orderService.getOrder(id);
+        if (ObjectUtil.isNull(order)) {
+            return success(new PayOrderDetailsRespVO());
+        }
+
+        PayMerchantDO merchantDO = merchantService.getMerchant(order.getMerchantId());
+        PayAppDO appDO = appService.getApp(order.getAppId());
+        PayChannelEnum channelEnum = PayChannelEnum.getByCode(order.getChannelCode());
+
+        PayOrderDetailsRespVO respVO = PayOrderConvert.INSTANCE.orderDetailConvert(order);
+        respVO.setMerchantName(ObjectUtil.isNotNull(merchantDO) ? merchantDO.getName() : "未知商户");
+        respVO.setAppName(ObjectUtil.isNotNull(appDO) ? appDO.getName() : "未知应用");
+        respVO.setChannelCodeName(ObjectUtil.isNotNull(channelEnum) ? channelEnum.getName() : "未知渠道");
+
+        PayOrderExtensionDO extensionDO = orderExtensionService.getOrderExtension(order.getSuccessExtensionId());
+        if (ObjectUtil.isNotNull(extensionDO)) {
+            respVO.setPayOrderExtension(PayOrderConvert.INSTANCE.orderDetailExtensionConvert(extensionDO));
+        }
+
+        return success(respVO);
+    }
+
+    @GetMapping("/page")
+    @ApiOperation("获得支付订单分页")
+    @PreAuthorize("@ss.hasPermission('pay:order:query')")
+    public CommonResult<PageResult<PayOrderPageItemRespVO>> getOrderPage(@Valid PayOrderPageReqVO pageVO) {
+        PageResult<PayOrderDO> pageResult = orderService.getOrderPage(pageVO);
+        if (CollectionUtil.isEmpty(pageResult.getList())) {
+            return success(new PageResult<>(pageResult.getTotal()));
+        }
+
+        // 处理商户ID数据
+        Map<Long, PayMerchantDO> merchantMap = merchantService.getMerchantMap(
+                CollectionUtils.convertList(pageResult.getList(), PayOrderDO::getMerchantId));
+        // 处理应用ID数据
+        Map<Long, PayAppDO> appMap = appService.getAppMap(
+                CollectionUtils.convertList(pageResult.getList(), PayOrderDO::getAppId));
+
+        List<PayOrderPageItemRespVO> pageList = new ArrayList<>(pageResult.getList().size());
+        pageResult.getList().forEach(c -> {
+            PayMerchantDO merchantDO = merchantMap.get(c.getMerchantId());
+            PayAppDO appDO = appMap.get(c.getAppId());
+            PayChannelEnum channelEnum = PayChannelEnum.getByCode(c.getChannelCode());
+
+            PayOrderPageItemRespVO orderItem = PayOrderConvert.INSTANCE.pageConvertItemPage(c);
+            orderItem.setMerchantName(ObjectUtil.isNotNull(merchantDO) ? merchantDO.getName() : "未知商户");
+            orderItem.setAppName(ObjectUtil.isNotNull(appDO) ? appDO.getName() : "未知应用");
+            orderItem.setChannelCodeName(ObjectUtil.isNotNull(channelEnum) ? channelEnum.getName() : "未知渠道");
+            pageList.add(orderItem);
+        });
+        return success(new PageResult<>(pageList, pageResult.getTotal()));
+    }
+
+    @GetMapping("/export-excel")
+    @ApiOperation("导出支付订单Excel")
+    @PreAuthorize("@ss.hasPermission('pay:order:export')")
+    @OperateLog(type = EXPORT)
+    public void exportOrderExcel(@Valid PayOrderExportReqVO exportReqVO,
+            HttpServletResponse response) throws IOException {
+
+        List<PayOrderDO> list = orderService.getOrderList(exportReqVO);
+        if (CollectionUtil.isEmpty(list)) {
+            ExcelUtils.write(response, "支付订单.xls", "数据",
+                    PayOrderExcelVO.class, new ArrayList<>());
+        }
+
+        // 处理商户ID数据
+        Map<Long, PayMerchantDO> merchantMap = merchantService.getMerchantMap(
+                CollectionUtils.convertList(list, PayOrderDO::getMerchantId));
+        // 处理应用ID数据
+        Map<Long, PayAppDO> appMap = appService.getAppMap(
+                CollectionUtils.convertList(list, PayOrderDO::getAppId));
+        // 处理扩展订单数据
+        Map<Long, PayOrderExtensionDO> orderExtensionMap = orderExtensionService
+                .getOrderExtensionMap(CollectionUtils.convertList(list, PayOrderDO::getSuccessExtensionId));
+
+        List<PayOrderExcelVO> excelDatum = new ArrayList<>(list.size());
+        list.forEach(c -> {
+            PayMerchantDO merchantDO = merchantMap.get(c.getMerchantId());
+            PayAppDO appDO = appMap.get(c.getAppId());
+            PayChannelEnum channelEnum = PayChannelEnum.getByCode(c.getChannelCode());
+            PayOrderExtensionDO orderExtensionDO = orderExtensionMap.get(c.getSuccessExtensionId());
+
+            PayOrderExcelVO excelItem = PayOrderConvert.INSTANCE.excelConvert(c);
+            excelItem.setMerchantName(ObjectUtil.isNotNull(merchantDO) ? merchantDO.getName() : "未知商户");
+            excelItem.setAppName(ObjectUtil.isNotNull(appDO) ? appDO.getName() : "未知应用");
+            excelItem.setChannelCodeName(ObjectUtil.isNotNull(channelEnum) ? channelEnum.getName() : "未知渠道");
+            excelItem.setNo(ObjectUtil.isNotNull(orderExtensionDO) ? orderExtensionDO.getNo() : "");
+            excelDatum.add(excelItem);
+        });
+
+        // 导出 Excel
+        ExcelUtils.write(response, "支付订单.xls", "数据", PayOrderExcelVO.class, excelDatum);
+    }
+
+}

+ 174 - 0
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/order/PayRefundController.java

@@ -0,0 +1,174 @@
+package cn.iocoder.yudao.adminserver.modules.pay.controller.order;
+
+import cn.hutool.core.collection.CollectionUtil;
+import cn.hutool.core.util.ObjectUtil;
+import cn.iocoder.yudao.adminserver.modules.pay.controller.order.vo.refund.vo.*;
+import cn.iocoder.yudao.adminserver.modules.pay.convert.refund.PayRefundConvert;
+import cn.iocoder.yudao.adminserver.modules.pay.service.app.PayAppService;
+import cn.iocoder.yudao.adminserver.modules.pay.service.merchant.PayMerchantService;
+import cn.iocoder.yudao.adminserver.modules.pay.service.order.PayOrderService;
+import cn.iocoder.yudao.adminserver.modules.pay.service.order.PayRefundService;
+import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayAppDO;
+import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayMerchantDO;
+import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayOrderDO;
+import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayRefundDO;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
+import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
+import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
+import cn.iocoder.yudao.framework.pay.core.enums.PayChannelEnum;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiImplicitParam;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+import javax.servlet.http.HttpServletResponse;
+import javax.validation.Valid;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT;
+
+/**
+ * 退款订单 Controller 组件
+ *
+ * @author aquan
+ */
+@Api(tags = "退款订单")
+@RestController
+@RequestMapping("/pay/refund")
+@Validated
+public class PayRefundController {
+
+    @Resource
+    private PayRefundService refundService;
+
+    /**
+     * 商户 service 组件
+     */
+    @Resource
+    private PayMerchantService merchantService;
+
+    /**
+     * 应用 service 组件
+     */
+    @Resource
+    private PayAppService appService;
+
+    /**
+     * 订单 service 组件
+     */
+    @Resource
+    private PayOrderService orderService;
+
+
+    @GetMapping("/get")
+    @ApiOperation("获得退款订单")
+    @ApiImplicitParam(name = "id", value = "编号", required = true, example = "1024", dataTypeClass = Long.class)
+    @PreAuthorize("@ss.hasPermission('pay:refund:query')")
+    public CommonResult<PayRefundDetailsRespVO> getRefund(@RequestParam("id") Long id) {
+        PayRefundDO refund = refundService.getRefund(id);
+        if (ObjectUtil.isNull(refund)) {
+            return success(new PayRefundDetailsRespVO());
+        }
+
+        PayMerchantDO merchantDO = merchantService.getMerchant(refund.getMerchantId());
+        PayAppDO appDO = appService.getApp(refund.getAppId());
+        PayChannelEnum channelEnum = PayChannelEnum.getByCode(refund.getChannelCode());
+        PayOrderDO orderDO = orderService.getOrder(refund.getOrderId());
+
+        PayRefundDetailsRespVO refundDetail = PayRefundConvert.INSTANCE.refundDetailConvert(refund);
+        refundDetail.setMerchantName(ObjectUtil.isNotNull(merchantDO) ? merchantDO.getName() : "未知商户");
+        refundDetail.setAppName(ObjectUtil.isNotNull(appDO) ? appDO.getName() : "未知应用");
+        refundDetail.setChannelCodeName(ObjectUtil.isNotNull(channelEnum) ? channelEnum.getName() : "未知渠道");
+        refundDetail.setSubject(orderDO.getSubject());
+
+        return success(refundDetail);
+    }
+
+    @GetMapping("/page")
+    @ApiOperation("获得退款订单分页")
+    @PreAuthorize("@ss.hasPermission('pay:refund:query')")
+    public CommonResult<PageResult<PayRefundPageItemRespVO>> getRefundPage(@Valid PayRefundPageReqVO pageVO) {
+        PageResult<PayRefundDO> pageResult = refundService.getRefundPage(pageVO);
+        if (CollectionUtil.isEmpty(pageResult.getList())) {
+            return success(new PageResult<>(pageResult.getTotal()));
+        }
+
+        // 处理商户ID数据
+        Map<Long, PayMerchantDO> merchantMap = merchantService.getMerchantMap(
+                CollectionUtils.convertList(pageResult.getList(), PayRefundDO::getMerchantId));
+        // 处理应用ID数据
+        Map<Long, PayAppDO> appMap = appService.getAppMap(
+                CollectionUtils.convertList(pageResult.getList(), PayRefundDO::getAppId));
+        List<PayRefundPageItemRespVO> list = new ArrayList<>(pageResult.getList().size());
+        pageResult.getList().forEach(c -> {
+            PayMerchantDO merchantDO = merchantMap.get(c.getMerchantId());
+            PayAppDO appDO = appMap.get(c.getAppId());
+            PayChannelEnum channelEnum = PayChannelEnum.getByCode(c.getChannelCode());
+
+            PayRefundPageItemRespVO item = PayRefundConvert.INSTANCE.pageItemConvert(c);
+
+            item.setMerchantName(ObjectUtil.isNotNull(merchantDO) ? merchantDO.getName() : "未知商户");
+            item.setAppName(ObjectUtil.isNotNull(appDO) ? appDO.getName() : "未知应用");
+            item.setChannelCodeName(ObjectUtil.isNotNull(channelEnum) ? channelEnum.getName() : "未知渠道");
+            list.add(item);
+        });
+
+        return success(new PageResult<>(list, pageResult.getTotal()));
+    }
+
+    @GetMapping("/export-excel")
+    @ApiOperation("导出退款订单 Excel")
+    @PreAuthorize("@ss.hasPermission('pay:refund:export')")
+    @OperateLog(type = EXPORT)
+    public void exportRefundExcel(@Valid PayRefundExportReqVO exportReqVO,
+            HttpServletResponse response) throws IOException {
+
+        List<PayRefundDO> list = refundService.getRefundList(exportReqVO);
+        if (CollectionUtil.isEmpty(list)) {
+            ExcelUtils.write(response, "退款订单.xls", "数据",
+                    PayRefundExcelVO.class, new ArrayList<>());
+        }
+
+        // 处理商户ID数据
+        Map<Long, PayMerchantDO> merchantMap = merchantService.getMerchantMap(
+                CollectionUtils.convertList(list, PayRefundDO::getMerchantId));
+        // 处理应用ID数据
+        Map<Long, PayAppDO> appMap = appService.getAppMap(
+                CollectionUtils.convertList(list, PayRefundDO::getAppId));
+
+        List<PayRefundExcelVO> excelDatum = new ArrayList<>(list.size());
+        // 处理商品名称数据
+        Map<Long, PayOrderDO> orderMap = orderService.getOrderSubjectMap(
+                CollectionUtils.convertList(list, PayRefundDO::getOrderId));
+
+        list.forEach(c -> {
+            PayMerchantDO merchantDO = merchantMap.get(c.getMerchantId());
+            PayAppDO appDO = appMap.get(c.getAppId());
+            PayChannelEnum channelEnum = PayChannelEnum.getByCode(c.getChannelCode());
+
+            PayRefundExcelVO excelItem = PayRefundConvert.INSTANCE.excelConvert(c);
+            excelItem.setMerchantName(ObjectUtil.isNotNull(merchantDO) ? merchantDO.getName() : "未知商户");
+            excelItem.setAppName(ObjectUtil.isNotNull(appDO) ? appDO.getName() : "未知应用");
+            excelItem.setChannelCodeName(ObjectUtil.isNotNull(channelEnum) ? channelEnum.getName() : "未知渠道");
+            excelItem.setSubject(orderMap.get(c.getOrderId()).getSubject());
+
+            excelDatum.add(excelItem);
+        });
+
+        // 导出 Excel
+        ExcelUtils.write(response, "退款订单.xls", "数据", PayRefundExcelVO.class, excelDatum);
+    }
+
+}

+ 108 - 0
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/order/vo/order/PayOrderBaseVO.java

@@ -0,0 +1,108 @@
+package cn.iocoder.yudao.adminserver.modules.pay.controller.order.vo.order;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import javax.validation.constraints.NotNull;
+import java.util.Date;
+
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
+/**
+ * 支付订单
+ * Base VO,提供给添加、修改、详细的子 VO 使用
+ * 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
+ *
+ * @author aquan
+ */
+@Data
+public class PayOrderBaseVO {
+
+    @ApiModelProperty(value = "商户编号", required = true)
+    @NotNull(message = "商户编号不能为空")
+    private Long merchantId;
+
+    @ApiModelProperty(value = "应用编号", required = true)
+    @NotNull(message = "应用编号不能为空")
+    private Long appId;
+
+    @ApiModelProperty(value = "渠道编号")
+    private Long channelId;
+
+    @ApiModelProperty(value = "渠道编码")
+    private String channelCode;
+
+    @ApiModelProperty(value = "商户订单编号", required = true)
+    @NotNull(message = "商户订单编号不能为空")
+    private String merchantOrderId;
+
+    @ApiModelProperty(value = "商品标题", required = true)
+    @NotNull(message = "商品标题不能为空")
+    private String subject;
+
+    @ApiModelProperty(value = "商品描述", required = true)
+    @NotNull(message = "商品描述不能为空")
+    private String body;
+
+    @ApiModelProperty(value = "异步通知地址", required = true)
+    @NotNull(message = "异步通知地址不能为空")
+    private String notifyUrl;
+
+    @ApiModelProperty(value = "通知商户支付结果的回调状态", required = true)
+    @NotNull(message = "通知商户支付结果的回调状态不能为空")
+    private Integer notifyStatus;
+
+    @ApiModelProperty(value = "支付金额,单位:分", required = true)
+    @NotNull(message = "支付金额,单位:分不能为空")
+    private Long amount;
+
+    @ApiModelProperty(value = "渠道手续费,单位:百分比")
+    private Double channelFeeRate;
+
+    @ApiModelProperty(value = "渠道手续金额,单位:分")
+    private Long channelFeeAmount;
+
+    @ApiModelProperty(value = "支付状态", required = true)
+    @NotNull(message = "支付状态不能为空")
+    private Integer status;
+
+    @ApiModelProperty(value = "用户 IP", required = true)
+    @NotNull(message = "用户 IP不能为空")
+    private String userIp;
+
+    @ApiModelProperty(value = "订单失效时间", required = true)
+    @NotNull(message = "订单失效时间不能为空")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    private Date expireTime;
+
+    @ApiModelProperty(value = "订单支付成功时间")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    private Date successTime;
+
+    @ApiModelProperty(value = "订单支付通知时间")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    private Date notifyTime;
+
+    @ApiModelProperty(value = "支付成功的订单拓展单编号")
+    private Long successExtensionId;
+
+    @ApiModelProperty(value = "退款状态", required = true)
+    @NotNull(message = "退款状态不能为空")
+    private Integer refundStatus;
+
+    @ApiModelProperty(value = "退款次数", required = true)
+    @NotNull(message = "退款次数不能为空")
+    private Integer refundTimes;
+
+    @ApiModelProperty(value = "退款总金额,单位:分", required = true)
+    @NotNull(message = "退款总金额,单位:分不能为空")
+    private Long refundAmount;
+
+    @ApiModelProperty(value = "渠道用户编号")
+    private String channelUserId;
+
+    @ApiModelProperty(value = "渠道订单号")
+    private String channelOrderNo;
+
+}

+ 53 - 0
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/order/vo/order/PayOrderDetailsRespVO.java

@@ -0,0 +1,53 @@
+package cn.iocoder.yudao.adminserver.modules.pay.controller.order.vo.order;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+import java.util.Date;
+
+/**
+ * 支付订单详细信息,由支付订单和支付订单扩展信息组成
+ *
+ * @author aquan
+ */
+@ApiModel("支付订单详细信息 Response VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class PayOrderDetailsRespVO extends PayOrderBaseVO {
+
+    @ApiModelProperty(value = "支付订单编号")
+    private Long id;
+
+    @ApiModelProperty(value = "商户名称")
+    private String merchantName;
+
+    @ApiModelProperty(value = "应用名称")
+    private String appName;
+
+    @ApiModelProperty(value = "渠道编号名称")
+    private String channelCodeName;
+
+    @ApiModelProperty(value = "创建时间")
+    private Date createTime;
+
+    /**
+     * 支付订单扩展
+     */
+    private PayOrderExtension payOrderExtension;
+
+    @Data
+    @ApiModel("支付订单扩展")
+    public static class PayOrderExtension {
+
+        @ApiModelProperty(value = "支付订单号")
+        private String no;
+
+        @ApiModelProperty(value = "支付异步通知的内容")
+        private String channelNotifyData;
+    }
+
+}

+ 91 - 0
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/order/vo/order/PayOrderExcelVO.java

@@ -0,0 +1,91 @@
+package cn.iocoder.yudao.adminserver.modules.pay.controller.order.vo.order;
+
+import cn.iocoder.yudao.adminserver.modules.system.enums.SysDictTypeConstants;
+import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
+import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
+import com.alibaba.excel.annotation.ExcelProperty;
+import lombok.Data;
+
+import java.util.Date;
+
+/**
+ * 支付订单Excel VO
+ *
+ * @author aquan
+ */
+@Data
+public class PayOrderExcelVO {
+
+    @ExcelProperty("支付订单编号")
+    private Long id;
+
+    @ExcelProperty(value = "商户名称")
+    private String merchantName;
+
+    @ExcelProperty(value = "应用名称")
+    private String appName;
+
+    @ExcelProperty("商品标题")
+    private String subject;
+
+    @ExcelProperty("商户订单编号")
+    private String merchantOrderId;
+
+    @ExcelProperty("渠道订单号")
+    private String channelOrderNo;
+
+    @ExcelProperty(value = "支付订单号")
+    private String no;
+
+    @ExcelProperty("支付金额,单位:元")
+    private String amount;
+
+    @ExcelProperty("渠道手续金额,单位:元")
+    private String channelFeeAmount;
+
+    @ExcelProperty("渠道手续费,单位:百分比")
+    private String channelFeeRate;
+
+    @DictFormat(SysDictTypeConstants.PAY_ORDER_STATUS)
+    @ExcelProperty(value = "支付状态", converter = DictConvert.class)
+    private Integer status;
+
+    @DictFormat(SysDictTypeConstants.PAY_ORDER_NOTIFY_STATUS)
+    @ExcelProperty(value = "通知商户支付结果的回调状态", converter = DictConvert.class)
+    private Integer notifyStatus;
+
+    @ExcelProperty("异步通知地址")
+    private String notifyUrl;
+
+    @ExcelProperty("创建时间")
+    private Date createTime;
+
+    @ExcelProperty("订单支付成功时间")
+    private Date successTime;
+
+    @ExcelProperty("订单失效时间")
+    private Date expireTime;
+
+    @ExcelProperty("订单支付通知时间")
+    private Date notifyTime;
+
+    @ExcelProperty(value = "渠道编号名称")
+    private String channelCodeName;
+
+    @ExcelProperty("用户 IP")
+    private String userIp;
+
+    @DictFormat(SysDictTypeConstants.PAY_ORDER_REFUND_STATUS)
+    @ExcelProperty(value = "退款状态", converter = DictConvert.class)
+    private Integer refundStatus;
+
+    @ExcelProperty("退款次数")
+    private Integer refundTimes;
+
+    @ExcelProperty("退款总金额,单位:元")
+    private String  refundAmount;
+
+    @ExcelProperty("商品描述")
+    private String body;
+
+}

+ 112 - 0
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/order/vo/order/PayOrderExportReqVO.java

@@ -0,0 +1,112 @@
+package cn.iocoder.yudao.adminserver.modules.pay.controller.order.vo.order;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.util.Date;
+
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
+/**
+ * 支付订单 Excel 导出 Request VO
+ * @author aquan
+ */
+@ApiModel(value = "支付订单 Excel 导出 Request VO", description = "参数和 PayOrderPageReqVO 是一致的")
+@Data
+public class PayOrderExportReqVO {
+
+    @ApiModelProperty(value = "商户编号")
+    private Long merchantId;
+
+    @ApiModelProperty(value = "应用编号")
+    private Long appId;
+
+    @ApiModelProperty(value = "渠道编号")
+    private Long channelId;
+
+    @ApiModelProperty(value = "渠道编码")
+    private String channelCode;
+
+    @ApiModelProperty(value = "商户订单编号")
+    private String merchantOrderId;
+
+    @ApiModelProperty(value = "商品标题")
+    private String subject;
+
+    @ApiModelProperty(value = "商品描述")
+    private String body;
+
+    @ApiModelProperty(value = "异步通知地址")
+    private String notifyUrl;
+
+    @ApiModelProperty(value = "通知商户支付结果的回调状态")
+    private Integer notifyStatus;
+
+    @ApiModelProperty(value = "支付金额,单位:分")
+    private Long amount;
+
+    @ApiModelProperty(value = "渠道手续费,单位:百分比")
+    private Double channelFeeRate;
+
+    @ApiModelProperty(value = "渠道手续金额,单位:分")
+    private Long channelFeeAmount;
+
+    @ApiModelProperty(value = "支付状态")
+    private Integer status;
+
+    @ApiModelProperty(value = "用户 IP")
+    private String userIp;
+
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    @ApiModelProperty(value = "开始订单失效时间")
+    private Date beginExpireTime;
+
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    @ApiModelProperty(value = "结束订单失效时间")
+    private Date endExpireTime;
+
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    @ApiModelProperty(value = "开始订单支付成功时间")
+    private Date beginSuccessTime;
+
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    @ApiModelProperty(value = "结束订单支付成功时间")
+    private Date endSuccessTime;
+
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    @ApiModelProperty(value = "开始订单支付通知时间")
+    private Date beginNotifyTime;
+
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    @ApiModelProperty(value = "结束订单支付通知时间")
+    private Date endNotifyTime;
+
+    @ApiModelProperty(value = "支付成功的订单拓展单编号")
+    private Long successExtensionId;
+
+    @ApiModelProperty(value = "退款状态")
+    private Integer refundStatus;
+
+    @ApiModelProperty(value = "退款次数")
+    private Integer refundTimes;
+
+    @ApiModelProperty(value = "退款总金额,单位:分")
+    private Long refundAmount;
+
+    @ApiModelProperty(value = "渠道用户编号")
+    private String channelUserId;
+
+    @ApiModelProperty(value = "渠道订单号")
+    private String channelOrderNo;
+
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    @ApiModelProperty(value = "开始创建时间")
+    private Date beginCreateTime;
+
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    @ApiModelProperty(value = "结束创建时间")
+    private Date endCreateTime;
+
+}

+ 41 - 0
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/order/vo/order/PayOrderPageItemRespVO.java

@@ -0,0 +1,41 @@
+package cn.iocoder.yudao.adminserver.modules.pay.controller.order.vo.order;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+import java.util.Date;
+
+/**
+ * 支付订单分页 Request VO
+ *
+ * @author aquan
+ */
+@ApiModel("支付订单分页 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class PayOrderPageItemRespVO extends PayOrderBaseVO {
+
+    @ApiModelProperty(value = "支付订单编号", required = true)
+    private Long id;
+
+    @ApiModelProperty(value = "创建时间", required = true)
+    private Date createTime;
+
+    @ApiModelProperty(value = "商户名称")
+    private String merchantName;
+
+    @ApiModelProperty(value = "应用名称")
+    private String  appName;
+
+    @ApiModelProperty(value = "渠道名称")
+    private String channelCodeName;
+
+    @ApiModelProperty(value = "支付订单号")
+    private String no;
+
+
+}

+ 118 - 0
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/order/vo/order/PayOrderPageReqVO.java

@@ -0,0 +1,118 @@
+package cn.iocoder.yudao.adminserver.modules.pay.controller.order.vo.order;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.util.Date;
+
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
+/**
+ * 支付订单分页 Request VO
+ *
+ * @author aquan
+ */
+@ApiModel("支付订单分页 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class PayOrderPageReqVO extends PageParam {
+
+    @ApiModelProperty(value = "商户编号")
+    private Long merchantId;
+
+    @ApiModelProperty(value = "应用编号")
+    private Long appId;
+
+    @ApiModelProperty(value = "渠道编号")
+    private Long channelId;
+
+    @ApiModelProperty(value = "渠道编码")
+    private String channelCode;
+
+    @ApiModelProperty(value = "商户订单编号")
+    private String merchantOrderId;
+
+    @ApiModelProperty(value = "商品标题")
+    private String subject;
+
+    @ApiModelProperty(value = "商品描述")
+    private String body;
+
+    @ApiModelProperty(value = "异步通知地址")
+    private String notifyUrl;
+
+    @ApiModelProperty(value = "通知商户支付结果的回调状态")
+    private Integer notifyStatus;
+
+    @ApiModelProperty(value = "支付金额,单位:分")
+    private Long amount;
+
+    @ApiModelProperty(value = "渠道手续费,单位:百分比")
+    private Double channelFeeRate;
+
+    @ApiModelProperty(value = "渠道手续金额,单位:分")
+    private Long channelFeeAmount;
+
+    @ApiModelProperty(value = "支付状态")
+    private Integer status;
+
+    @ApiModelProperty(value = "用户 IP")
+    private String userIp;
+
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    @ApiModelProperty(value = "开始订单失效时间")
+    private Date beginExpireTime;
+
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    @ApiModelProperty(value = "结束订单失效时间")
+    private Date endExpireTime;
+
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    @ApiModelProperty(value = "开始订单支付成功时间")
+    private Date beginSuccessTime;
+
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    @ApiModelProperty(value = "结束订单支付成功时间")
+    private Date endSuccessTime;
+
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    @ApiModelProperty(value = "开始订单支付通知时间")
+    private Date beginNotifyTime;
+
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    @ApiModelProperty(value = "结束订单支付通知时间")
+    private Date endNotifyTime;
+
+    @ApiModelProperty(value = "支付成功的订单拓展单编号")
+    private Long successExtensionId;
+
+    @ApiModelProperty(value = "退款状态")
+    private Integer refundStatus;
+
+    @ApiModelProperty(value = "退款次数")
+    private Integer refundTimes;
+
+    @ApiModelProperty(value = "退款总金额,单位:分")
+    private Long refundAmount;
+
+    @ApiModelProperty(value = "渠道用户编号")
+    private String channelUserId;
+
+    @ApiModelProperty(value = "渠道订单号")
+    private String channelOrderNo;
+
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    @ApiModelProperty(value = "开始创建时间")
+    private Date beginCreateTime;
+
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    @ApiModelProperty(value = "结束创建时间")
+    private Date endCreateTime;
+
+}

+ 28 - 0
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/order/vo/order/PayOrderRespVO.java

@@ -0,0 +1,28 @@
+package cn.iocoder.yudao.adminserver.modules.pay.controller.order.vo.order;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+import java.util.Date;
+
+/**
+ * 支付订单 Response VO
+ *
+ * @author aquan
+ */
+@ApiModel("支付订单 Response VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class PayOrderRespVO extends PayOrderBaseVO {
+
+    @ApiModelProperty(value = "支付订单编号", required = true)
+    private Long id;
+
+    @ApiModelProperty(value = "创建时间", required = true)
+    private Date createTime;
+
+}

+ 110 - 0
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/order/vo/refund/vo/PayRefundBaseVO.java

@@ -0,0 +1,110 @@
+package cn.iocoder.yudao.adminserver.modules.pay.controller.order.vo.refund.vo;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import javax.validation.constraints.NotNull;
+import java.util.Date;
+
+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 PayRefundBaseVO {
+
+    @ApiModelProperty(value = "商户编号", required = true)
+    @NotNull(message = "商户编号不能为空")
+    private Long merchantId;
+
+    @ApiModelProperty(value = "应用编号", required = true)
+    @NotNull(message = "应用编号不能为空")
+    private Long appId;
+
+    @ApiModelProperty(value = "渠道编号", required = true)
+    @NotNull(message = "渠道编号不能为空")
+    private Long channelId;
+
+    @ApiModelProperty(value = "渠道编码", required = true)
+    @NotNull(message = "渠道编码不能为空")
+    private String channelCode;
+
+    @ApiModelProperty(value = "支付订单编号 pay_order 表id", required = true)
+    @NotNull(message = "支付订单编号 pay_order 表id不能为空")
+    private Long orderId;
+
+    @ApiModelProperty(value = "交易订单号 pay_extension 表no 字段", required = true)
+    @NotNull(message = "交易订单号 pay_extension 表no 字段不能为空")
+    private String tradeNo;
+
+    @ApiModelProperty(value = "商户订单编号(商户系统生成)", required = true)
+    @NotNull(message = "商户订单编号(商户系统生成)不能为空")
+    private String merchantOrderId;
+
+    @ApiModelProperty(value = "商户退款订单号(商户系统生成)", required = true)
+    @NotNull(message = "商户退款订单号(商户系统生成)不能为空")
+    private String merchantRefundNo;
+
+    @ApiModelProperty(value = "异步通知商户地址", required = true)
+    @NotNull(message = "异步通知商户地址不能为空")
+    private String notifyUrl;
+
+    @ApiModelProperty(value = "通知商户退款结果的回调状态", required = true)
+    @NotNull(message = "通知商户退款结果的回调状态不能为空")
+    private Integer notifyStatus;
+
+    @ApiModelProperty(value = "退款状态", required = true)
+    @NotNull(message = "退款状态不能为空")
+    private Integer status;
+
+    @ApiModelProperty(value = "退款类型(部分退款,全部退款)", required = true)
+    @NotNull(message = "退款类型(部分退款,全部退款)不能为空")
+    private Integer type;
+
+    @ApiModelProperty(value = "支付金额,单位分", required = true)
+    @NotNull(message = "支付金额,单位分不能为空")
+    private Long payAmount;
+
+    @ApiModelProperty(value = "退款金额,单位分", required = true)
+    @NotNull(message = "退款金额,单位分不能为空")
+    private Long refundAmount;
+
+    @ApiModelProperty(value = "退款原因", required = true)
+    @NotNull(message = "退款原因不能为空")
+    private String reason;
+
+    @ApiModelProperty(value = "用户 IP")
+    private String userIp;
+
+    @ApiModelProperty(value = "渠道订单号,pay_order 中的channel_order_no 对应", required = true)
+    @NotNull(message = "渠道订单号,pay_order 中的channel_order_no 对应不能为空")
+    private String channelOrderNo;
+
+    @ApiModelProperty(value = "渠道退款单号,渠道返回")
+    private String channelRefundNo;
+
+    @ApiModelProperty(value = "渠道调用报错时,错误码")
+    private String channelErrorCode;
+
+    @ApiModelProperty(value = "渠道调用报错时,错误信息")
+    private String channelErrorMsg;
+
+    @ApiModelProperty(value = "支付渠道的额外参数")
+    private String channelExtras;
+
+    @ApiModelProperty(value = "退款失效时间")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    private Date expireTime;
+
+    @ApiModelProperty(value = "退款成功时间")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    private Date successTime;
+
+    @ApiModelProperty(value = "退款通知时间")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    private Date notifyTime;
+
+}

+ 12 - 0
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/order/vo/refund/vo/PayRefundCreateReqVO.java

@@ -0,0 +1,12 @@
+package cn.iocoder.yudao.adminserver.modules.pay.controller.order.vo.refund.vo;
+
+import lombok.*;
+import io.swagger.annotations.*;
+
+@ApiModel("退款订单创建 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class PayRefundCreateReqVO extends PayRefundBaseVO {
+
+}

+ 44 - 0
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/order/vo/refund/vo/PayRefundDetailsRespVO.java

@@ -0,0 +1,44 @@
+package cn.iocoder.yudao.adminserver.modules.pay.controller.order.vo.refund.vo;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+import javax.validation.constraints.NotNull;
+import java.util.Date;
+
+/**
+ * 退款订单详情 Response VO
+ *
+ * @author aquan
+ */
+@ApiModel("退款订单详情 Response VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class PayRefundDetailsRespVO extends PayRefundBaseVO {
+
+    @ApiModelProperty(value = "支付退款编号", required = true)
+    private Long id;
+
+    @ApiModelProperty(value = "商户名称")
+    private String merchantName;
+
+    @ApiModelProperty(value = "应用名称")
+    private String appName;
+
+    @ApiModelProperty(value = "渠道编号名称")
+    private String channelCodeName;
+
+    @NotNull(message = "商品标题不能为空")
+    private String subject;
+
+    @ApiModelProperty(value = "创建时间")
+    private Date createTime;
+
+    @ApiModelProperty(value = "更新时间")
+    private Date updateTime;
+
+}

+ 88 - 0
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/order/vo/refund/vo/PayRefundExcelVO.java

@@ -0,0 +1,88 @@
+package cn.iocoder.yudao.adminserver.modules.pay.controller.order.vo.refund.vo;
+
+import cn.iocoder.yudao.adminserver.modules.system.enums.SysDictTypeConstants;
+import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
+import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
+import com.alibaba.excel.annotation.ExcelProperty;
+import lombok.Data;
+
+import java.util.Date;
+
+/**
+ * 退款订单 Excel VO
+ *
+ * @author aquan
+ */
+@Data
+public class PayRefundExcelVO {
+
+    @ExcelProperty("支付退款编号")
+    private Long id;
+
+    @ExcelProperty("商品名称")
+    private String subject;
+
+    @ExcelProperty(value = "商户名称")
+    private String merchantName;
+
+    @ExcelProperty(value = "应用名称")
+    private String appName;
+
+    @ExcelProperty(value = "渠道编号名称")
+    private String channelCodeName;
+
+    @ExcelProperty("交易订单号")
+    private String tradeNo;
+
+    @ExcelProperty("商户订单编号")
+    private String merchantOrderId;
+
+    @ExcelProperty("商户退款订单号")
+    private String merchantRefundNo;
+
+    @ExcelProperty("异步通知商户地址")
+    private String notifyUrl;
+
+    @DictFormat(SysDictTypeConstants.PAY_ORDER_NOTIFY_STATUS)
+    @ExcelProperty(value = "商户退款结果回调状态", converter = DictConvert.class)
+    private Integer notifyStatus;
+
+    @DictFormat(SysDictTypeConstants.PAY_REFUND_ORDER_STATUS)
+    @ExcelProperty(value = "退款状态", converter = DictConvert.class)
+    private Integer status;
+
+    @DictFormat(SysDictTypeConstants.PAY_REFUND_ORDER_TYPE)
+    @ExcelProperty(value = "退款类型", converter = DictConvert.class)
+    private Integer type;
+
+    @ExcelProperty("支付金额,单位:元")
+    private String payAmount;
+
+    @ExcelProperty("退款金额,单位:元")
+    private String refundAmount;
+
+    @ExcelProperty("退款原因")
+    private String reason;
+
+    @ExcelProperty("用户付款 IP")
+    private String userIp;
+
+    @ExcelProperty("渠道订单号")
+    private String channelOrderNo;
+
+    @ExcelProperty("渠道退款单号")
+    private String channelRefundNo;
+
+    @ExcelProperty("创建时间")
+    private Date createTime;
+
+    @ExcelProperty("退款成功时间")
+    private Date successTime;
+
+    @ExcelProperty("退款通知时间")
+    private Date notifyTime;
+
+    @ExcelProperty("退款失效时间")
+    private Date expireTime;
+
+}

+ 111 - 0
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/order/vo/refund/vo/PayRefundExportReqVO.java

@@ -0,0 +1,111 @@
+package cn.iocoder.yudao.adminserver.modules.pay.controller.order.vo.refund.vo;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.util.Date;
+
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
+@ApiModel(value = "退款订单 Excel 导出 Request VO", description = "参数和 PayRefundPageReqVO 是一致的")
+@Data
+public class PayRefundExportReqVO {
+
+    @ApiModelProperty(value = "商户编号")
+    private Long merchantId;
+
+    @ApiModelProperty(value = "应用编号")
+    private Long appId;
+
+    @ApiModelProperty(value = "渠道编号")
+    private Long channelId;
+
+    @ApiModelProperty(value = "渠道编码")
+    private String channelCode;
+
+    @ApiModelProperty(value = "支付订单编号 pay_order 表id")
+    private Long orderId;
+
+    @ApiModelProperty(value = "交易订单号 pay_extension 表no 字段")
+    private String tradeNo;
+
+    @ApiModelProperty(value = "商户订单编号(商户系统生成)")
+    private String merchantOrderId;
+
+    @ApiModelProperty(value = "商户退款订单号(商户系统生成)")
+    private String merchantRefundNo;
+
+    @ApiModelProperty(value = "异步通知商户地址")
+    private String notifyUrl;
+
+    @ApiModelProperty(value = "通知商户退款结果的回调状态")
+    private Integer notifyStatus;
+
+    @ApiModelProperty(value = "退款状态")
+    private Integer status;
+
+    @ApiModelProperty(value = "退款类型(部分退款,全部退款)")
+    private Integer type;
+
+    @ApiModelProperty(value = "支付金额,单位分")
+    private Long payAmount;
+
+    @ApiModelProperty(value = "退款金额,单位分")
+    private Long refundAmount;
+
+    @ApiModelProperty(value = "退款原因")
+    private String reason;
+
+    @ApiModelProperty(value = "用户 IP")
+    private String userIp;
+
+    @ApiModelProperty(value = "渠道订单号,pay_order 中的channel_order_no 对应")
+    private String channelOrderNo;
+
+    @ApiModelProperty(value = "渠道退款单号,渠道返回")
+    private String channelRefundNo;
+
+    @ApiModelProperty(value = "渠道调用报错时,错误码")
+    private String channelErrorCode;
+
+    @ApiModelProperty(value = "渠道调用报错时,错误信息")
+    private String channelErrorMsg;
+
+    @ApiModelProperty(value = "支付渠道的额外参数")
+    private String channelExtras;
+
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    @ApiModelProperty(value = "开始退款失效时间")
+    private Date beginExpireTime;
+
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    @ApiModelProperty(value = "结束退款失效时间")
+    private Date endExpireTime;
+
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    @ApiModelProperty(value = "开始退款成功时间")
+    private Date beginSuccessTime;
+
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    @ApiModelProperty(value = "结束退款成功时间")
+    private Date endSuccessTime;
+
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    @ApiModelProperty(value = "开始退款通知时间")
+    private Date beginNotifyTime;
+
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    @ApiModelProperty(value = "结束退款通知时间")
+    private Date endNotifyTime;
+
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    @ApiModelProperty(value = "开始创建时间")
+    private Date beginCreateTime;
+
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    @ApiModelProperty(value = "结束创建时间")
+    private Date endCreateTime;
+
+}

+ 36 - 0
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/order/vo/refund/vo/PayRefundPageItemRespVO.java

@@ -0,0 +1,36 @@
+package cn.iocoder.yudao.adminserver.modules.pay.controller.order.vo.refund.vo;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+import java.util.Date;
+
+/**
+ * 退款订单分页查询 Response VO
+ * @author aquan
+ */
+@ApiModel("退款订单分页查询 Response VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class PayRefundPageItemRespVO extends PayRefundBaseVO {
+
+    @ApiModelProperty(value = "支付订单编号", required = true)
+    private Long id;
+
+    @ApiModelProperty(value = "商户名称")
+    private String merchantName;
+
+    @ApiModelProperty(value = "应用名称")
+    private String  appName;
+
+    @ApiModelProperty(value = "渠道名称")
+    private String channelCodeName;
+
+    @ApiModelProperty(value = "创建时间", required = true)
+    private Date createTime;
+
+}

+ 116 - 0
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/order/vo/refund/vo/PayRefundPageReqVO.java

@@ -0,0 +1,116 @@
+package cn.iocoder.yudao.adminserver.modules.pay.controller.order.vo.refund.vo;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.util.Date;
+
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
+@ApiModel("退款订单分页 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class PayRefundPageReqVO extends PageParam {
+
+    @ApiModelProperty(value = "商户编号")
+    private Long merchantId;
+
+    @ApiModelProperty(value = "应用编号")
+    private Long appId;
+
+    @ApiModelProperty(value = "渠道编号")
+    private Long channelId;
+
+    @ApiModelProperty(value = "渠道编码")
+    private String channelCode;
+
+    @ApiModelProperty(value = "支付订单编号 pay_order 表id")
+    private Long orderId;
+
+    @ApiModelProperty(value = "交易订单号 pay_extension 表no 字段")
+    private String tradeNo;
+
+    @ApiModelProperty(value = "商户订单编号(商户系统生成)")
+    private String merchantOrderId;
+
+    @ApiModelProperty(value = "商户退款订单号(商户系统生成)")
+    private String merchantRefundNo;
+
+    @ApiModelProperty(value = "异步通知商户地址")
+    private String notifyUrl;
+
+    @ApiModelProperty(value = "通知商户退款结果的回调状态")
+    private Integer notifyStatus;
+
+    @ApiModelProperty(value = "退款状态")
+    private Integer status;
+
+    @ApiModelProperty(value = "退款类型(部分退款,全部退款)")
+    private Integer type;
+
+    @ApiModelProperty(value = "支付金额,单位分")
+    private Long payAmount;
+
+    @ApiModelProperty(value = "退款金额,单位分")
+    private Long refundAmount;
+
+    @ApiModelProperty(value = "退款原因")
+    private String reason;
+
+    @ApiModelProperty(value = "用户 IP")
+    private String userIp;
+
+    @ApiModelProperty(value = "渠道订单号,pay_order 中的channel_order_no 对应")
+    private String channelOrderNo;
+
+    @ApiModelProperty(value = "渠道退款单号,渠道返回")
+    private String channelRefundNo;
+
+    @ApiModelProperty(value = "渠道调用报错时,错误码")
+    private String channelErrorCode;
+
+    @ApiModelProperty(value = "渠道调用报错时,错误信息")
+    private String channelErrorMsg;
+
+    @ApiModelProperty(value = "支付渠道的额外参数")
+    private String channelExtras;
+
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    @ApiModelProperty(value = "开始退款失效时间")
+    private Date beginExpireTime;
+
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    @ApiModelProperty(value = "结束退款失效时间")
+    private Date endExpireTime;
+
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    @ApiModelProperty(value = "开始退款成功时间")
+    private Date beginSuccessTime;
+
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    @ApiModelProperty(value = "结束退款成功时间")
+    private Date endSuccessTime;
+
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    @ApiModelProperty(value = "开始退款通知时间")
+    private Date beginNotifyTime;
+
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    @ApiModelProperty(value = "结束退款通知时间")
+    private Date endNotifyTime;
+
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    @ApiModelProperty(value = "开始创建时间")
+    private Date beginCreateTime;
+
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    @ApiModelProperty(value = "结束创建时间")
+    private Date endCreateTime;
+
+}

+ 23 - 0
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/order/vo/refund/vo/PayRefundRespVO.java

@@ -0,0 +1,23 @@
+package cn.iocoder.yudao.adminserver.modules.pay.controller.order.vo.refund.vo;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+import java.util.Date;
+
+@ApiModel("退款订单 Response VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class PayRefundRespVO extends PayRefundBaseVO {
+
+    @ApiModelProperty(value = "支付退款编号", required = true)
+    private Long id;
+
+    @ApiModelProperty(value = "创建时间", required = true)
+    private Date createTime;
+
+}

+ 17 - 0
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/controller/order/vo/refund/vo/PayRefundUpdateReqVO.java

@@ -0,0 +1,17 @@
+package cn.iocoder.yudao.adminserver.modules.pay.controller.order.vo.refund.vo;
+
+import lombok.*;
+import io.swagger.annotations.*;
+import javax.validation.constraints.*;
+
+@ApiModel("退款订单更新 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class PayRefundUpdateReqVO extends PayRefundBaseVO {
+
+    @ApiModelProperty(value = "支付退款编号", required = true)
+    @NotNull(message = "支付退款编号不能为空")
+    private Long id;
+
+}

+ 41 - 0
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/convert/app/PayAppConvert.java

@@ -0,0 +1,41 @@
+package cn.iocoder.yudao.adminserver.modules.pay.convert.app;
+
+import java.util.*;
+
+import cn.iocoder.yudao.adminserver.modules.system.controller.user.vo.user.SysUserPageItemRespVO;
+import cn.iocoder.yudao.adminserver.modules.system.dal.dataobject.dept.SysDeptDO;
+import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayAppDO;
+import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayMerchantDO;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+
+import org.mapstruct.Mapper;
+import org.mapstruct.factory.Mappers;
+import cn.iocoder.yudao.adminserver.modules.pay.controller.app.vo.*;
+
+/**
+ * 支付应用信息 Convert
+ *
+ * @author 芋艿
+ */
+@Mapper
+public interface PayAppConvert {
+
+    PayAppConvert INSTANCE = Mappers.getMapper(PayAppConvert.class);
+
+    PayAppPageItemRespVO pageConvert (PayAppDO bean);
+
+    PayAppPageItemRespVO.PayMerchant convert(PayMerchantDO bean);
+
+    PayAppDO convert(PayAppCreateReqVO bean);
+
+    PayAppDO convert(PayAppUpdateReqVO bean);
+
+    PayAppRespVO convert(PayAppDO bean);
+
+    List<PayAppRespVO> convertList(List<PayAppDO> list);
+
+    PageResult<PayAppRespVO> convertPage(PageResult<PayAppDO> page);
+
+    List<PayAppExcelVO> convertList02(List<PayAppDO> list);
+
+}

+ 42 - 0
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/convert/channel/PayChannelConvert.java

@@ -0,0 +1,42 @@
+package cn.iocoder.yudao.adminserver.modules.pay.convert.channel;
+
+import java.util.*;
+
+import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayChannelDO;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+
+import cn.iocoder.yudao.framework.pay.core.client.impl.wx.WXPayClientConfig;
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+import org.mapstruct.factory.Mappers;
+import cn.iocoder.yudao.adminserver.modules.pay.controller.channel.vo.*;
+
+/**
+ * 支付渠道
+ Convert
+ *
+ * @author 芋艿
+ */
+@Mapper
+public interface PayChannelConvert {
+
+    PayChannelConvert INSTANCE = Mappers.getMapper(PayChannelConvert.class);
+
+    @Mapping(target = "config",ignore = true)
+    PayChannelDO convert(PayChannelCreateReqVO bean);
+
+    @Mapping(target = "config",ignore = true)
+    PayChannelDO convert(PayChannelUpdateReqVO bean);
+
+    @Mapping(target = "config",expression = "java(cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString(bean.getConfig()))")
+    PayChannelRespVO convert(PayChannelDO bean);
+
+    List<PayChannelRespVO> convertList(List<PayChannelDO> list);
+
+    PageResult<PayChannelRespVO> convertPage(PageResult<PayChannelDO> page);
+
+    List<PayChannelExcelVO> convertList02(List<PayChannelDO> list);
+
+
+
+}

+ 34 - 0
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/convert/merchant/PayMerchantConvert.java

@@ -0,0 +1,34 @@
+package cn.iocoder.yudao.adminserver.modules.pay.convert.merchant;
+
+import java.util.*;
+
+import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayMerchantDO;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+
+import org.mapstruct.Mapper;
+import org.mapstruct.factory.Mappers;
+import cn.iocoder.yudao.adminserver.modules.pay.controller.merchant.vo.*;
+
+/**
+ * 支付商户信息 Convert
+ *
+ * @author 芋艿
+ */
+@Mapper
+public interface PayMerchantConvert {
+
+    PayMerchantConvert INSTANCE = Mappers.getMapper(PayMerchantConvert.class);
+
+    PayMerchantDO convert(PayMerchantCreateReqVO bean);
+
+    PayMerchantDO convert(PayMerchantUpdateReqVO bean);
+
+    PayMerchantRespVO convert(PayMerchantDO bean);
+
+    List<PayMerchantRespVO> convertList(List<PayMerchantDO> list);
+
+    PageResult<PayMerchantRespVO> convertPage(PageResult<PayMerchantDO> page);
+
+    List<PayMerchantExcelVO> convertList02(List<PayMerchantDO> list);
+
+}

+ 104 - 0
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/convert/order/PayOrderConvert.java

@@ -0,0 +1,104 @@
+package cn.iocoder.yudao.adminserver.modules.pay.convert.order;
+
+import cn.iocoder.yudao.adminserver.modules.pay.controller.order.vo.order.PayOrderDetailsRespVO;
+import cn.iocoder.yudao.adminserver.modules.pay.controller.order.vo.order.PayOrderExcelVO;
+import cn.iocoder.yudao.adminserver.modules.pay.controller.order.vo.order.PayOrderPageItemRespVO;
+import cn.iocoder.yudao.adminserver.modules.pay.controller.order.vo.order.PayOrderRespVO;
+import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayOrderDO;
+import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayOrderExtensionDO;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import org.mapstruct.Mapper;
+import org.mapstruct.factory.Mappers;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.util.List;
+
+/**
+ * 支付订单 Convert
+ *
+ * @author aquan
+ */
+@Mapper
+public interface PayOrderConvert {
+
+    PayOrderConvert INSTANCE = Mappers.getMapper(PayOrderConvert.class);
+
+
+
+    PayOrderRespVO convert(PayOrderDO bean);
+
+    /**
+     * 订单DO 转换为 详细订单 RespVO
+     *
+     * @param bean 订单DO
+     * @return 详细订单 RespVO
+     */
+    PayOrderDetailsRespVO orderDetailConvert(PayOrderDO bean);
+
+    /**
+     * 订单扩展DO 转换为 详细订单扩展 RespVO
+     *
+     * @param bean 订单扩展DO
+     * @return 详细订单扩展 RespVO
+     */
+    PayOrderDetailsRespVO.PayOrderExtension orderDetailExtensionConvert(PayOrderExtensionDO bean);
+
+    List<PayOrderRespVO> convertList(List<PayOrderDO> list);
+
+    PageResult<PayOrderRespVO> convertPage(PageResult<PayOrderDO> page);
+
+    List<PayOrderExcelVO> convertList02(List<PayOrderDO> list);
+
+    /**
+     * 订单DO转自定义分页对象
+     *
+     * @param bean 订单DO
+     * @return 分页对象
+     */
+    PayOrderPageItemRespVO pageConvertItemPage(PayOrderDO bean);
+
+    /**
+     * 订单DO 转 订单导出excel VO
+     *
+     * @param bean 订单 DO
+     * @return 订单导出excel VO
+     */
+    default PayOrderExcelVO excelConvert(PayOrderDO bean) {
+        if (bean == null) {
+            return null;
+        }
+
+        PayOrderExcelVO payOrderExcelVO = new PayOrderExcelVO();
+
+        payOrderExcelVO.setId(bean.getId());
+        payOrderExcelVO.setSubject(bean.getSubject());
+        payOrderExcelVO.setMerchantOrderId(bean.getMerchantOrderId());
+        payOrderExcelVO.setChannelOrderNo(bean.getChannelOrderNo());
+        payOrderExcelVO.setStatus(bean.getStatus());
+        payOrderExcelVO.setNotifyStatus(bean.getNotifyStatus());
+        payOrderExcelVO.setNotifyUrl(bean.getNotifyUrl());
+        payOrderExcelVO.setCreateTime(bean.getCreateTime());
+        payOrderExcelVO.setSuccessTime(bean.getSuccessTime());
+        payOrderExcelVO.setExpireTime(bean.getExpireTime());
+        payOrderExcelVO.setNotifyTime(bean.getNotifyTime());
+        payOrderExcelVO.setUserIp(bean.getUserIp());
+        payOrderExcelVO.setRefundStatus(bean.getRefundStatus());
+        payOrderExcelVO.setRefundTimes(bean.getRefundTimes());
+        payOrderExcelVO.setBody(bean.getBody());
+
+        BigDecimal multiple = new BigDecimal(100);
+
+        payOrderExcelVO.setAmount(BigDecimal.valueOf(bean.getAmount())
+                .divide(multiple, 2, RoundingMode.HALF_UP).toString());
+
+        payOrderExcelVO.setChannelFeeAmount(BigDecimal.valueOf(bean.getChannelFeeAmount())
+                .divide(multiple, 2, RoundingMode.HALF_UP).toString());
+        payOrderExcelVO.setChannelFeeRate(java.math.BigDecimal.valueOf(bean.getChannelFeeRate())
+                .multiply(multiple).toString());
+        payOrderExcelVO.setRefundAmount(BigDecimal.valueOf(bean.getRefundAmount())
+                .divide(multiple, 2, RoundingMode.HALF_UP).toString());
+
+        return payOrderExcelVO;
+    }
+}

+ 90 - 0
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/convert/refund/PayRefundConvert.java

@@ -0,0 +1,90 @@
+package cn.iocoder.yudao.adminserver.modules.pay.convert.refund;
+
+import cn.iocoder.yudao.adminserver.modules.pay.controller.order.vo.refund.vo.*;
+import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayRefundDO;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import org.mapstruct.Mapper;
+import org.mapstruct.factory.Mappers;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.util.List;
+
+/**
+ * 退款订单 Convert
+ *
+ * @author aquan
+ */
+@Mapper
+public interface PayRefundConvert {
+
+    PayRefundConvert INSTANCE = Mappers.getMapper(PayRefundConvert.class);
+
+    PayRefundDO convert(PayRefundCreateReqVO bean);
+
+    PayRefundDO convert(PayRefundUpdateReqVO bean);
+
+    PayRefundRespVO convert(PayRefundDO bean);
+
+    /**
+     * 退款订单 DO 转 退款详情订单 VO
+     *
+     * @param bean 退款订单 DO
+     * @return 退款详情订单 VO
+     */
+    PayRefundDetailsRespVO refundDetailConvert(PayRefundDO bean);
+
+    /**
+     * 退款订单DO 转 分页退款条目VO
+     *
+     * @param bean 退款订单DO
+     * @return 分页退款条目VO
+     */
+    PayRefundPageItemRespVO pageItemConvert(PayRefundDO bean);
+
+    List<PayRefundRespVO> convertList(List<PayRefundDO> list);
+
+    PageResult<PayRefundRespVO> convertPage(PageResult<PayRefundDO> page);
+
+    List<PayRefundExcelVO> convertList02(List<PayRefundDO> list);
+
+    /**
+     * 退款订单DO 转 导出excel VO
+     *
+     * @param bean 退款订单DO
+     * @return 导出 excel VO
+     */
+    default PayRefundExcelVO excelConvert(PayRefundDO bean) {
+        if (bean == null) {
+            return null;
+        }
+
+        PayRefundExcelVO payRefundExcelVO = new PayRefundExcelVO();
+
+        payRefundExcelVO.setId(bean.getId());
+        payRefundExcelVO.setTradeNo(bean.getTradeNo());
+        payRefundExcelVO.setMerchantOrderId(bean.getMerchantOrderId());
+        payRefundExcelVO.setMerchantRefundNo(bean.getMerchantRefundNo());
+        payRefundExcelVO.setNotifyUrl(bean.getNotifyUrl());
+        payRefundExcelVO.setNotifyStatus(bean.getNotifyStatus());
+        payRefundExcelVO.setStatus(bean.getStatus());
+        payRefundExcelVO.setType(bean.getType());
+        payRefundExcelVO.setReason(bean.getReason());
+        payRefundExcelVO.setUserIp(bean.getUserIp());
+        payRefundExcelVO.setChannelOrderNo(bean.getChannelOrderNo());
+        payRefundExcelVO.setChannelRefundNo(bean.getChannelRefundNo());
+        payRefundExcelVO.setExpireTime(bean.getExpireTime());
+        payRefundExcelVO.setSuccessTime(bean.getSuccessTime());
+        payRefundExcelVO.setNotifyTime(bean.getNotifyTime());
+        payRefundExcelVO.setCreateTime(bean.getCreateTime());
+
+        BigDecimal multiple = new BigDecimal(100);
+        payRefundExcelVO.setPayAmount(BigDecimal.valueOf(bean.getPayAmount())
+                .divide(multiple, 2, RoundingMode.HALF_UP).toString());
+        payRefundExcelVO.setRefundAmount(BigDecimal.valueOf(bean.getRefundAmount())
+                .divide(multiple, 2, RoundingMode.HALF_UP).toString());
+
+        return payRefundExcelVO;
+    }
+
+}

+ 83 - 0
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/dal/mysql/app/PayAppMapper.java

@@ -0,0 +1,83 @@
+package cn.iocoder.yudao.adminserver.modules.pay.dal.mysql.app;
+
+import cn.iocoder.yudao.adminserver.modules.pay.controller.app.vo.PayAppExportReqVO;
+import cn.iocoder.yudao.adminserver.modules.pay.controller.app.vo.PayAppPageReqVO;
+import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayAppDO;
+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.QueryWrapperX;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * 支付应用信息 Mapper
+ *
+ * @author aquan
+ */
+@Mapper
+public interface PayAppMapper extends BaseMapperX<PayAppDO> {
+
+    /**
+     * 分页查询
+     *
+     * @param reqVO       支付应用信息分页查询条件
+     * @param merchantIds 商户 ID 集合
+     * @return 支付应用信息
+     */
+    default PageResult<PayAppDO> selectPage(PayAppPageReqVO reqVO, Collection<Long> merchantIds) {
+        return selectPage(reqVO, new QueryWrapperX<PayAppDO>()
+                .likeIfPresent("name", reqVO.getName())
+                .eqIfPresent("status", reqVO.getStatus())
+                .eqIfPresent("remark", reqVO.getRemark())
+                .eqIfPresent("pay_notify_url", reqVO.getPayNotifyUrl())
+                .eqIfPresent("refund_notify_url", reqVO.getRefundNotifyUrl())
+                .inIfPresent("merchant_id", merchantIds)
+                .betweenIfPresent("create_time", reqVO.getBeginCreateTime(), reqVO.getEndCreateTime())
+                .orderByDesc("id"));
+    }
+
+    /**
+     * 列表查询
+     *
+     * @param reqVO       支付应用信息 Excel 导出查询条件
+     * @param merchantIds 商户 ID 集合
+     * @return 支付应用信息
+     */
+    default List<PayAppDO> selectList(PayAppExportReqVO reqVO, Collection<Long> merchantIds) {
+        return selectList(new QueryWrapperX<PayAppDO>()
+                .likeIfPresent("name", reqVO.getName())
+                .eqIfPresent("status", reqVO.getStatus())
+                .eqIfPresent("remark", reqVO.getRemark())
+                .eqIfPresent("pay_notify_url", reqVO.getPayNotifyUrl())
+                .eqIfPresent("refund_notify_url", reqVO.getRefundNotifyUrl())
+                .inIfPresent("merchant_id", merchantIds)
+                .betweenIfPresent("create_time", reqVO.getBeginCreateTime(), reqVO.getEndCreateTime())
+                .orderByDesc("id"));
+    }
+
+    /**
+     * 根据 商户 ID 查询支付应用信息
+     *
+     * @param merchantId 商户 ID
+     * @return 支付应用信息列表
+     */
+    default List<PayAppDO> getListByMerchantId(String merchantId) {
+        return selectList(new LambdaQueryWrapper<PayAppDO>()
+                .select(PayAppDO::getId, PayAppDO::getName)
+                .eq(PayAppDO::getMerchantId, merchantId));
+    }
+
+    /**
+     * 根据商户号统计存在的支付应用数量
+     *
+     * @param merchantId 商户 ID
+     * @return 支付应用数量
+     */
+    default Long selectCount(Long merchantId) {
+        return selectCount(new LambdaQueryWrapper<PayAppDO>().eq(PayAppDO::getMerchantId, merchantId));
+    }
+
+}

+ 91 - 0
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/dal/mysql/channel/PayChannelMapper.java

@@ -0,0 +1,91 @@
+package cn.iocoder.yudao.adminserver.modules.pay.dal.mysql.channel;
+
+import cn.iocoder.yudao.adminserver.modules.pay.controller.channel.vo.PayChannelExportReqVO;
+import cn.iocoder.yudao.adminserver.modules.pay.controller.channel.vo.PayChannelPageReqVO;
+import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayChannelDO;
+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.QueryWrapperX;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * 支付渠道
+ Mapper
+ *
+ * @author 芋艿
+ */
+@Mapper
+public interface PayChannelMapper extends BaseMapperX<PayChannelDO> {
+
+    default PageResult<PayChannelDO> selectPage(PayChannelPageReqVO reqVO) {
+        return selectPage(reqVO, new QueryWrapperX<PayChannelDO>()
+                .eqIfPresent("code", reqVO.getCode())
+                .eqIfPresent("status", reqVO.getStatus())
+                .eqIfPresent("remark", reqVO.getRemark())
+                .eqIfPresent("fee_rate", reqVO.getFeeRate())
+                .eqIfPresent("merchant_id", reqVO.getMerchantId())
+                .eqIfPresent("app_id", reqVO.getAppId())
+                // .eqIfPresent("config", reqVO.getConfig())
+                .betweenIfPresent("create_time", reqVO.getBeginCreateTime(), reqVO.getEndCreateTime())
+                .orderByDesc("id")        );
+    }
+
+    default List<PayChannelDO> selectList(PayChannelExportReqVO reqVO) {
+        return selectList(new QueryWrapperX<PayChannelDO>()
+                .eqIfPresent("code", reqVO.getCode())
+                .eqIfPresent("status", reqVO.getStatus())
+                .eqIfPresent("remark", reqVO.getRemark())
+                .eqIfPresent("fee_rate", reqVO.getFeeRate())
+                .eqIfPresent("merchant_id", reqVO.getMerchantId())
+                .eqIfPresent("app_id", reqVO.getAppId())
+                // .eqIfPresent("config", reqVO.getConfig())
+                .betweenIfPresent("create_time", reqVO.getBeginCreateTime(), reqVO.getEndCreateTime())
+                .orderByDesc("id")        );
+    }
+
+    /**
+     * 根据条件获取通道数量
+     *
+     * @param merchantId 商户编号
+     * @param appid      应用编号
+     * @param code       通道编码
+     * @return 数量
+     */
+    default Integer selectCount(Long merchantId, Long appid, String code) {
+        return this.selectCount(new QueryWrapper<PayChannelDO>().lambda()
+                .eq(PayChannelDO::getMerchantId, merchantId)
+                .eq(PayChannelDO::getAppId, appid)
+                .eq(PayChannelDO::getCode, code)).intValue();
+    }
+
+    /**
+     * 根据条件获取通道
+     *
+     * @param merchantId 商户编号
+     * @param appid      应用编号
+     * @param code       通道编码
+     * @return 数量
+     */
+    default PayChannelDO selectOne(Long merchantId, Long appid, String code) {
+        return this.selectOne((new QueryWrapper<PayChannelDO>().lambda()
+                .eq(PayChannelDO::getMerchantId, merchantId)
+                .eq(PayChannelDO::getAppId, appid)
+                .eq(PayChannelDO::getCode, code)
+        ));
+    }
+
+    /**
+     * 根据支付应用ID集合获得支付渠道列表
+     *
+     * @param appIds 应用编号集合
+     * @return 支付渠道列表
+     */
+    default List<PayChannelDO> getChannelListByAppIds(Collection<Long> appIds){
+        return this.selectList(new QueryWrapper<PayChannelDO>().lambda()
+                .in(PayChannelDO::getAppId, appIds));
+    }
+}

+ 53 - 0
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/dal/mysql/merchant/PayMerchantMapper.java

@@ -0,0 +1,53 @@
+package cn.iocoder.yudao.adminserver.modules.pay.dal.mysql.merchant;
+
+import cn.iocoder.yudao.adminserver.modules.pay.controller.merchant.vo.PayMerchantExportReqVO;
+import cn.iocoder.yudao.adminserver.modules.pay.controller.merchant.vo.PayMerchantPageReqVO;
+import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayMerchantDO;
+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.QueryWrapperX;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.List;
+
+/**
+ * 支付商户信息 Mapper
+ *
+ * @author 芋艿
+ */
+@Mapper
+public interface PayMerchantMapper extends BaseMapperX<PayMerchantDO> {
+
+    default PageResult<PayMerchantDO> selectPage(PayMerchantPageReqVO reqVO) {
+        return selectPage(reqVO, new QueryWrapperX<PayMerchantDO>()
+                .likeIfPresent("no", reqVO.getNo())
+                .likeIfPresent("name", reqVO.getName())
+                .likeIfPresent("short_name", reqVO.getShortName())
+                .eqIfPresent("status", reqVO.getStatus())
+                .eqIfPresent("remark", reqVO.getRemark())
+                .betweenIfPresent("create_time", reqVO.getBeginCreateTime(), reqVO.getEndCreateTime())
+                .orderByDesc("id"));
+    }
+
+    default List<PayMerchantDO> selectList(PayMerchantExportReqVO reqVO) {
+        return selectList(new QueryWrapperX<PayMerchantDO>()
+                .likeIfPresent("no", reqVO.getNo())
+                .likeIfPresent("name", reqVO.getName())
+                .likeIfPresent("short_name", reqVO.getShortName())
+                .eqIfPresent("status", reqVO.getStatus())
+                .eqIfPresent("remark", reqVO.getRemark())
+                .betweenIfPresent("create_time", reqVO.getBeginCreateTime(), reqVO.getEndCreateTime())
+                .orderByDesc("id"));
+    }
+
+    /**
+     * 根据商户名称模糊查询商户集合
+     *
+     * @param merchantName 商户名称
+     * @return 商户集合
+     */
+    default List<PayMerchantDO> getMerchantListByName(String merchantName) {
+        return this.selectList(new QueryWrapperX<PayMerchantDO>()
+                .likeIfPresent("name", merchantName));
+    }
+}

+ 16 - 0
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/dal/mysql/order/PayOrderExtensionMapper.java

@@ -0,0 +1,16 @@
+package cn.iocoder.yudao.adminserver.modules.pay.dal.mysql.order;
+
+import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayOrderExtensionDO;
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * 支付订单 Mapper
+ *
+ * @author aquan
+ */
+@Mapper
+public interface PayOrderExtensionMapper extends BaseMapperX<PayOrderExtensionDO> {
+
+
+}

+ 79 - 0
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/dal/mysql/order/PayOrderMapper.java

@@ -0,0 +1,79 @@
+package cn.iocoder.yudao.adminserver.modules.pay.dal.mysql.order;
+
+import cn.iocoder.yudao.adminserver.modules.pay.controller.order.vo.order.PayOrderExportReqVO;
+import cn.iocoder.yudao.adminserver.modules.pay.controller.order.vo.order.PayOrderPageReqVO;
+import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayOrderDO;
+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.QueryWrapperX;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * 支付订单 Mapper 组件
+ *
+ * @author aquan
+ */
+@Mapper
+public interface PayOrderMapper extends BaseMapperX<PayOrderDO> {
+
+    default PageResult<PayOrderDO> selectPage(PayOrderPageReqVO reqVO) {
+        return selectPage(reqVO, new QueryWrapperX<PayOrderDO>()
+                .eqIfPresent("merchant_id", reqVO.getMerchantId())
+                .eqIfPresent("app_id", reqVO.getAppId())
+                .eqIfPresent("channel_id", reqVO.getChannelId())
+                .eqIfPresent("channel_code", reqVO.getChannelCode())
+                .likeIfPresent("merchant_order_id", reqVO.getMerchantOrderId())
+                .eqIfPresent("notify_status", reqVO.getNotifyStatus())
+                .eqIfPresent("status", reqVO.getStatus())
+                .eqIfPresent("refund_status", reqVO.getRefundStatus())
+                .likeIfPresent("channel_order_no", reqVO.getChannelOrderNo())
+                .betweenIfPresent("create_time", reqVO.getBeginCreateTime(), reqVO.getEndCreateTime())
+                .orderByDesc("id"));
+    }
+
+    default List<PayOrderDO> selectList(PayOrderExportReqVO reqVO) {
+        return selectList(new QueryWrapperX<PayOrderDO>()
+                .eqIfPresent("merchant_id", reqVO.getMerchantId())
+                .eqIfPresent("app_id", reqVO.getAppId())
+                .eqIfPresent("channel_id", reqVO.getChannelId())
+                .eqIfPresent("channel_code", reqVO.getChannelCode())
+                .likeIfPresent("merchant_order_id", reqVO.getMerchantOrderId())
+                .eqIfPresent("notify_status", reqVO.getNotifyStatus())
+                .eqIfPresent("status", reqVO.getStatus())
+                .eqIfPresent("refund_status", reqVO.getRefundStatus())
+                .likeIfPresent("channel_order_no", reqVO.getChannelOrderNo())
+                .betweenIfPresent("create_time", reqVO.getBeginCreateTime(), reqVO.getEndCreateTime())
+                .orderByDesc("id"));
+    }
+
+    /**
+     * 根据订单 ID 集合查询订单商品名称
+     *
+     * @param idList 订单 ID 集合
+     * @return 只包含商品名称和标题的订单集合对象
+     */
+    default List<PayOrderDO> findByIdListQueryOrderSubject(Collection<Long> idList) {
+        return selectList(new LambdaQueryWrapper<PayOrderDO>()
+                .select(PayOrderDO::getId, PayOrderDO::getSubject)
+                .in(PayOrderDO::getId, idList));
+    }
+
+    /**
+     * 查询符合的订单数量
+     *
+     * @param appId 应用编号
+     * @param status 订单状态
+     * @return 条数
+     */
+    default Long selectCount(Long appId, Integer status) {
+
+        return selectCount(new LambdaQueryWrapper<PayOrderDO>()
+                .eq(PayOrderDO::getAppId, appId)
+                .in(PayOrderDO::getStatus, status));
+    }
+
+}

+ 61 - 0
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/dal/mysql/order/PayRefundMapper.java

@@ -0,0 +1,61 @@
+package cn.iocoder.yudao.adminserver.modules.pay.dal.mysql.order;
+
+import cn.iocoder.yudao.adminserver.modules.pay.controller.order.vo.refund.vo.PayRefundExportReqVO;
+import cn.iocoder.yudao.adminserver.modules.pay.controller.order.vo.refund.vo.PayRefundPageReqVO;
+import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayRefundDO;
+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.QueryWrapperX;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.List;
+
+/**
+ * 退款订单 Mapper
+ *
+ * @author aquan
+ */
+@Mapper
+public interface PayRefundMapper extends BaseMapperX<PayRefundDO> {
+
+    default PageResult<PayRefundDO> selectPage(PayRefundPageReqVO reqVO) {
+        return selectPage(reqVO, new QueryWrapperX<PayRefundDO>()
+                .eqIfPresent("merchant_id", reqVO.getMerchantId())
+                .eqIfPresent("app_id", reqVO.getAppId())
+                .eqIfPresent("channel_code", reqVO.getChannelCode())
+                .likeIfPresent("merchant_refund_no", reqVO.getMerchantRefundNo())
+                .eqIfPresent("type", reqVO.getType())
+                .eqIfPresent("status", reqVO.getStatus())
+                .eqIfPresent("notify_status", reqVO.getNotifyStatus())
+                .betweenIfPresent("create_time", reqVO.getBeginCreateTime(), reqVO.getEndCreateTime())
+                .orderByDesc("id"));
+    }
+
+    default List<PayRefundDO> selectList(PayRefundExportReqVO reqVO) {
+        return selectList(new QueryWrapperX<PayRefundDO>()
+                .eqIfPresent("merchant_id", reqVO.getMerchantId())
+                .eqIfPresent("app_id", reqVO.getAppId())
+                .eqIfPresent("channel_code", reqVO.getChannelCode())
+                .likeIfPresent("merchant_refund_no", reqVO.getMerchantRefundNo())
+                .eqIfPresent("type", reqVO.getType())
+                .eqIfPresent("status", reqVO.getStatus())
+                .eqIfPresent("notify_status", reqVO.getNotifyStatus())
+                .betweenIfPresent("create_time", reqVO.getBeginCreateTime(), reqVO.getEndCreateTime())
+                .orderByDesc("id"));
+    }
+
+    /**
+     * 查询符合的订单数量
+     *
+     * @param appId 应用编号
+     * @param status 订单状态
+     * @return 条数
+     */
+    default Long selectCount(Long appId, Integer status) {
+
+        return selectCount(new LambdaQueryWrapper<PayRefundDO>()
+                .eq(PayRefundDO::getAppId, appId)
+                .eq(PayRefundDO::getStatus, status));
+    }
+}

+ 1 - 0
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/enums/PayErrorCodeConstants.java

@@ -0,0 +1 @@
+package cn.iocoder.yudao.adminserver.modules.pay.enums;

+ 103 - 0
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/service/app/PayAppService.java

@@ -0,0 +1,103 @@
+package cn.iocoder.yudao.adminserver.modules.pay.service.app;
+
+import cn.iocoder.yudao.adminserver.modules.pay.controller.app.vo.PayAppCreateReqVO;
+import cn.iocoder.yudao.adminserver.modules.pay.controller.app.vo.PayAppExportReqVO;
+import cn.iocoder.yudao.adminserver.modules.pay.controller.app.vo.PayAppPageReqVO;
+import cn.iocoder.yudao.adminserver.modules.pay.controller.app.vo.PayAppUpdateReqVO;
+import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayAppDO;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
+
+import javax.validation.Valid;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 支付应用信息 Service 接口
+ *
+ * @author 芋艿
+ */
+public interface PayAppService {
+
+    /**
+     * 创建支付应用信息
+     *
+     * @param createReqVO 创建信息
+     * @return 编号
+     */
+    Long createApp(@Valid PayAppCreateReqVO createReqVO);
+
+    /**
+     * 更新支付应用信息
+     *
+     * @param updateReqVO 更新信息
+     */
+    void updateApp(@Valid PayAppUpdateReqVO updateReqVO);
+
+    /**
+     * 删除支付应用信息
+     *
+     * @param id 编号
+     */
+    void deleteApp(Long id);
+
+    /**
+     * 获得支付应用信息
+     *
+     * @param id 编号
+     * @return 支付应用信息
+     */
+    PayAppDO getApp(Long id);
+
+    /**
+     * 获得支付应用信息列表
+     *
+     * @param ids 编号
+     * @return 支付应用信息列表
+     */
+    List<PayAppDO> getAppList(Collection<Long> ids);
+
+    /**
+     * 获得支付应用信息分页
+     *
+     * @param pageReqVO 分页查询
+     * @return 支付应用信息分页
+     */
+    PageResult<PayAppDO> getAppPage(PayAppPageReqVO pageReqVO);
+
+    /**
+     * 获得支付应用信息列表, 用于 Excel 导出
+     *
+     * @param exportReqVO 查询条件
+     * @return 支付应用信息列表
+     */
+    List<PayAppDO> getAppList(PayAppExportReqVO exportReqVO);
+
+    /**
+     * 修改应用信息状态
+     *
+     * @param id     应用编号
+     * @param status 状态{@link cn.iocoder.yudao.framework.common.enums.CommonStatusEnum}
+     */
+    void updateAppStatus(Long id, Integer status);
+
+    /**
+     * 根据商户 ID 获得支付应用信息列表,
+     *
+     * @param merchantId 商户 ID
+     * @return 支付应用信息列表
+     */
+    List<PayAppDO> getListByMerchantId(String merchantId);
+
+    /**
+     * 获得指定编号的商户 Map
+     *
+     * @param appIdList 应用编号集合
+     * @return 商户 Map
+     */
+    default Map<Long, PayAppDO> getAppMap(Collection<Long> appIdList) {
+        List<PayAppDO> list =  this.getAppList(appIdList);
+        return CollectionUtils.convertMap(list, PayAppDO::getId);
+    }
+}

+ 189 - 0
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/service/app/impl/PayAppServiceImpl.java

@@ -0,0 +1,189 @@
+package cn.iocoder.yudao.adminserver.modules.pay.service.app.impl;
+
+import cn.hutool.core.collection.CollectionUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.iocoder.yudao.adminserver.modules.pay.controller.app.vo.PayAppCreateReqVO;
+import cn.iocoder.yudao.adminserver.modules.pay.controller.app.vo.PayAppExportReqVO;
+import cn.iocoder.yudao.adminserver.modules.pay.controller.app.vo.PayAppPageReqVO;
+import cn.iocoder.yudao.adminserver.modules.pay.controller.app.vo.PayAppUpdateReqVO;
+import cn.iocoder.yudao.adminserver.modules.pay.convert.app.PayAppConvert;
+import cn.iocoder.yudao.adminserver.modules.pay.dal.mysql.app.PayAppMapper;
+import cn.iocoder.yudao.adminserver.modules.pay.dal.mysql.merchant.PayMerchantMapper;
+import cn.iocoder.yudao.adminserver.modules.pay.dal.mysql.order.PayOrderMapper;
+import cn.iocoder.yudao.adminserver.modules.pay.dal.mysql.order.PayRefundMapper;
+import cn.iocoder.yudao.adminserver.modules.pay.service.app.PayAppService;
+import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayAppDO;
+import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayMerchantDO;
+import cn.iocoder.yudao.coreservice.modules.pay.enums.order.PayOrderStatusEnum;
+import cn.iocoder.yudao.coreservice.modules.pay.enums.order.PayRefundStatusEnum;
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import com.google.common.annotations.VisibleForTesting;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+import javax.annotation.Resource;
+import java.util.*;
+
+import static cn.iocoder.yudao.coreservice.modules.pay.enums.PayErrorCodeCoreConstants.*;
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
+
+/**
+ * 支付应用信息 Service 实现类
+ *
+ * @author aquan
+ */
+@Service
+@Validated
+public class PayAppServiceImpl implements PayAppService {
+
+    @Resource
+    private PayAppMapper appMapper;
+
+    @Resource
+    private PayMerchantMapper merchantMapper;
+
+    @Resource
+    private PayOrderMapper orderMapper;
+
+    @Resource
+    private PayRefundMapper refundMapper;
+
+
+    @Override
+    public Long createApp(PayAppCreateReqVO createReqVO) {
+        // 插入
+        PayAppDO app = PayAppConvert.INSTANCE.convert(createReqVO);
+        appMapper.insert(app);
+        // 返回
+        return app.getId();
+    }
+
+    @Override
+    public void updateApp(PayAppUpdateReqVO updateReqVO) {
+        // 校验存在
+        this.validateAppExists(updateReqVO.getId());
+        // 更新
+        PayAppDO updateObj = PayAppConvert.INSTANCE.convert(updateReqVO);
+        appMapper.updateById(updateObj);
+    }
+
+    @Override
+    public void deleteApp(Long id) {
+        // 校验存在
+        this.validateAppExists(id);
+        this.validateOrderTransactionExist(id);
+
+        // 删除
+        appMapper.deleteById(id);
+    }
+
+    private void validateAppExists(Long id) {
+        if (appMapper.selectById(id) == null) {
+            throw exception(PAY_APP_NOT_FOUND);
+        }
+    }
+
+    @Override
+    public PayAppDO getApp(Long id) {
+        return appMapper.selectById(id);
+    }
+
+    @Override
+    public List<PayAppDO> getAppList(Collection<Long> ids) {
+        return appMapper.selectBatchIds(ids);
+    }
+
+    @Override
+    public PageResult<PayAppDO> getAppPage(PayAppPageReqVO pageReqVO) {
+        Set<Long> merchantIdList = this.getMerchantCondition(pageReqVO.getMerchantName());
+        if (StrUtil.isNotBlank(pageReqVO.getMerchantName()) && CollectionUtil.isEmpty(merchantIdList)) {
+            return new PageResult<>();
+        }
+        return appMapper.selectPage(pageReqVO, merchantIdList);
+    }
+
+    @Override
+    public List<PayAppDO> getAppList(PayAppExportReqVO exportReqVO) {
+        Set<Long> merchantIdList = this.getMerchantCondition(exportReqVO.getMerchantName());
+        if (StrUtil.isNotBlank(exportReqVO.getMerchantName()) && CollectionUtil.isEmpty(merchantIdList)) {
+            return new ArrayList<>();
+        }
+        return appMapper.selectList(exportReqVO, merchantIdList);
+    }
+
+    /**
+     * 获取商户编号集合,根据商户名称模糊查询得到所有的商户编号集合
+     *
+     * @param merchantName 商户名称
+     * @return 商户编号集合
+     */
+    private Set<Long> getMerchantCondition(String merchantName) {
+        if (StrUtil.isBlank(merchantName)) {
+            return Collections.emptySet();
+        }
+        return convertSet(merchantMapper.getMerchantListByName(merchantName), PayMerchantDO::getId);
+    }
+
+    /**
+     * 修改应用信息状态
+     *
+     * @param id     应用编号
+     * @param status 状态{@link CommonStatusEnum}
+     */
+    @Override
+    public void updateAppStatus(Long id, Integer status) {
+        // 校验商户存在
+        this.checkAppExists(id);
+        // 更新状态
+        PayAppDO app = new PayAppDO();
+        app.setId(id);
+        app.setStatus(status);
+        appMapper.updateById(app);
+    }
+
+    /**
+     * 根据商户 ID 获得支付应用信息列表,
+     *
+     * @param merchantId 商户 ID
+     * @return 支付应用信息列表
+     */
+    @Override
+    public List<PayAppDO> getListByMerchantId(String merchantId) {
+        return appMapper.getListByMerchantId(merchantId);
+    }
+
+    /**
+     * 检查商户是否存在
+     *
+     * @param id 商户编号
+     */
+    @VisibleForTesting
+    public void checkAppExists(Long id) {
+        if (id == null) {
+            return;
+        }
+        PayAppDO payApp = appMapper.selectById(id);
+        if (payApp == null) {
+            throw exception(PAY_APP_NOT_FOUND);
+        }
+    }
+
+    /**
+     * 验证是否存在交易中或者退款中等处理中状态的订单
+     *
+     * @param appId 应用 ID
+     */
+    private void validateOrderTransactionExist(Long appId) {
+        // 查看交易订单
+        if (orderMapper.selectCount(appId, PayOrderStatusEnum.WAITING.getStatus()) > 0) {
+            throw exception(PAY_APP_EXIST_TRANSACTION_ORDER_CANT_DELETE);
+        }
+        // 查看退款订单
+        if (refundMapper.selectCount(appId, PayRefundStatusEnum.CREATE.getStatus()) > 0) {
+            throw exception(PAY_APP_EXIST_TRANSACTION_ORDER_CANT_DELETE);
+        }
+    }
+
+}

+ 107 - 0
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/service/channel/PayChannelService.java

@@ -0,0 +1,107 @@
+package cn.iocoder.yudao.adminserver.modules.pay.service.channel;
+
+import cn.iocoder.yudao.adminserver.modules.pay.controller.channel.vo.*;
+import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayChannelDO;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import org.springframework.web.multipart.MultipartFile;
+
+import javax.validation.Valid;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * 支付渠道 Service 接口
+ *
+ * @author aquan
+ */
+public interface PayChannelService {
+
+    /**
+     * 创建支付渠道
+     *
+     * @param createReqVO 创建信息
+     * @return 编号
+     */
+    Long createChannel(@Valid PayChannelCreateReqVO createReqVO);
+
+    /**
+     * 更新支付渠道
+     *
+     * @param updateReqVO 更新信息
+     */
+    void updateChannel(@Valid PayChannelUpdateReqVO updateReqVO);
+
+    /**
+     * 删除支付渠道
+     *
+     * @param id 编号
+     */
+    void deleteChannel(Long id);
+
+    /**
+     * 获得支付渠道
+     *
+     * @param id 编号
+     * @return 支付渠道
+     */
+    PayChannelDO getChannel(Long id);
+
+    /**
+     * 获得支付渠道
+     * 列表
+     *
+     * @param ids 编号
+     * @return 支付渠道
+     * 列表
+     */
+    List<PayChannelDO> getChannelList(Collection<Long> ids);
+
+    /**
+     * 获得支付渠道
+     * 分页
+     *
+     * @param pageReqVO 分页查询
+     * @return 支付渠道
+     * 分页
+     */
+    PageResult<PayChannelDO> getChannelPage(PayChannelPageReqVO pageReqVO);
+
+    /**
+     * 获得支付渠道
+     * 列表, 用于 Excel 导出
+     *
+     * @param exportReqVO 查询条件
+     * @return 支付渠道列表
+     */
+    List<PayChannelDO> getChannelList(PayChannelExportReqVO exportReqVO);
+
+    /**
+     * 根据支付应用ID集合获得支付渠道列表
+     *
+     * @param appIds 应用编号集合
+     * @return 支付渠道列表
+     */
+    List<PayChannelDO> getChannelListByAppIds(Collection<Long> appIds);
+
+    /**
+     * 根据条件获取通道数量
+     *
+     * @param merchantId 商户编号
+     * @param appid      应用编号
+     * @param code       通道编码
+     * @return 数量
+     */
+    Integer getChannelCountByConditions(Long merchantId, Long appid, String code);
+
+    /**
+     * 根据条件获取通道
+     *
+     * @param merchantId 商户编号
+     * @param appid      应用编号
+     * @param code       通道编码
+     * @return 数量
+     */
+    PayChannelDO getChannelByConditions(Long merchantId, Long appid, String code);
+
+
+}

+ 161 - 0
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/service/channel/impl/PayChannelServiceImpl.java

@@ -0,0 +1,161 @@
+package cn.iocoder.yudao.adminserver.modules.pay.service.channel.impl;
+
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.json.JSONUtil;
+import cn.iocoder.yudao.adminserver.modules.pay.controller.channel.vo.PayChannelCreateReqVO;
+import cn.iocoder.yudao.adminserver.modules.pay.controller.channel.vo.PayChannelExportReqVO;
+import cn.iocoder.yudao.adminserver.modules.pay.controller.channel.vo.PayChannelPageReqVO;
+import cn.iocoder.yudao.adminserver.modules.pay.controller.channel.vo.PayChannelUpdateReqVO;
+import cn.iocoder.yudao.adminserver.modules.pay.convert.channel.PayChannelConvert;
+import cn.iocoder.yudao.adminserver.modules.pay.dal.mysql.channel.PayChannelMapper;
+import cn.iocoder.yudao.adminserver.modules.pay.service.channel.PayChannelService;
+import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayChannelDO;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.pay.core.client.PayClientConfig;
+import cn.iocoder.yudao.framework.pay.core.enums.PayChannelEnum;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+import javax.annotation.Resource;
+import javax.validation.Validator;
+import java.util.Collection;
+import java.util.List;
+
+import static cn.iocoder.yudao.coreservice.modules.pay.enums.PayErrorCodeCoreConstants.CHANNEL_EXIST_SAME_CHANNEL_ERROR;
+import static cn.iocoder.yudao.coreservice.modules.pay.enums.PayErrorCodeCoreConstants.CHANNEL_NOT_EXISTS;
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+
+/**
+ * 支付渠道 Service 实现类
+ *
+ * @author aquan
+ */
+@Service
+@Slf4j
+@Validated
+public class PayChannelServiceImpl implements PayChannelService {
+
+    @Resource
+    private PayChannelMapper channelMapper;
+
+    @Resource
+    private Validator validator;
+
+    @Override
+    public Long createChannel(PayChannelCreateReqVO reqVO) {
+        // 断言是否有重复的
+        PayChannelDO channelDO = this.getChannelByConditions(reqVO.getMerchantId(), reqVO.getAppId(), reqVO.getCode());
+        if (ObjectUtil.isNotNull(channelDO)) {
+            throw exception(CHANNEL_EXIST_SAME_CHANNEL_ERROR);
+        }
+
+        // 新增渠道
+        PayChannelDO channel = PayChannelConvert.INSTANCE.convert(reqVO);
+        settingConfigAndCheckParam(channel, reqVO.getConfig());
+        channelMapper.insert(channel);
+        return channel.getId();
+    }
+
+    @Override
+    public void updateChannel(PayChannelUpdateReqVO updateReqVO) {
+        // 校验存在
+        this.validateChannelExists(updateReqVO.getId());
+        // 更新
+        PayChannelDO channel = PayChannelConvert.INSTANCE.convert(updateReqVO);
+        settingConfigAndCheckParam(channel, updateReqVO.getConfig());
+        channelMapper.updateById(channel);
+    }
+
+    @Override
+    public void deleteChannel(Long id) {
+        // 校验存在
+        this.validateChannelExists(id);
+        // 删除
+        channelMapper.deleteById(id);
+    }
+
+    private void validateChannelExists(Long id) {
+        if (channelMapper.selectById(id) == null) {
+            throw exception(CHANNEL_NOT_EXISTS);
+        }
+    }
+
+    @Override
+    public PayChannelDO getChannel(Long id) {
+        return channelMapper.selectById(id);
+    }
+
+    @Override
+    public List<PayChannelDO> getChannelList(Collection<Long> ids) {
+        return channelMapper.selectBatchIds(ids);
+    }
+
+    @Override
+    public PageResult<PayChannelDO> getChannelPage(PayChannelPageReqVO pageReqVO) {
+        return channelMapper.selectPage(pageReqVO);
+    }
+
+    @Override
+    public List<PayChannelDO> getChannelList(PayChannelExportReqVO exportReqVO) {
+        return channelMapper.selectList(exportReqVO);
+    }
+
+    /**
+     * 根据支付应用ID集合获得支付渠道列表
+     *
+     * @param appIds 应用编号集合
+     * @return 支付渠道列表
+     */
+    @Override
+    public List<PayChannelDO> getChannelListByAppIds(Collection<Long> appIds) {
+        return channelMapper.getChannelListByAppIds(appIds);
+    }
+
+
+    /**
+     * 根据条件获取通道数量
+     *
+     * @param merchantId 商户编号
+     * @param appid      应用编号
+     * @param code       通道编码
+     * @return 数量
+     */
+    @Override
+    public Integer getChannelCountByConditions(Long merchantId, Long appid, String code) {
+        return this.channelMapper.selectCount(merchantId, appid, code);
+    }
+
+    /**
+     * 根据条件获取通道
+     *
+     * @param merchantId 商户编号
+     * @param appid      应用编号
+     * @param code       通道编码
+     * @return 数量
+     */
+    @Override
+    public PayChannelDO getChannelByConditions(Long merchantId, Long appid, String code) {
+        return this.channelMapper.selectOne(merchantId, appid, code);
+    }
+
+    /**
+     * 设置渠道配置以及参数校验
+     *
+     * @param channel   渠道
+     * @param configStr 配置
+     */
+    private void settingConfigAndCheckParam(PayChannelDO channel, String configStr) {
+        // 得到这个渠道是微信的还是支付宝的
+        Class<? extends PayClientConfig> payClass = PayChannelEnum.getByCode(channel.getCode()).getConfigClass();
+        if (ObjectUtil.isNull(payClass)) {
+            throw exception(CHANNEL_NOT_EXISTS);
+        }
+        PayClientConfig config = JSONUtil.toBean(configStr, payClass);
+
+        // 验证参数
+        config.validate(validator);
+        channel.setConfig(config);
+    }
+
+}

+ 104 - 0
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/service/merchant/PayMerchantService.java

@@ -0,0 +1,104 @@
+package cn.iocoder.yudao.adminserver.modules.pay.service.merchant;
+
+import cn.iocoder.yudao.adminserver.modules.pay.controller.merchant.vo.PayMerchantCreateReqVO;
+import cn.iocoder.yudao.adminserver.modules.pay.controller.merchant.vo.PayMerchantExportReqVO;
+import cn.iocoder.yudao.adminserver.modules.pay.controller.merchant.vo.PayMerchantPageReqVO;
+import cn.iocoder.yudao.adminserver.modules.pay.controller.merchant.vo.PayMerchantUpdateReqVO;
+import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayMerchantDO;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
+
+import javax.validation.Valid;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 支付商户信息 Service 接口
+ *
+ * @author aquan
+ */
+public interface PayMerchantService {
+
+    /**
+     * 创建支付商户信息
+     *
+     * @param createReqVO 创建信息
+     * @return 编号
+     */
+    Long createMerchant(@Valid PayMerchantCreateReqVO createReqVO);
+
+    /**
+     * 更新支付商户信息
+     *
+     * @param updateReqVO 更新信息
+     */
+    void updateMerchant(@Valid PayMerchantUpdateReqVO updateReqVO);
+
+    /**
+     * 删除支付商户信息
+     *
+     * @param id 编号
+     */
+    void deleteMerchant(Long id);
+
+    /**
+     * 获得支付商户信息
+     *
+     * @param id 编号
+     * @return 支付商户信息
+     */
+    PayMerchantDO getMerchant(Long id);
+
+    /**
+     * 获得支付商户信息列表
+     *
+     * @param ids 编号
+     * @return 支付商户信息列表
+     */
+    List<PayMerchantDO> getMerchantList(Collection<Long> ids);
+
+    /**
+     * 获得支付商户信息分页
+     *
+     * @param pageReqVO 分页查询
+     * @return 支付商户信息分页
+     */
+    PageResult<PayMerchantDO> getMerchantPage(PayMerchantPageReqVO pageReqVO);
+
+    /**
+     * 获得支付商户信息列表, 用于 Excel 导出
+     *
+     * @param exportReqVO 查询条件
+     * @return 支付商户信息列表
+     */
+    List<PayMerchantDO> getMerchantList(PayMerchantExportReqVO exportReqVO);
+
+    /**
+     * 修改商户状态
+     *
+     * @param id     商户编号
+     * @param status 状态
+     */
+    void updateMerchantStatus(Long id, Integer status);
+
+    /**
+     * 根据商户名称模糊查询商户集合
+     *
+     * @param merchantName 商户名称
+     * @return 商户集合
+     */
+    List<PayMerchantDO> getMerchantListByName(String merchantName);
+
+    /**
+     * 获得指定编号的商户 Map
+     *
+     * @param merchantIds 商户编号数组
+     * @return 商户 Map
+     */
+    default Map<Long, PayMerchantDO> getMerchantMap(Collection<Long> merchantIds) {
+        List<PayMerchantDO> list =  this.getMerchantList(merchantIds);
+        return CollectionUtils.convertMap(list, PayMerchantDO::getId);
+    }
+
+}

+ 150 - 0
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/service/merchant/impl/PayMerchantServiceImpl.java

@@ -0,0 +1,150 @@
+package cn.iocoder.yudao.adminserver.modules.pay.service.merchant.impl;
+
+import cn.hutool.core.date.DateUtil;
+import cn.hutool.core.util.ObjectUtil;
+import cn.iocoder.yudao.adminserver.modules.pay.controller.merchant.vo.PayMerchantCreateReqVO;
+import cn.iocoder.yudao.adminserver.modules.pay.controller.merchant.vo.PayMerchantExportReqVO;
+import cn.iocoder.yudao.adminserver.modules.pay.controller.merchant.vo.PayMerchantPageReqVO;
+import cn.iocoder.yudao.adminserver.modules.pay.controller.merchant.vo.PayMerchantUpdateReqVO;
+import cn.iocoder.yudao.adminserver.modules.pay.convert.merchant.PayMerchantConvert;
+import cn.iocoder.yudao.adminserver.modules.pay.dal.mysql.app.PayAppMapper;
+import cn.iocoder.yudao.adminserver.modules.pay.dal.mysql.merchant.PayMerchantMapper;
+import cn.iocoder.yudao.adminserver.modules.pay.service.merchant.PayMerchantService;
+import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayMerchantDO;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import com.google.common.annotations.VisibleForTesting;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+import javax.annotation.Resource;
+import java.time.LocalDateTime;
+import java.util.Collection;
+import java.util.List;
+
+import static cn.iocoder.yudao.coreservice.modules.pay.enums.PayErrorCodeCoreConstants.PAY_MERCHANT_EXIST_APP_CANT_DELETE;
+import static cn.iocoder.yudao.coreservice.modules.pay.enums.PayErrorCodeCoreConstants.PAY_MERCHANT_NOT_EXISTS;
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+
+/**
+ * 支付商户信息 Service 实现类
+ *
+ * @author aquan
+ */
+@Service
+@Validated
+public class PayMerchantServiceImpl implements PayMerchantService {
+
+    @Resource
+    private PayMerchantMapper merchantMapper;
+
+    @Resource
+    private PayAppMapper appMapper;
+
+    @Override
+    public Long createMerchant(PayMerchantCreateReqVO createReqVO) {
+        // 插入
+        PayMerchantDO merchant = PayMerchantConvert.INSTANCE.convert(createReqVO);
+        merchant.setNo(this.generateMerchantNo());
+        merchantMapper.insert(merchant);
+        // 返回
+        return merchant.getId();
+    }
+
+    @Override
+    public void updateMerchant(PayMerchantUpdateReqVO updateReqVO) {
+        // 校验存在
+        this.validateMerchantExists(updateReqVO.getId());
+        // 更新
+        PayMerchantDO updateObj = PayMerchantConvert.INSTANCE.convert(updateReqVO);
+        merchantMapper.updateById(updateObj);
+    }
+
+    @Override
+    public void deleteMerchant(Long id) {
+        // 校验
+        this.validateMerchantExists(id);
+        this.validateAppExists(id);
+        // 删除
+        merchantMapper.deleteById(id);
+    }
+
+    @Override
+    public PayMerchantDO getMerchant(Long id) {
+        return merchantMapper.selectById(id);
+    }
+
+    @Override
+    public List<PayMerchantDO> getMerchantList(Collection<Long> ids) {
+        return merchantMapper.selectBatchIds(ids);
+    }
+
+    @Override
+    public PageResult<PayMerchantDO> getMerchantPage(PayMerchantPageReqVO pageReqVO) {
+        return merchantMapper.selectPage(pageReqVO);
+    }
+
+    @Override
+    public List<PayMerchantDO> getMerchantList(PayMerchantExportReqVO exportReqVO) {
+        return merchantMapper.selectList(exportReqVO);
+    }
+
+    @Override
+    public void updateMerchantStatus(Long id, Integer status) {
+        // 校验商户存在
+        this.checkMerchantExists(id);
+        // 更新状态
+        PayMerchantDO merchant = new PayMerchantDO();
+        merchant.setId(id);
+        merchant.setStatus(status);
+        merchantMapper.updateById(merchant);
+    }
+
+    @Override
+    public List<PayMerchantDO> getMerchantListByName(String merchantName) {
+        return this.merchantMapper.getMerchantListByName(merchantName);
+    }
+
+    @VisibleForTesting
+    public void checkMerchantExists(Long id) {
+        if (id == null) {
+            return;
+        }
+        PayMerchantDO merchant = merchantMapper.selectById(id);
+        if (merchant == null) {
+            throw exception(PAY_MERCHANT_NOT_EXISTS);
+        }
+    }
+
+    /**
+     * 校验商户是否存在
+     *
+     * @param id 商户 ID
+     */
+    private void validateMerchantExists(Long id) {
+        if (ObjectUtil.isNull(merchantMapper.selectById(id))) {
+            throw exception(PAY_MERCHANT_NOT_EXISTS);
+        }
+    }
+
+    /**
+     * 校验商户是否还存在支付应用
+     *
+     * @param id 商户ID
+     */
+    private void validateAppExists(Long id) {
+        if (appMapper.selectCount(id) > 0) {
+            throw exception(PAY_MERCHANT_EXIST_APP_CANT_DELETE);
+        }
+    }
+
+    // TODO @芋艿:后续增加下合适的算法
+    /**
+     * 根据年月日时分秒毫秒生成商户号
+     *
+     * @return 商户号
+     */
+    private String generateMerchantNo() {
+        return "M" + DateUtil.format(LocalDateTime.now(), "yyyyMMddHHmmssSSS");
+    }
+
+}

+ 48 - 0
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/service/order/PayOrderExtensionService.java

@@ -0,0 +1,48 @@
+package cn.iocoder.yudao.adminserver.modules.pay.service.order;
+
+import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayOrderExtensionDO;
+import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 支付订单 Service 接口
+ *
+ * @author aquan
+ */
+public interface PayOrderExtensionService {
+
+    /**
+     * 获得支付订单
+     *
+     * @param id 编号
+     * @return 支付订单
+     */
+    PayOrderExtensionDO getOrderExtension(Long id);
+
+    /**
+     * 获得支付订单
+     * 列表
+     *
+     * @param ids 编号
+     * @return 支付订单
+     * 列表
+     */
+    List<PayOrderExtensionDO> getOrderExtensionList(Collection<Long> ids);
+
+
+    /**
+     * 根据订单成功的 扩展订单ID 查询所有的扩展订单转 成 map 返回
+     *
+     * @param successExtensionIdList 订单 ID 集合
+     * @return 订单扩展 map 集合
+     */
+    default Map<Long, PayOrderExtensionDO> getOrderExtensionMap(Collection<Long> successExtensionIdList) {
+        List<PayOrderExtensionDO> list = this.getOrderExtensionList(successExtensionIdList);
+        return CollectionUtils.convertMap(list, PayOrderExtensionDO::getId);
+    }
+
+
+}

+ 67 - 0
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/service/order/PayOrderService.java

@@ -0,0 +1,67 @@
+package cn.iocoder.yudao.adminserver.modules.pay.service.order;
+
+import cn.iocoder.yudao.adminserver.modules.pay.controller.order.vo.order.PayOrderExportReqVO;
+import cn.iocoder.yudao.adminserver.modules.pay.controller.order.vo.order.PayOrderPageReqVO;
+import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayOrderDO;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 支付订单 Service 接口
+ *
+ * @author aquan
+ */
+public interface PayOrderService {
+
+    /**
+     * 获得支付订单
+     *
+     * @param id 编号
+     * @return 支付订单
+     */
+    PayOrderDO getOrder(Long id);
+
+    /**
+     * 获得支付订单
+     * 分页
+     *
+     * @param pageReqVO 分页查询
+     * @return 支付订单
+     * 分页
+     */
+    PageResult<PayOrderDO> getOrderPage(PayOrderPageReqVO pageReqVO);
+
+    /**
+     * 获得支付订单
+     * 列表, 用于 Excel 导出
+     *
+     * @param exportReqVO 查询条件
+     * @return 支付订单
+     * 列表
+     */
+    List<PayOrderDO> getOrderList(PayOrderExportReqVO exportReqVO);
+
+    /**
+     * 根据 ID 集合获取只包含商品名称的订单集合
+     *
+     * @param idList 订单 ID 集合
+     * @return 只包含商品名称的订单集合
+     */
+    List<PayOrderDO> getOrderSubjectList(Collection<Long> idList);
+
+    /**
+     * 根据订单 ID 集合获取订单商品名称Map集合
+     *
+     * @param idList 订单 ID 集合
+     * @return 订单商品 map 集合
+     */
+    default Map<Long, PayOrderDO> getOrderSubjectMap(Collection<Long> idList) {
+        List<PayOrderDO> list = getOrderSubjectList(idList);
+        return CollectionUtils.convertMap(list, PayOrderDO::getId);
+    }
+
+}

+ 41 - 0
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/service/order/PayRefundService.java

@@ -0,0 +1,41 @@
+package cn.iocoder.yudao.adminserver.modules.pay.service.order;
+
+import cn.iocoder.yudao.adminserver.modules.pay.controller.order.vo.refund.vo.PayRefundExportReqVO;
+import cn.iocoder.yudao.adminserver.modules.pay.controller.order.vo.refund.vo.PayRefundPageReqVO;
+import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayRefundDO;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+
+import java.util.List;
+
+/**
+ * 退款订单 Service 接口
+ *
+ * @author aquan
+ */
+public interface PayRefundService {
+
+    /**
+     * 获得退款订单
+     *
+     * @param id 编号
+     * @return 退款订单
+     */
+    PayRefundDO getRefund(Long id);
+
+    /**
+     * 获得退款订单分页
+     *
+     * @param pageReqVO 分页查询
+     * @return 退款订单分页
+     */
+    PageResult<PayRefundDO> getRefundPage(PayRefundPageReqVO pageReqVO);
+
+    /**
+     * 获得退款订单列表, 用于 Excel 导出
+     *
+     * @param exportReqVO 查询条件
+     * @return 退款订单列表
+     */
+    List<PayRefundDO> getRefundList(PayRefundExportReqVO exportReqVO);
+
+}

+ 35 - 0
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/service/order/impl/PayOrderExtensionServiceImpl.java

@@ -0,0 +1,35 @@
+package cn.iocoder.yudao.adminserver.modules.pay.service.order.impl;
+
+import cn.iocoder.yudao.adminserver.modules.pay.dal.mysql.order.PayOrderExtensionMapper;
+import cn.iocoder.yudao.adminserver.modules.pay.service.order.PayOrderExtensionService;
+import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayOrderExtensionDO;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+import javax.annotation.Resource;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * 支付订单 Service 实现类
+ *
+ * @author aquan
+ */
+@Service
+@Validated
+public class PayOrderExtensionServiceImpl implements PayOrderExtensionService {
+
+    @Resource
+    private PayOrderExtensionMapper orderExtensionMapper;
+
+    @Override
+    public PayOrderExtensionDO getOrderExtension(Long id) {
+        return orderExtensionMapper.selectById(id);
+    }
+
+    @Override
+    public List<PayOrderExtensionDO> getOrderExtensionList(Collection<Long> ids) {
+        return orderExtensionMapper.selectBatchIds(ids);
+    }
+
+}

+ 54 - 0
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/service/order/impl/PayOrderServiceImpl.java

@@ -0,0 +1,54 @@
+package cn.iocoder.yudao.adminserver.modules.pay.service.order.impl;
+
+import cn.iocoder.yudao.adminserver.modules.pay.controller.order.vo.order.PayOrderExportReqVO;
+import cn.iocoder.yudao.adminserver.modules.pay.controller.order.vo.order.PayOrderPageReqVO;
+import cn.iocoder.yudao.adminserver.modules.pay.dal.mysql.order.PayOrderMapper;
+import cn.iocoder.yudao.adminserver.modules.pay.service.order.PayOrderService;
+import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayOrderDO;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+import javax.annotation.Resource;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * 支付订单 Service 实现类
+ *
+ * @author aquan
+ */
+@Service
+@Validated
+public class PayOrderServiceImpl implements PayOrderService {
+
+    @Resource
+    private PayOrderMapper orderMapper;
+
+    @Override
+    public PayOrderDO getOrder(Long id) {
+        return orderMapper.selectById(id);
+    }
+
+    @Override
+    public PageResult<PayOrderDO> getOrderPage(PayOrderPageReqVO pageReqVO) {
+        return orderMapper.selectPage(pageReqVO);
+    }
+
+    @Override
+    public List<PayOrderDO> getOrderList(PayOrderExportReqVO exportReqVO) {
+        return orderMapper.selectList(exportReqVO);
+    }
+
+    /**
+     * 根据 ID 集合获取只包含商品名称的订单集合
+     *
+     * @param idList 订单 ID 集合
+     * @return 只包含商品名称的订单集合
+     */
+    @Override
+    public List<PayOrderDO> getOrderSubjectList(Collection<Long> idList) {
+        return orderMapper.findByIdListQueryOrderSubject(idList);
+    }
+
+}

+ 42 - 0
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/service/order/impl/PayRefundServiceImpl.java

@@ -0,0 +1,42 @@
+package cn.iocoder.yudao.adminserver.modules.pay.service.order.impl;
+
+import cn.iocoder.yudao.adminserver.modules.pay.controller.order.vo.refund.vo.PayRefundExportReqVO;
+import cn.iocoder.yudao.adminserver.modules.pay.controller.order.vo.refund.vo.PayRefundPageReqVO;
+import cn.iocoder.yudao.adminserver.modules.pay.dal.mysql.order.PayRefundMapper;
+import cn.iocoder.yudao.adminserver.modules.pay.service.order.PayRefundService;
+import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayRefundDO;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+import javax.annotation.Resource;
+import java.util.List;
+
+/**
+ * 退款订单 Service 实现类
+ *
+ * @author aquan
+ */
+@Service
+@Validated
+public class PayRefundServiceImpl implements PayRefundService {
+
+    @Resource
+    private PayRefundMapper refundMapper;
+
+    @Override
+    public PayRefundDO getRefund(Long id) {
+        return refundMapper.selectById(id);
+    }
+
+    @Override
+    public PageResult<PayRefundDO> getRefundPage(PayRefundPageReqVO pageReqVO) {
+        return refundMapper.selectPage(pageReqVO);
+    }
+
+    @Override
+    public List<PayRefundDO> getRefundList(PayRefundExportReqVO exportReqVO) {
+        return refundMapper.selectList(exportReqVO);
+    }
+
+}

+ 25 - 0
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/system/enums/SysDictTypeConstants.java

@@ -22,6 +22,31 @@ public interface SysDictTypeConstants {
     String SMS_SEND_STATUS = "sys_sms_send_status"; // 短信发送状态
     String SMS_RECEIVE_STATUS = "sys_sms_receive_status"; // 短信接收状态
 
+    /**
+     * 支付-订单-订单状态
+     */
+    String PAY_ORDER_STATUS = "pay_order_status";
+
+    /**
+     * 支付-订单-订单回调商户状态
+     */
+    String PAY_ORDER_NOTIFY_STATUS = "pay_order_notify_status";
+
+    /**
+     * 支付-订单-订单退款状态
+     */
+    String PAY_ORDER_REFUND_STATUS = "pay_order_refund_status";
+
+    /**
+     * 支付-退款订单-退款状态
+     */
+    String PAY_REFUND_ORDER_STATUS = "pay_refund_order_status";
+
+    /**
+     * 支付-退款订单-退款类别
+     */
+    String PAY_REFUND_ORDER_TYPE = "pay_refund_order_type";
+
     String BPM_TASK_ASSIGN_RULE_TYPE = "bpm_task_assign_rule_type"; // 任务分配规则类型
     String BPM_TASK_ASSIGN_SCRIPT = "bpm_task_assign_script"; // 任务分配自定义脚本
 

+ 1 - 0
yudao-admin-server/src/main/resources/application-local.yaml

@@ -183,6 +183,7 @@ yudao:
       - ${management.endpoints.web.base-path}/** # 不处理 Actuator 的请求
   pay:
     pay-notify-url: http://niubi.natapp1.cc/api/pay/order/notify
+    pay-return-url: http://niubi.natapp1.cc/api/pay/order/return
     refund-notify-url: http://niubi.natapp1.cc/api/pay/refund/notify
   demo: false # 关闭演示模式
 

+ 1 - 1
yudao-admin-server/src/test/java/cn/iocoder/yudao/adminserver/modules/bpm/framework/activiti/core/behavior/BpmUserTaskActivitiBehaviorTest.java

@@ -31,7 +31,7 @@ import static org.junit.jupiter.api.Assertions.*;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.when;
 
-class BpmUserTaskActivitiBehaviorTest extends BaseMockitoUnitTest {
+public class BpmUserTaskActivitiBehaviorTest extends BaseMockitoUnitTest {
 
     @InjectMocks
     private BpmUserTaskActivitiBehavior behavior;

+ 245 - 0
yudao-admin-server/src/test/java/cn/iocoder/yudao/adminserver/modules/pay/service/app/PayAppServiceTest.java

@@ -0,0 +1,245 @@
+package cn.iocoder.yudao.adminserver.modules.pay.service.app;
+
+import cn.hutool.core.util.RandomUtil;
+import cn.iocoder.yudao.adminserver.BaseDbUnitTest;
+import cn.iocoder.yudao.adminserver.modules.pay.controller.app.vo.PayAppCreateReqVO;
+import cn.iocoder.yudao.adminserver.modules.pay.controller.app.vo.PayAppExportReqVO;
+import cn.iocoder.yudao.adminserver.modules.pay.controller.app.vo.PayAppPageReqVO;
+import cn.iocoder.yudao.adminserver.modules.pay.controller.app.vo.PayAppUpdateReqVO;
+import cn.iocoder.yudao.adminserver.modules.pay.dal.mysql.app.PayAppMapper;
+import cn.iocoder.yudao.adminserver.modules.pay.dal.mysql.merchant.PayMerchantMapper;
+import cn.iocoder.yudao.adminserver.modules.pay.service.app.impl.PayAppServiceImpl;
+import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayAppDO;
+import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayMerchantDO;
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mockito;
+import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.context.annotation.Import;
+
+import javax.annotation.Resource;
+import java.util.Collections;
+import java.util.List;
+
+import static cn.iocoder.yudao.coreservice.modules.pay.enums.PayErrorCodeCoreConstants.PAY_APP_NOT_FOUND;
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.buildTime;
+import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId;
+import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
+import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException;
+import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId;
+import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * {@link PayAppServiceImpl} 的单元测试类
+ *
+ * @author 芋艿
+ */
+@Import(PayAppServiceImpl.class)
+public class PayAppServiceTest extends BaseDbUnitTest {
+
+    @Resource
+    private PayAppServiceImpl appService;
+
+    @Resource
+    private PayAppMapper appMapper;
+
+    @MockBean(name = "payMerchantMapper")
+    private PayMerchantMapper payMerchantMapper;
+
+    @Test
+    public void testCreateApp_success() {
+        // 准备参数
+        PayAppCreateReqVO reqVO = randomPojo(PayAppCreateReqVO.class, o ->
+                o.setStatus((RandomUtil.randomEle(CommonStatusEnum.values()).getStatus())));
+
+        // 调用
+        Long appId = appService.createApp(reqVO);
+        // 断言
+        assertNotNull(appId);
+        // 校验记录的属性是否正确
+        PayAppDO app = appMapper.selectById(appId);
+        assertPojoEquals(reqVO, app);
+    }
+
+    @Test
+    public void testUpdateApp_success() {
+        // mock 数据
+        PayAppDO dbApp = randomPojo(PayAppDO.class, o ->
+                o.setStatus(CommonStatusEnum.DISABLE.getStatus()));
+        appMapper.insert(dbApp);// @Sql: 先插入出一条存在的数据
+        // 准备参数
+        PayAppUpdateReqVO reqVO = randomPojo(PayAppUpdateReqVO.class, o -> {
+            o.setStatus(CommonStatusEnum.ENABLE.getStatus());
+            o.setId(dbApp.getId()); // 设置更新的 ID
+        });
+
+        // 调用
+        appService.updateApp(reqVO);
+        // 校验是否更新正确
+        PayAppDO app = appMapper.selectById(reqVO.getId()); // 获取最新的
+        assertPojoEquals(reqVO, app);
+    }
+
+    @Test
+    public void testUpdateApp_notExists() {
+        // 准备参数
+        PayAppUpdateReqVO reqVO = randomPojo(PayAppUpdateReqVO.class, o ->
+                o.setStatus((RandomUtil.randomEle(CommonStatusEnum.values()).getStatus())));
+        // 调用, 并断言异常
+        assertServiceException(() -> appService.updateApp(reqVO), PAY_APP_NOT_FOUND);
+    }
+
+    @Test
+    public void testDeleteApp_success() {
+        // mock 数据
+        PayAppDO dbApp = randomPojo(PayAppDO.class, o ->
+                o.setStatus((RandomUtil.randomEle(CommonStatusEnum.values()).getStatus())));
+        appMapper.insert(dbApp);// @Sql: 先插入出一条存在的数据
+        // 准备参数
+        Long id = dbApp.getId();
+
+        // 调用
+        appService.deleteApp(id);
+        // 校验数据不存在了
+        assertNull(appMapper.selectById(id));
+    }
+
+    @Test
+    public void testDeleteApp_notExists() {
+        // 准备参数
+        Long id = randomLongId();
+
+        // 调用, 并断言异常
+        assertServiceException(() -> appService.deleteApp(id), PAY_APP_NOT_FOUND);
+    }
+
+    @Test
+    public void testGetAppPage() {
+        Long merchantId = 1L;
+        Long mismatchMerchantId = 2L;
+
+        // mock 数据
+        PayAppDO dbApp = randomPojo(PayAppDO.class, o -> { // 等会查询到
+            o.setName("灿灿姐的杂货铺");
+            o.setStatus(CommonStatusEnum.ENABLE.getStatus());
+            o.setRemark("敏敏姐的小卖铺");
+            o.setPayNotifyUrl("https://www.hc.com");
+            o.setRefundNotifyUrl("https://www.xm.com");
+            o.setMerchantId(merchantId);
+            o.setCreateTime(buildTime(2021,11,20));
+        });
+
+        // mock 数据
+        PayMerchantDO dbMerchant = randomPojo(PayMerchantDO.class, o -> { // 等会查询到
+            o.setId(merchantId);
+            o.setNo("M1008611");
+            o.setName("灿哥的杂货铺");
+            o.setShortName("灿灿子");
+            o.setStatus(CommonStatusEnum.ENABLE.getStatus());
+            o.setRemark("灿哥的杂货铺");
+            o.setCreateTime(buildTime(2021,11,3));
+        });
+
+        Mockito.when(payMerchantMapper.getMerchantListByName(dbMerchant.getName()))
+                .thenReturn(Collections.singletonList(dbMerchant));
+
+        appMapper.insert(dbApp);
+        // 测试 name 不匹配
+        appMapper.insert(cloneIgnoreId(dbApp, o -> o.setName("敏敏姐的杂货铺")));
+        // 测试 status 不匹配
+        appMapper.insert(cloneIgnoreId(dbApp, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus())));
+        // 测试 remark 不匹配
+        appMapper.insert(cloneIgnoreId(dbApp, o -> o.setRemark("灿灿姐的小卖部")));
+        // 测试 payNotifyUrl 不匹配
+        appMapper.insert(cloneIgnoreId(dbApp, o -> o.setPayNotifyUrl("xm.com")));
+        // 测试 refundNotifyUrl 不匹配
+        appMapper.insert(cloneIgnoreId(dbApp, o -> o.setRefundNotifyUrl("hc.com")));
+        // 测试 merchantId 不匹配
+        appMapper.insert(cloneIgnoreId(dbApp, o -> o.setMerchantId(mismatchMerchantId)));
+        // 测试 createTime 不匹配
+        appMapper.insert(cloneIgnoreId(dbApp, o -> o.setCreateTime(buildTime(2021,12,21))));
+        // 准备参数
+        PayAppPageReqVO reqVO = new PayAppPageReqVO();
+        reqVO.setName("灿灿姐的杂货铺");
+        reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus());
+        reqVO.setRemark("敏敏姐的小卖铺");
+        reqVO.setPayNotifyUrl("https://www.hc.com");
+        reqVO.setRefundNotifyUrl("https://www.xm.com");
+        reqVO.setMerchantName(dbMerchant.getName());
+        reqVO.setBeginCreateTime(buildTime(2021,11,19));
+        reqVO.setEndCreateTime(buildTime(2021,11,21));
+
+        // 调用
+        PageResult<PayAppDO> pageResult = appService.getAppPage(reqVO);
+        // 断言
+        assertEquals(1, pageResult.getTotal());
+        assertEquals(1, pageResult.getList().size());
+        assertPojoEquals(dbApp, pageResult.getList().get(0));
+    }
+
+    @Test // TODO 请修改 null 为需要的值
+    public void testGetAppList() {
+        Long merchantId = 1L;
+        Long mismatchMerchantId = 2L;
+
+        // mock 数据
+        PayAppDO dbApp = randomPojo(PayAppDO.class, o -> { // 等会查询到
+            o.setName("灿灿姐的杂货铺");
+            o.setStatus(CommonStatusEnum.ENABLE.getStatus());
+            o.setRemark("敏敏姐的小卖铺");
+            o.setPayNotifyUrl("https://www.hc.com");
+            o.setRefundNotifyUrl("https://www.xm.com");
+            o.setMerchantId(merchantId);
+            o.setCreateTime(buildTime(2021,11,20));
+        });
+
+        // mock 数据
+        PayMerchantDO dbMerchant = randomPojo(PayMerchantDO.class, o -> { // 等会查询到
+            o.setId(merchantId);
+            o.setNo("M1008611");
+            o.setName("灿哥的杂货铺");
+            o.setShortName("灿灿子");
+            o.setStatus(CommonStatusEnum.ENABLE.getStatus());
+            o.setRemark("灿哥的杂货铺");
+            o.setCreateTime(buildTime(2021,11,3));
+        });
+
+        Mockito.when(payMerchantMapper.getMerchantListByName(dbMerchant.getName()))
+                .thenReturn(Collections.singletonList(dbMerchant));
+
+        appMapper.insert(dbApp);
+        // 测试 name 不匹配
+        appMapper.insert(cloneIgnoreId(dbApp, o -> o.setName("敏敏姐的杂货铺")));
+        // 测试 status 不匹配
+        appMapper.insert(cloneIgnoreId(dbApp, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus())));
+        // 测试 remark 不匹配
+        appMapper.insert(cloneIgnoreId(dbApp, o -> o.setRemark("灿灿姐的小卖部")));
+        // 测试 payNotifyUrl 不匹配
+        appMapper.insert(cloneIgnoreId(dbApp, o -> o.setPayNotifyUrl("xm.com")));
+        // 测试 refundNotifyUrl 不匹配
+        appMapper.insert(cloneIgnoreId(dbApp, o -> o.setRefundNotifyUrl("hc.com")));
+        // 测试 merchantId 不匹配
+        appMapper.insert(cloneIgnoreId(dbApp, o -> o.setMerchantId(mismatchMerchantId)));
+        // 测试 createTime 不匹配
+        appMapper.insert(cloneIgnoreId(dbApp, o -> o.setCreateTime(buildTime(2021,12,21))));
+        // 准备参数
+        PayAppExportReqVO reqVO = new PayAppExportReqVO();
+        reqVO.setName("灿灿姐的杂货铺");
+        reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus());
+        reqVO.setRemark("敏敏姐的小卖铺");
+        reqVO.setPayNotifyUrl("https://www.hc.com");
+        reqVO.setRefundNotifyUrl("https://www.xm.com");
+        reqVO.setMerchantName(dbMerchant.getName());
+        reqVO.setBeginCreateTime(buildTime(2021,11,19));
+        reqVO.setEndCreateTime(buildTime(2021,11,21));
+
+        // 调用
+        List<PayAppDO> list = appService.getAppList(reqVO);
+        // 断言
+        assertEquals(1, list.size());
+        assertPojoEquals(dbApp, list.get(0));
+    }
+
+}

+ 20 - 0
yudao-admin-server/src/test/java/cn/iocoder/yudao/adminserver/modules/pay/service/channel/PayChannelConfig.java

@@ -0,0 +1,20 @@
+package cn.iocoder.yudao.adminserver.modules.pay.service.channel;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import javax.validation.Validation;
+import javax.validation.Validator;
+
+/**
+ * 用于初始化 validator Bean 对象
+ * @author aquan
+ */
+@Configuration
+public class PayChannelConfig {
+
+    @Bean
+    public Validator validator(){
+        return Validation.buildDefaultValidatorFactory().getValidator();
+    }
+}

+ 404 - 0
yudao-admin-server/src/test/java/cn/iocoder/yudao/adminserver/modules/pay/service/channel/PayChannelServiceTest.java

@@ -0,0 +1,404 @@
+package cn.iocoder.yudao.adminserver.modules.pay.service.channel;
+
+import cn.iocoder.yudao.adminserver.BaseDbUnitTest;
+import cn.iocoder.yudao.adminserver.modules.pay.controller.channel.vo.PayChannelCreateReqVO;
+import cn.iocoder.yudao.adminserver.modules.pay.controller.channel.vo.PayChannelExportReqVO;
+import cn.iocoder.yudao.adminserver.modules.pay.controller.channel.vo.PayChannelPageReqVO;
+import cn.iocoder.yudao.adminserver.modules.pay.controller.channel.vo.PayChannelUpdateReqVO;
+import cn.iocoder.yudao.adminserver.modules.pay.dal.mysql.channel.PayChannelMapper;
+import cn.iocoder.yudao.adminserver.modules.pay.service.channel.impl.PayChannelServiceImpl;
+import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayChannelDO;
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.pay.core.client.impl.alipay.AlipayPayClientConfig;
+import cn.iocoder.yudao.framework.pay.core.client.impl.wx.WXPayClientConfig;
+import cn.iocoder.yudao.framework.pay.core.enums.PayChannelEnum;
+import com.alibaba.fastjson.JSON;
+import org.junit.jupiter.api.Test;
+import org.springframework.context.annotation.Import;
+
+import javax.annotation.Resource;
+import java.util.List;
+
+import static cn.iocoder.yudao.coreservice.modules.pay.enums.PayErrorCodeCoreConstants.CHANNEL_NOT_EXISTS;
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.buildTime;
+import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId;
+import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
+import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException;
+import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId;
+import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * {@link PayChannelServiceImpl} 的单元测试类
+ *
+ * @author 芋艿
+ */
+@Import({
+        PayChannelServiceImpl.class,
+        PayChannelConfig.class
+})
+public class PayChannelServiceTest extends BaseDbUnitTest {
+
+    @Resource
+    private PayChannelServiceImpl channelService;
+
+    @Resource
+    private PayChannelMapper channelMapper;
+
+    @Test
+    public void testCreateWechatVersion2Channel_success() {
+        // 准备参数
+
+        WXPayClientConfig v2Config = getV2Config();
+        PayChannelCreateReqVO reqVO = randomPojo(PayChannelCreateReqVO.class, o -> {
+            o.setCode(PayChannelEnum.WX_PUB.getCode());
+            o.setStatus(CommonStatusEnum.ENABLE.getStatus());
+            o.setConfig(JSON.toJSONString(v2Config));
+        });
+
+        // 调用
+        Long channelId = channelService.createChannel(reqVO);
+        // 断言
+        assertNotNull(channelId);
+        // 校验记录的属性是否正确
+        PayChannelDO channel = channelMapper.selectById(channelId);
+        assertPojoEquals(reqVO, channel, "config");
+        // 关于config 对象应该拿出来重新对比
+        assertPojoEquals(v2Config, channel.getConfig());
+
+    }
+
+    @Test
+    public void testCreateWechatVersion3Channel_success() {
+        // 准备参数
+        WXPayClientConfig v3Config = getV3Config();
+        PayChannelCreateReqVO reqVO = randomPojo(PayChannelCreateReqVO.class, o -> {
+            o.setCode(PayChannelEnum.WX_PUB.getCode());
+            o.setStatus(CommonStatusEnum.ENABLE.getStatus());
+            o.setConfig(JSON.toJSONString(v3Config));
+        });
+
+        // 调用
+        Long channelId = channelService.createChannel(reqVO);
+        // 断言
+        assertNotNull(channelId);
+        // 校验记录的属性是否正确
+        PayChannelDO channel = channelMapper.selectById(channelId);
+        assertPojoEquals(reqVO, channel, "config");
+        // 关于config 对象应该拿出来重新对比
+        assertPojoEquals(v3Config, channel.getConfig());
+    }
+
+    @Test
+    public void testCreateAliPayPublicKeyChannel_success() {
+        // 准备参数
+
+        AlipayPayClientConfig payClientConfig = getPublicKeyConfig();
+        PayChannelCreateReqVO reqVO = randomPojo(PayChannelCreateReqVO.class, o -> {
+            o.setCode(PayChannelEnum.ALIPAY_APP.getCode());
+            o.setStatus(CommonStatusEnum.ENABLE.getStatus());
+            o.setConfig(JSON.toJSONString(payClientConfig));
+        });
+
+        // 调用
+        Long channelId = channelService.createChannel(reqVO);
+        // 断言
+        assertNotNull(channelId);
+        // 校验记录的属性是否正确
+        PayChannelDO channel = channelMapper.selectById(channelId);
+        assertPojoEquals(reqVO, channel, "config");
+        // 关于config 对象应该拿出来重新对比
+        assertPojoEquals(payClientConfig, channel.getConfig());
+
+    }
+
+    @Test
+    public void testCreateAliPayCertificateChannel_success() {
+        // 准备参数
+
+        AlipayPayClientConfig payClientConfig = getCertificateConfig();
+        PayChannelCreateReqVO reqVO = randomPojo(PayChannelCreateReqVO.class, o -> {
+            o.setCode(PayChannelEnum.ALIPAY_APP.getCode());
+            o.setStatus(CommonStatusEnum.ENABLE.getStatus());
+            o.setConfig(JSON.toJSONString(payClientConfig));
+        });
+
+        // 调用
+        Long channelId = channelService.createChannel(reqVO);
+        // 断言
+        assertNotNull(channelId);
+        // 校验记录的属性是否正确
+        PayChannelDO channel = channelMapper.selectById(channelId);
+        assertPojoEquals(reqVO, channel, "config");
+        // 关于config 对象应该拿出来重新对比
+        assertPojoEquals(payClientConfig, channel.getConfig());
+    }
+
+    @Test
+    public void testUpdateChannel_success() {
+        // mock 数据
+        AlipayPayClientConfig payClientConfig = getCertificateConfig();
+        PayChannelDO dbChannel = randomPojo(PayChannelDO.class, o -> {
+            o.setCode(PayChannelEnum.ALIPAY_APP.getCode());
+            o.setStatus(CommonStatusEnum.ENABLE.getStatus());
+            o.setConfig(payClientConfig);
+        });
+        channelMapper.insert(dbChannel);// @Sql: 先插入出一条存在的数据
+        // 准备参数
+        AlipayPayClientConfig payClientPublicKeyConfig = getPublicKeyConfig();
+        PayChannelUpdateReqVO reqVO = randomPojo(PayChannelUpdateReqVO.class, o -> {
+            o.setCode(dbChannel.getCode());
+            o.setStatus(dbChannel.getStatus());
+            o.setConfig(JSON.toJSONString(payClientPublicKeyConfig));
+            o.setId(dbChannel.getId()); // 设置更新的 ID
+        });
+
+        // 调用
+        channelService.updateChannel(reqVO);
+        // 校验是否更新正确
+        PayChannelDO channel = channelMapper.selectById(reqVO.getId()); // 获取最新的
+        assertPojoEquals(reqVO, channel, "config");
+        assertPojoEquals(payClientPublicKeyConfig, channel.getConfig());
+    }
+
+    @Test
+    public void testUpdateChannel_notExists() {
+        // 准备参数
+        AlipayPayClientConfig payClientPublicKeyConfig = getPublicKeyConfig();
+        PayChannelUpdateReqVO reqVO = randomPojo(PayChannelUpdateReqVO.class, o -> {
+            o.setCode(PayChannelEnum.ALIPAY_APP.getCode());
+            o.setStatus(CommonStatusEnum.ENABLE.getStatus());
+            o.setConfig(JSON.toJSONString(payClientPublicKeyConfig));
+        });
+
+        // 调用, 并断言异常
+        assertServiceException(() -> channelService.updateChannel(reqVO), CHANNEL_NOT_EXISTS);
+    }
+
+    @Test
+    public void testDeleteChannel_success() {
+        // mock 数据
+        AlipayPayClientConfig payClientConfig = getCertificateConfig();
+        PayChannelDO dbChannel = randomPojo(PayChannelDO.class, o -> {
+            o.setCode(PayChannelEnum.ALIPAY_APP.getCode());
+            o.setStatus(CommonStatusEnum.ENABLE.getStatus());
+            o.setConfig(payClientConfig);
+        });
+        channelMapper.insert(dbChannel);// @Sql: 先插入出一条存在的数据
+        // 准备参数
+        Long id = dbChannel.getId();
+
+        // 调用
+        channelService.deleteChannel(id);
+        // 校验数据不存在了
+        assertNull(channelMapper.selectById(id));
+    }
+
+    @Test
+    public void testDeleteChannel_notExists() {
+        // 准备参数
+        Long id = randomLongId();
+
+        // 调用, 并断言异常
+        assertServiceException(() -> channelService.deleteChannel(id), CHANNEL_NOT_EXISTS);
+    }
+
+    @Test // TODO 请修改 null 为需要的值
+    public void testGetChannelPage() {
+        // mock 数据
+        AlipayPayClientConfig payClientConfig = getCertificateConfig();
+        PayChannelDO dbChannel = randomPojo(PayChannelDO.class, o -> { // 等会查询到
+            o.setCode(PayChannelEnum.ALIPAY_APP.getCode());
+            o.setStatus(CommonStatusEnum.ENABLE.getStatus());
+            o.setRemark("灿灿子的支付渠道");
+            o.setFeeRate(0.03);
+            o.setMerchantId(1L);
+            o.setAppId(1L);
+            o.setConfig(payClientConfig);
+            o.setCreateTime(buildTime(2021,11,20));
+        });
+        channelMapper.insert(dbChannel);
+        // 执行拷贝的时候会出现异常,所以在插入后要重置为null 后续在写入新的
+        dbChannel.setConfig(null);
+        // 测试 code 不匹配
+        channelMapper.insert(cloneIgnoreId(dbChannel, o -> {
+            o.setConfig(payClientConfig);
+            o.setCode(PayChannelEnum.WX_PUB.getCode());
+        }));
+        // 测试 status 不匹配
+        channelMapper.insert(cloneIgnoreId(dbChannel, o -> {
+            o.setConfig(payClientConfig);
+            o.setStatus(CommonStatusEnum.DISABLE.getStatus());
+        }));
+        // 测试 remark 不匹配
+        channelMapper.insert(cloneIgnoreId(dbChannel, o ->{
+            o.setConfig(payClientConfig);
+            o.setRemark("敏敏子的渠道");
+        }));
+        // 测试 feeRate 不匹配
+        channelMapper.insert(cloneIgnoreId(dbChannel, o -> {
+            o.setConfig(payClientConfig);
+            o.setFeeRate(1.23);
+        }));
+        // 测试 merchantId 不匹配
+        channelMapper.insert(cloneIgnoreId(dbChannel, o -> {
+            o.setConfig(payClientConfig);
+            o.setMerchantId(2L);
+        }));
+        // 测试 appId 不匹配
+        channelMapper.insert(cloneIgnoreId(dbChannel, o -> {
+            o.setConfig(payClientConfig);
+            o.setAppId(2L);
+        }));
+        // 测试 createTime 不匹配
+        channelMapper.insert(cloneIgnoreId(dbChannel, o -> {
+            o.setConfig(payClientConfig);
+            o.setCreateTime(buildTime(2021, 10, 20));
+        }));
+        // 准备参数
+        PayChannelPageReqVO reqVO = new PayChannelPageReqVO();
+        reqVO.setCode(PayChannelEnum.ALIPAY_APP.getCode());
+        reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus());
+        reqVO.setRemark("灿灿子的支付渠道");
+        reqVO.setFeeRate(0.03);
+        reqVO.setMerchantId(1L);
+        reqVO.setAppId(1L);
+        reqVO.setConfig(JSON.toJSONString(payClientConfig));
+        reqVO.setBeginCreateTime(buildTime(2021,11,19));
+        reqVO.setEndCreateTime(buildTime(2021,11,21));
+
+        // 调用
+        PageResult<PayChannelDO> pageResult = channelService.getChannelPage(reqVO);
+        // 断言
+        assertEquals(1, pageResult.getTotal());
+        assertEquals(1, pageResult.getList().size());
+        assertPojoEquals(dbChannel, pageResult.getList().get(0), "config");
+        assertPojoEquals(payClientConfig, pageResult.getList().get(0).getConfig());
+
+    }
+
+    @Test
+    public void testGetChannelList() {
+        // mock 数据
+        AlipayPayClientConfig payClientConfig = getCertificateConfig();
+        PayChannelDO dbChannel = randomPojo(PayChannelDO.class, o -> { // 等会查询到
+            o.setCode(PayChannelEnum.ALIPAY_APP.getCode());
+            o.setStatus(CommonStatusEnum.ENABLE.getStatus());
+            o.setRemark("灿灿子的支付渠道");
+            o.setFeeRate(0.03);
+            o.setMerchantId(1L);
+            o.setAppId(1L);
+            o.setConfig(payClientConfig);
+            o.setCreateTime(buildTime(2021,11,20));
+        });
+        channelMapper.insert(dbChannel);
+        // 执行拷贝的时候会出现异常,所以在插入后要重置为null 后续在写入新的
+        dbChannel.setConfig(null);
+        // 测试 code 不匹配
+        channelMapper.insert(cloneIgnoreId(dbChannel, o -> {
+            o.setConfig(payClientConfig);
+            o.setCode(PayChannelEnum.WX_PUB.getCode());
+        }));
+        // 测试 status 不匹配
+        channelMapper.insert(cloneIgnoreId(dbChannel, o -> {
+            o.setConfig(payClientConfig);
+            o.setStatus(CommonStatusEnum.DISABLE.getStatus());
+        }));
+        // 测试 remark 不匹配
+        channelMapper.insert(cloneIgnoreId(dbChannel, o ->{
+            o.setConfig(payClientConfig);
+            o.setRemark("敏敏子的渠道");
+        }));
+        // 测试 feeRate 不匹配
+        channelMapper.insert(cloneIgnoreId(dbChannel, o -> {
+            o.setConfig(payClientConfig);
+            o.setFeeRate(1.23);
+        }));
+        // 测试 merchantId 不匹配
+        channelMapper.insert(cloneIgnoreId(dbChannel, o -> {
+            o.setConfig(payClientConfig);
+            o.setMerchantId(2L);
+        }));
+        // 测试 appId 不匹配
+        channelMapper.insert(cloneIgnoreId(dbChannel, o -> {
+            o.setConfig(payClientConfig);
+            o.setAppId(2L);
+        }));
+        // 测试 createTime 不匹配
+        channelMapper.insert(cloneIgnoreId(dbChannel, o -> {
+            o.setConfig(payClientConfig);
+            o.setCreateTime(buildTime(2021, 10, 20));
+        }));
+        // 准备参数
+        PayChannelExportReqVO reqVO = new PayChannelExportReqVO();
+        reqVO.setCode(PayChannelEnum.ALIPAY_APP.getCode());
+        reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus());
+        reqVO.setRemark("灿灿子的支付渠道");
+        reqVO.setFeeRate(0.03);
+        reqVO.setMerchantId(1L);
+        reqVO.setAppId(1L);
+        reqVO.setConfig(JSON.toJSONString(payClientConfig));
+        reqVO.setBeginCreateTime(buildTime(2021,11,19));
+        reqVO.setEndCreateTime(buildTime(2021,11,21));
+
+        // 调用
+        List<PayChannelDO> list = channelService.getChannelList(reqVO);
+        // 断言
+        assertEquals(1, list.size());
+        assertPojoEquals(dbChannel, list.get(0), "config");
+        assertPojoEquals(payClientConfig, list.get(0).getConfig());
+    }
+
+
+    public WXPayClientConfig getV2Config() {
+        return new WXPayClientConfig()
+                .setAppId("APP00001")
+                .setMchId("MCH00001")
+                .setApiVersion(WXPayClientConfig.API_VERSION_V2)
+                .setMchKey("dsa1d5s6a1d6sa16d1sa56d15a61das6")
+                .setApiV3Key("")
+                .setPrivateCertContent("")
+                .setPrivateKeyContent("");
+    }
+
+    public WXPayClientConfig getV3Config() {
+        return new WXPayClientConfig()
+                .setAppId("APP00001")
+                .setMchId("MCH00001")
+                .setApiVersion(WXPayClientConfig.API_VERSION_V3)
+                .setMchKey("")
+                .setApiV3Key("sdadasdsadadsa")
+                .setPrivateKeyContent("dsa445das415d15asd16ad156as")
+                .setPrivateCertContent("dsadasd45asd4s5a");
+
+    }
+
+    public AlipayPayClientConfig getPublicKeyConfig() {
+        return new AlipayPayClientConfig()
+                .setServerUrl(AlipayPayClientConfig.SERVER_URL_PROD)
+                .setAppId("APP00001")
+                .setSignType(AlipayPayClientConfig.SIGN_TYPE_DEFAULT)
+                .setMode(AlipayPayClientConfig.MODE_PUBLIC_KEY)
+                .setPrivateKey("13131321312")
+                .setAlipayPublicKey("13321321321")
+                .setAppCertContent("")
+                .setAlipayPublicCertContent("")
+                .setRootCertContent("");
+    }
+
+    public AlipayPayClientConfig getCertificateConfig() {
+        return new AlipayPayClientConfig()
+                .setServerUrl(AlipayPayClientConfig.SERVER_URL_PROD)
+                .setAppId("APP00001")
+                .setSignType(AlipayPayClientConfig.SIGN_TYPE_DEFAULT)
+                .setMode(AlipayPayClientConfig.MODE_CERTIFICATE)
+                .setPrivateKey("")
+                .setAlipayPublicKey("")
+                .setAppCertContent("13321321321sda")
+                .setAlipayPublicCertContent("13321321321aqeqw")
+                .setRootCertContent("13321321321dsad");
+    }
+
+
+}

+ 193 - 0
yudao-admin-server/src/test/java/cn/iocoder/yudao/adminserver/modules/pay/service/merchant/PayMerchantServiceTest.java

@@ -0,0 +1,193 @@
+package cn.iocoder.yudao.adminserver.modules.pay.service.merchant;
+
+import cn.hutool.core.util.RandomUtil;
+import cn.iocoder.yudao.adminserver.BaseDbUnitTest;
+import cn.iocoder.yudao.adminserver.modules.pay.controller.merchant.vo.PayMerchantCreateReqVO;
+import cn.iocoder.yudao.adminserver.modules.pay.controller.merchant.vo.PayMerchantExportReqVO;
+import cn.iocoder.yudao.adminserver.modules.pay.controller.merchant.vo.PayMerchantPageReqVO;
+import cn.iocoder.yudao.adminserver.modules.pay.controller.merchant.vo.PayMerchantUpdateReqVO;
+import cn.iocoder.yudao.adminserver.modules.pay.dal.mysql.merchant.PayMerchantMapper;
+import cn.iocoder.yudao.adminserver.modules.pay.service.merchant.impl.PayMerchantServiceImpl;
+import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayMerchantDO;
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import org.junit.jupiter.api.Test;
+import org.springframework.context.annotation.Import;
+
+import javax.annotation.Resource;
+import java.util.List;
+
+import static cn.iocoder.yudao.coreservice.modules.pay.enums.PayErrorCodeCoreConstants.PAY_MERCHANT_NOT_EXISTS;
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.buildTime;
+import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId;
+import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
+import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException;
+import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId;
+import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+* {@link PayMerchantServiceImpl} 的单元测试类
+*
+* @author aquan
+*/
+@Import(PayMerchantServiceImpl.class)
+public class PayMerchantServiceTest extends BaseDbUnitTest {
+
+    @Resource
+    private PayMerchantServiceImpl merchantService;
+
+    @Resource
+    private PayMerchantMapper merchantMapper;
+
+    @Test
+    public void testCreateMerchant_success() {
+        // 准备参数
+        PayMerchantCreateReqVO reqVO = randomPojo(PayMerchantCreateReqVO.class,o ->
+            o.setStatus(RandomUtil.randomEle(CommonStatusEnum.values()).getStatus()));
+
+        // 调用
+        Long merchantId = merchantService.createMerchant(reqVO);
+        // 断言
+        assertNotNull(merchantId);
+        // 校验记录的属性是否正确
+        PayMerchantDO merchant = merchantMapper.selectById(merchantId);
+        assertPojoEquals(reqVO, merchant);
+    }
+
+    @Test
+    public void testUpdateMerchant_success() {
+        // mock 数据
+        PayMerchantDO dbMerchant = randomPojo(PayMerchantDO.class, o ->
+            o.setStatus(CommonStatusEnum.ENABLE.getStatus()));
+        merchantMapper.insert(dbMerchant);// @Sql: 先插入出一条存在的数据
+        // 准备参数
+        PayMerchantUpdateReqVO reqVO = randomPojo(PayMerchantUpdateReqVO.class, o -> {
+            o.setId(dbMerchant.getId()); // 设置更新的 ID
+            o.setStatus(CommonStatusEnum.DISABLE.getStatus());
+        });
+
+        // 调用
+        merchantService.updateMerchant(reqVO);
+        // 校验是否更新正确
+        PayMerchantDO merchant = merchantMapper.selectById(reqVO.getId()); // 获取最新的
+        assertPojoEquals(reqVO, merchant);
+    }
+
+    @Test
+    public void testUpdateMerchant_notExists() {
+        // 准备参数
+        PayMerchantUpdateReqVO reqVO = randomPojo(PayMerchantUpdateReqVO.class);
+
+        // 调用, 并断言异常
+        assertServiceException(() -> merchantService.updateMerchant(reqVO), PAY_MERCHANT_NOT_EXISTS);
+    }
+
+    @Test
+    public void testDeleteMerchant_success() {
+        // mock 数据
+        PayMerchantDO dbMerchant = randomPojo(PayMerchantDO.class,
+                o-> o.setStatus(CommonStatusEnum.ENABLE.getStatus()));
+        merchantMapper.insert(dbMerchant);// @Sql: 先插入出一条存在的数据
+        // 准备参数
+        Long id = dbMerchant.getId();
+
+        // 调用
+        merchantService.deleteMerchant(id);
+       // 校验数据不存在了
+       assertNull(merchantMapper.selectById(id));
+    }
+
+    @Test
+    public void testDeleteMerchant_notExists() {
+        // 准备参数
+        Long id = randomLongId();
+
+        // 调用, 并断言异常
+        assertServiceException(() -> merchantService.deleteMerchant(id), PAY_MERCHANT_NOT_EXISTS);
+    }
+
+    @Test
+    public void testGetMerchantPage() {
+       // mock 数据
+       PayMerchantDO dbMerchant = randomPojo(PayMerchantDO.class, o -> { // 等会查询到
+           o.setNo("M1008611");
+           o.setName("灿哥的杂货铺");
+           o.setShortName("灿灿子");
+           o.setStatus(CommonStatusEnum.ENABLE.getStatus());
+           o.setRemark("灿哥的杂货铺");
+           o.setCreateTime(buildTime(2021,11,3));
+       });
+       merchantMapper.insert(dbMerchant);
+       // 测试 no 不匹配
+       merchantMapper.insert(cloneIgnoreId(dbMerchant, o -> o.setNo("M200000")));
+       // 测试 name 不匹配
+       merchantMapper.insert(cloneIgnoreId(dbMerchant, o -> o.setName("斌哥的杂货铺")));
+       // 测试 shortName 不匹配
+       merchantMapper.insert(cloneIgnoreId(dbMerchant, o -> o.setShortName("斌斌子")));
+       // 测试 status 不匹配
+       merchantMapper.insert(cloneIgnoreId(dbMerchant, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus())));
+       // 测试 remark 不匹配
+       merchantMapper.insert(cloneIgnoreId(dbMerchant, o -> o.setRemark("斌哥的杂货铺")));
+       // 测试 createTime 不匹配
+       merchantMapper.insert(cloneIgnoreId(dbMerchant, o -> o.setCreateTime(buildTime(2022,12,4))));
+       // 准备参数
+       PayMerchantPageReqVO reqVO = new PayMerchantPageReqVO();
+       reqVO.setNo("M1008611");
+       reqVO.setName("灿哥的杂货铺");
+       reqVO.setShortName("灿灿子");
+       reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus());
+       reqVO.setRemark("灿哥的杂货铺");
+       reqVO.setBeginCreateTime(buildTime(2021,11,2));
+       reqVO.setEndCreateTime(buildTime(2021,11,4));
+
+       // 调用
+       PageResult<PayMerchantDO> pageResult = merchantService.getMerchantPage(reqVO);
+       // 断言
+       assertEquals(1, pageResult.getTotal());
+       assertEquals(1, pageResult.getList().size());
+       assertPojoEquals(dbMerchant, pageResult.getList().get(0));
+    }
+
+    @Test
+    public void testGetMerchantList() {
+       // mock 数据
+       PayMerchantDO dbMerchant = randomPojo(PayMerchantDO.class, o -> { // 等会查询到
+           o.setNo("M1008611");
+           o.setName("灿哥的杂货铺");
+           o.setShortName("灿灿子");
+           o.setStatus(CommonStatusEnum.ENABLE.getStatus());
+           o.setRemark("灿哥的杂货铺");
+           o.setCreateTime(buildTime(2021,11,3));
+       });
+        merchantMapper.insert(dbMerchant);
+        // 测试 no 不匹配
+        merchantMapper.insert(cloneIgnoreId(dbMerchant, o -> o.setNo("M200000")));
+        // 测试 name 不匹配
+        merchantMapper.insert(cloneIgnoreId(dbMerchant, o -> o.setName("斌哥的杂货铺")));
+        // 测试 shortName 不匹配
+        merchantMapper.insert(cloneIgnoreId(dbMerchant, o -> o.setShortName("斌斌子")));
+        // 测试 status 不匹配
+        merchantMapper.insert(cloneIgnoreId(dbMerchant, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus())));
+        // 测试 remark 不匹配
+        merchantMapper.insert(cloneIgnoreId(dbMerchant, o -> o.setRemark("斌哥的杂货铺")));
+        // 测试 createTime 不匹配
+        merchantMapper.insert(cloneIgnoreId(dbMerchant, o -> o.setCreateTime(buildTime(2022,12,4))));
+       // 准备参数
+       PayMerchantExportReqVO reqVO = new PayMerchantExportReqVO();
+       reqVO.setNo("M1008611");
+       reqVO.setName("灿哥的杂货铺");
+       reqVO.setShortName("灿灿子");
+       reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus());
+       reqVO.setRemark("灿哥的杂货铺");
+       reqVO.setBeginCreateTime(buildTime(2021,11,2));
+       reqVO.setEndCreateTime(buildTime(2021,11,4));
+
+       // 调用
+       List<PayMerchantDO> list = merchantService.getMerchantList(reqVO);
+       // 断言
+       assertEquals(1, list.size());
+       assertPojoEquals(dbMerchant, list.get(0));
+    }
+
+}

+ 196 - 0
yudao-admin-server/src/test/java/cn/iocoder/yudao/adminserver/modules/pay/service/order/PayOrderServiceTest.java

@@ -0,0 +1,196 @@
+package cn.iocoder.yudao.adminserver.modules.pay.service.order;
+
+import cn.hutool.core.date.DateUtil;
+import cn.hutool.core.util.RandomUtil;
+import cn.iocoder.yudao.adminserver.BaseDbUnitTest;
+import cn.iocoder.yudao.adminserver.modules.pay.controller.order.vo.order.PayOrderExportReqVO;
+import cn.iocoder.yudao.adminserver.modules.pay.controller.order.vo.order.PayOrderPageReqVO;
+import cn.iocoder.yudao.adminserver.modules.pay.dal.mysql.order.PayOrderMapper;
+import cn.iocoder.yudao.adminserver.modules.pay.service.order.impl.PayOrderServiceImpl;
+import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayOrderDO;
+import cn.iocoder.yudao.coreservice.modules.pay.enums.order.PayOrderNotifyStatusEnum;
+import cn.iocoder.yudao.coreservice.modules.pay.enums.order.PayOrderStatusEnum;
+import cn.iocoder.yudao.coreservice.modules.pay.enums.order.PayRefundTypeEnum;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.date.DateUtils;
+import cn.iocoder.yudao.framework.pay.core.enums.PayChannelEnum;
+import org.junit.jupiter.api.Test;
+import org.springframework.context.annotation.Import;
+
+import javax.annotation.Resource;
+import java.util.Date;
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId;
+import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
+import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+/**
+ * {@link PayOrderServiceImpl} 的单元测试类
+ *
+ * @author 芋艿
+ */
+@Import(PayOrderServiceImpl.class)
+public class PayOrderServiceTest extends BaseDbUnitTest {
+
+    @Resource
+    private PayOrderServiceImpl orderService;
+
+    @Resource
+    private PayOrderMapper orderMapper;
+
+    public String generateNo() {
+        return DateUtil.format(new Date(), "yyyyMMddHHmmss") + RandomUtil.randomInt(100000, 999999);
+    }
+
+    @Test
+    public void testGetOrderPage() {
+
+        String merchantOrderId = generateNo();
+        String channelOrderId = generateNo();
+
+        // mock 数据
+        PayOrderDO dbOrder = randomPojo(PayOrderDO.class, o -> { // 等会查询到
+            o.setMerchantId(1L);
+            o.setAppId(1L);
+            o.setChannelId(1L);
+            o.setChannelCode(PayChannelEnum.WX_PUB.getCode());
+            o.setMerchantOrderId(merchantOrderId);
+            o.setSubject("灿灿子的炸弹猫");
+            o.setBody("斌斌子送给灿灿子的炸弹猫");
+            o.setNotifyUrl("https://hc.com/lbh");
+            o.setNotifyStatus(PayOrderNotifyStatusEnum.SUCCESS.getStatus());
+            o.setAmount(10000L);
+            o.setChannelFeeRate(0.01);
+            o.setChannelFeeAmount(1L);
+            o.setStatus(PayOrderStatusEnum.SUCCESS.getStatus());
+            o.setUserIp("127.0.0.1");
+            o.setCreateTime(DateUtils.buildTime(2018, 1, 1, 10, 1, 0));
+            o.setExpireTime(DateUtils.buildTime(2018, 1, 1, 10, 30, 0));
+            o.setSuccessTime(DateUtils.buildTime(2018, 1, 1, 10, 10, 2));
+            o.setNotifyTime(DateUtils.buildTime(2018, 1, 1, 10, 10, 15));
+            o.setSuccessExtensionId(1L);
+            o.setRefundStatus(PayRefundTypeEnum.NO.getStatus());
+            o.setRefundTimes(0);
+            o.setRefundAmount(0L);
+            o.setChannelUserId("1008611");
+            o.setChannelOrderNo(channelOrderId);
+            o.setUpdateTime(DateUtils.buildTime(2018, 1, 1, 10, 10, 15));
+        });
+        orderMapper.insert(dbOrder);
+        // 测试 merchantId 不匹配
+        orderMapper.insert(cloneIgnoreId(dbOrder, o -> o.setMerchantId(2L)));
+        // 测试 appId 不匹配
+        orderMapper.insert(cloneIgnoreId(dbOrder, o -> o.setAppId(2L)));
+        // 测试 channelId 不匹配
+        orderMapper.insert(cloneIgnoreId(dbOrder, o -> o.setChannelId(2L)));
+        // 测试 channelCode 不匹配
+        orderMapper.insert(cloneIgnoreId(dbOrder, o -> o.setChannelCode(PayChannelEnum.ALIPAY_APP.getCode())));
+        // 测试 merchantOrderId 不匹配
+        orderMapper.insert(cloneIgnoreId(dbOrder, o -> o.setMerchantOrderId(generateNo())));
+        // 测试 notifyStatus 不匹配
+        orderMapper.insert(cloneIgnoreId(dbOrder, o -> o.setNotifyStatus(PayOrderNotifyStatusEnum.FAILURE.getStatus())));
+        // 测试 status 不匹配
+        orderMapper.insert(cloneIgnoreId(dbOrder, o -> o.setStatus(PayOrderStatusEnum.CLOSED.getStatus())));
+        // 测试 refundStatus 不匹配
+        orderMapper.insert(cloneIgnoreId(dbOrder, o -> o.setRefundStatus(PayRefundTypeEnum.ALL.getStatus())));
+        // 测试 createTime 不匹配
+        orderMapper.insert(cloneIgnoreId(dbOrder, o -> o.setCreateTime(DateUtils.buildTime(2019, 1, 1, 10, 10,
+                1))));
+        // 准备参数
+        PayOrderPageReqVO reqVO = new PayOrderPageReqVO();
+        reqVO.setMerchantId(1L);
+        reqVO.setAppId(1L);
+        reqVO.setChannelId(1L);
+        reqVO.setChannelCode(PayChannelEnum.WX_PUB.getCode());
+        reqVO.setMerchantOrderId(merchantOrderId);
+        reqVO.setNotifyStatus(PayOrderNotifyStatusEnum.SUCCESS.getStatus());
+        reqVO.setStatus(PayOrderStatusEnum.SUCCESS.getStatus());
+        reqVO.setRefundStatus(PayRefundTypeEnum.NO.getStatus());
+        reqVO.setBeginCreateTime(DateUtils.buildTime(2018, 1, 1, 10, 1, 0));
+        reqVO.setEndCreateTime(DateUtils.buildTime(2018, 1, 1, 10, 1, 0));
+
+        // 调用
+        PageResult<PayOrderDO> pageResult = orderService.getOrderPage(reqVO);
+        // 断言
+        assertEquals(1, pageResult.getTotal());
+        assertEquals(1, pageResult.getList().size());
+        assertPojoEquals(dbOrder, pageResult.getList().get(0));
+        // assertEquals(0, dbOrder.getUpdateTime().compareTo(pageResult.getList().get(0).getUpdateTime()));
+    }
+
+    @Test
+    public void testGetOrderList() {
+        // mock 数据
+        String merchantOrderId = generateNo();
+        String channelOrderId = generateNo();
+        PayOrderDO dbOrder = randomPojo(PayOrderDO.class, o -> { // 等会查询到
+            o.setMerchantId(1L);
+            o.setAppId(1L);
+            o.setChannelId(1L);
+            o.setChannelCode(PayChannelEnum.WX_PUB.getCode());
+            o.setMerchantOrderId(merchantOrderId);
+            o.setSubject("灿灿子的炸弹猫");
+            o.setBody("斌斌子送给灿灿子的炸弹猫");
+            o.setNotifyUrl("https://hc.com/lbh");
+            o.setNotifyStatus(PayOrderNotifyStatusEnum.SUCCESS.getStatus());
+            o.setAmount(10000L);
+            o.setChannelFeeRate(0.01);
+            o.setChannelFeeAmount(1L);
+            o.setStatus(PayOrderStatusEnum.SUCCESS.getStatus());
+            o.setUserIp("127.0.0.1");
+            o.setCreateTime(DateUtils.buildTime(2018, 1, 1, 10, 1, 0));
+            o.setExpireTime(DateUtils.buildTime(2018, 1, 1, 10, 30, 0));
+            o.setSuccessTime(DateUtils.buildTime(2018, 1, 1, 10, 10, 2));
+            o.setNotifyTime(DateUtils.buildTime(2018, 1, 1, 10, 10, 15));
+            o.setSuccessExtensionId(1L);
+            o.setRefundStatus(PayRefundTypeEnum.NO.getStatus());
+            o.setRefundTimes(0);
+            o.setRefundAmount(0L);
+            o.setChannelUserId("1008611");
+            o.setChannelOrderNo(channelOrderId);
+            o.setUpdateTime(DateUtils.buildTime(2018, 1, 1, 10, 10, 15));
+
+        });
+        orderMapper.insert(dbOrder);
+        // 测试 merchantId 不匹配
+        orderMapper.insert(cloneIgnoreId(dbOrder, o -> o.setMerchantId(2L)));
+        // 测试 appId 不匹配
+        orderMapper.insert(cloneIgnoreId(dbOrder, o -> o.setAppId(2L)));
+        // 测试 channelId 不匹配
+        orderMapper.insert(cloneIgnoreId(dbOrder, o -> o.setChannelId(2L)));
+        // 测试 channelCode 不匹配
+        orderMapper.insert(cloneIgnoreId(dbOrder, o -> o.setChannelCode(PayChannelEnum.ALIPAY_APP.getCode())));
+        // 测试 merchantOrderId 不匹配
+        orderMapper.insert(cloneIgnoreId(dbOrder, o -> o.setMerchantOrderId(generateNo())));
+        // 测试 notifyStatus 不匹配
+        orderMapper.insert(cloneIgnoreId(dbOrder, o -> o.setNotifyStatus(PayOrderNotifyStatusEnum.FAILURE.getStatus())));
+        // 测试 status 不匹配
+        orderMapper.insert(cloneIgnoreId(dbOrder, o -> o.setStatus(PayOrderStatusEnum.CLOSED.getStatus())));
+        // 测试 refundStatus 不匹配
+        orderMapper.insert(cloneIgnoreId(dbOrder, o -> o.setRefundStatus(PayRefundTypeEnum.ALL.getStatus())));
+        // 测试 createTime 不匹配
+        orderMapper.insert(cloneIgnoreId(dbOrder, o -> o.setCreateTime(DateUtils.buildTime(2019, 1, 1, 10, 10,
+                1))));
+        // 准备参数
+        PayOrderExportReqVO reqVO = new PayOrderExportReqVO();
+        reqVO.setMerchantId(1L);
+        reqVO.setAppId(1L);
+        reqVO.setChannelId(1L);
+        reqVO.setChannelCode(PayChannelEnum.WX_PUB.getCode());
+        reqVO.setMerchantOrderId(merchantOrderId);
+        reqVO.setNotifyStatus(PayOrderNotifyStatusEnum.SUCCESS.getStatus());
+        reqVO.setStatus(PayOrderStatusEnum.SUCCESS.getStatus());
+        reqVO.setRefundStatus(PayRefundTypeEnum.NO.getStatus());
+        reqVO.setBeginCreateTime(DateUtils.buildTime(2018, 1, 1, 10, 1, 0));
+        reqVO.setEndCreateTime(DateUtils.buildTime(2018, 1, 1, 10, 1, 0));
+
+        // 调用
+        List<PayOrderDO> list = orderService.getOrderList(reqVO);
+        // 断言
+        assertEquals(1, list.size());
+        assertPojoEquals(dbOrder, list.get(0));
+    }
+
+}

+ 183 - 0
yudao-admin-server/src/test/java/cn/iocoder/yudao/adminserver/modules/pay/service/refund/PayRefundServiceTest.java

@@ -0,0 +1,183 @@
+package cn.iocoder.yudao.adminserver.modules.pay.service.refund;
+
+import cn.iocoder.yudao.adminserver.BaseDbUnitTest;
+import cn.iocoder.yudao.adminserver.modules.pay.controller.order.vo.refund.vo.PayRefundExportReqVO;
+import cn.iocoder.yudao.adminserver.modules.pay.controller.order.vo.refund.vo.PayRefundPageReqVO;
+import cn.iocoder.yudao.adminserver.modules.pay.dal.mysql.order.PayRefundMapper;
+import cn.iocoder.yudao.adminserver.modules.pay.service.order.impl.PayRefundServiceImpl;
+import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayRefundDO;
+import cn.iocoder.yudao.coreservice.modules.pay.enums.order.PayOrderNotifyStatusEnum;
+import cn.iocoder.yudao.coreservice.modules.pay.enums.order.PayRefundStatusEnum;
+import cn.iocoder.yudao.coreservice.modules.pay.enums.order.PayRefundTypeEnum;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.date.DateUtils;
+import cn.iocoder.yudao.framework.pay.core.enums.PayChannelEnum;
+import org.junit.jupiter.api.Test;
+import org.springframework.context.annotation.Import;
+
+import javax.annotation.Resource;
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId;
+import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
+import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+
+/**
+ * {@link PayRefundServiceImpl} 的单元测试类
+ *
+ * @author aquan
+ */
+@Import(PayRefundServiceImpl.class)
+public class PayRefundServiceTest extends BaseDbUnitTest {
+
+    @Resource
+    private PayRefundServiceImpl refundService;
+
+    @Resource
+    private PayRefundMapper refundMapper;
+
+
+    @Test
+    public void testGetRefundPage() {
+        // mock 数据
+        PayRefundDO dbRefund = randomPojo(PayRefundDO.class, o -> { // 等会查询到
+            o.setMerchantId(1L);
+            o.setAppId(1L);
+            o.setChannelId(1L);
+            o.setChannelCode(PayChannelEnum.WX_PUB.getCode());
+            o.setOrderId(1L);
+            o.setTradeNo("OT0000001");
+            o.setMerchantOrderId("MOT0000001");
+            o.setMerchantRefundNo("MRF0000001");
+            o.setNotifyUrl("https://www.cancanzi.com");
+            o.setNotifyStatus(PayOrderNotifyStatusEnum.SUCCESS.getStatus());
+            o.setStatus(PayRefundStatusEnum.SUCCESS.getStatus());
+            o.setType(PayRefundTypeEnum.SOME.getStatus());
+            o.setPayAmount(100L);
+            o.setRefundAmount(500L);
+            o.setReason("就是想退款了,你有意见吗");
+            o.setUserIp("127.0.0.1");
+            o.setChannelOrderNo("CH0000001");
+            o.setChannelRefundNo("CHR0000001");
+            o.setChannelErrorCode("");
+            o.setChannelErrorMsg("");
+            o.setChannelExtras("");
+            o.setExpireTime(DateUtils.buildTime(2021, 1, 1, 10, 10, 30));
+            o.setSuccessTime(DateUtils.buildTime(2021, 1, 1, 10, 10, 15));
+            o.setNotifyTime(DateUtils.buildTime(2021, 1, 1, 10, 10, 20));
+            o.setCreateTime(DateUtils.buildTime(2021, 1, 1, 10, 10, 10));
+            o.setUpdateTime(DateUtils.buildTime(2021, 1, 1, 10, 10, 35));
+        });
+        refundMapper.insert(dbRefund);
+        // 测试 merchantId 不匹配
+        refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setMerchantId(2L)));
+        // 测试 appId 不匹配
+        refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setAppId(2L)));
+        // 测试 channelCode 不匹配
+        refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setChannelCode(PayChannelEnum.ALIPAY_APP.getCode())));
+        // 测试 merchantRefundNo 不匹配
+        refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setMerchantRefundNo("MRF1111112")));
+        // 测试 notifyStatus 不匹配
+        refundMapper.insert(
+                cloneIgnoreId(dbRefund, o -> o.setNotifyStatus(PayOrderNotifyStatusEnum.FAILURE.getStatus())));
+        // 测试 status 不匹配
+        refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setStatus(PayRefundStatusEnum.CLOSE.getStatus())));
+        // 测试 type 不匹配
+        refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setType(PayRefundTypeEnum.ALL.getStatus())));
+        // 测试 createTime 不匹配
+        refundMapper.insert(cloneIgnoreId(dbRefund, o ->
+                o.setCreateTime(DateUtils.buildTime(2022, 1, 1, 10, 10, 10))));
+        // 准备参数
+        PayRefundPageReqVO reqVO = new PayRefundPageReqVO();
+        reqVO.setMerchantId(1L);
+        reqVO.setAppId(1L);
+        reqVO.setChannelCode(PayChannelEnum.WX_PUB.getCode());
+        reqVO.setMerchantRefundNo("MRF0000001");
+        reqVO.setNotifyStatus(PayOrderNotifyStatusEnum.SUCCESS.getStatus());
+        reqVO.setStatus(PayRefundStatusEnum.SUCCESS.getStatus());
+        reqVO.setType(PayRefundTypeEnum.SOME.getStatus());
+        reqVO.setBeginCreateTime(DateUtils.buildTime(2021, 1, 1, 10, 10, 10));
+        reqVO.setEndCreateTime(DateUtils.buildTime(2021, 1, 1, 10, 10, 12));
+
+
+        // 调用
+        PageResult<PayRefundDO> pageResult = refundService.getRefundPage(reqVO);
+        // 断言
+        assertEquals(1, pageResult.getTotal());
+        assertEquals(1, pageResult.getList().size());
+        assertPojoEquals(dbRefund, pageResult.getList().get(0));
+    }
+
+    @Test
+    public void testGetRefundList() {
+        // mock 数据
+        PayRefundDO dbRefund = randomPojo(PayRefundDO.class, o -> { // 等会查询到
+            o.setMerchantId(1L);
+            o.setAppId(1L);
+            o.setChannelId(1L);
+            o.setChannelCode(PayChannelEnum.WX_PUB.getCode());
+            o.setOrderId(1L);
+            o.setTradeNo("OT0000001");
+            o.setMerchantOrderId("MOT0000001");
+            o.setMerchantRefundNo("MRF0000001");
+            o.setNotifyUrl("https://www.cancanzi.com");
+            o.setNotifyStatus(PayOrderNotifyStatusEnum.SUCCESS.getStatus());
+            o.setStatus(PayRefundStatusEnum.SUCCESS.getStatus());
+            o.setType(PayRefundTypeEnum.SOME.getStatus());
+            o.setPayAmount(100L);
+            o.setRefundAmount(500L);
+            o.setReason("就是想退款了,你有意见吗");
+            o.setUserIp("127.0.0.1");
+            o.setChannelOrderNo("CH0000001");
+            o.setChannelRefundNo("CHR0000001");
+            o.setChannelErrorCode("");
+            o.setChannelErrorMsg("");
+            o.setChannelExtras("");
+            o.setExpireTime(DateUtils.buildTime(2021, 1, 1, 10, 10, 30));
+            o.setSuccessTime(DateUtils.buildTime(2021, 1, 1, 10, 10, 15));
+            o.setNotifyTime(DateUtils.buildTime(2021, 1, 1, 10, 10, 20));
+            o.setCreateTime(DateUtils.buildTime(2021, 1, 1, 10, 10, 10));
+            o.setUpdateTime(DateUtils.buildTime(2021, 1, 1, 10, 10, 35));
+        });
+        refundMapper.insert(dbRefund);
+        // 测试 merchantId 不匹配
+        refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setMerchantId(2L)));
+        // 测试 appId 不匹配
+        refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setAppId(2L)));
+        // 测试 channelCode 不匹配
+        refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setChannelCode(PayChannelEnum.ALIPAY_APP.getCode())));
+        // 测试 merchantRefundNo 不匹配
+        refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setMerchantRefundNo("MRF1111112")));
+        // 测试 notifyStatus 不匹配
+        refundMapper.insert(
+                cloneIgnoreId(dbRefund, o -> o.setNotifyStatus(PayOrderNotifyStatusEnum.FAILURE.getStatus())));
+        // 测试 status 不匹配
+        refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setStatus(PayRefundStatusEnum.CLOSE.getStatus())));
+        // 测试 type 不匹配
+        refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setType(PayRefundTypeEnum.ALL.getStatus())));
+        // 测试 createTime 不匹配
+        refundMapper.insert(cloneIgnoreId(dbRefund, o ->
+                o.setCreateTime(DateUtils.buildTime(2022, 1, 1, 10, 10, 10))));
+
+        // 准备参数
+        PayRefundExportReqVO reqVO = new PayRefundExportReqVO();
+        reqVO.setMerchantId(1L);
+        reqVO.setAppId(1L);
+        reqVO.setChannelCode(PayChannelEnum.WX_PUB.getCode());
+        reqVO.setMerchantRefundNo("MRF0000001");
+        reqVO.setNotifyStatus(PayOrderNotifyStatusEnum.SUCCESS.getStatus());
+        reqVO.setStatus(PayRefundStatusEnum.SUCCESS.getStatus());
+        reqVO.setType(PayRefundTypeEnum.SOME.getStatus());
+        reqVO.setBeginCreateTime(DateUtils.buildTime(2021, 1, 1, 10, 10, 10));
+        reqVO.setEndCreateTime(DateUtils.buildTime(2021, 1, 1, 10, 10, 12));
+
+        // 调用
+        List<PayRefundDO> list = refundService.getRefundList(reqVO);
+        // 断言
+        assertEquals(1, list.size());
+        assertPojoEquals(dbRefund, list.get(0));
+    }
+
+}

+ 7 - 0
yudao-admin-server/src/test/resources/sql/clean.sql

@@ -26,5 +26,12 @@ DELETE FROM "sys_error_code";
 DELETE FROM "sys_social_user";
 DELETE FROM "sys_tenant";
 
+-- pay 开头的 DB
+DELETE FROM pay_merchant;
+DELETE FROM pay_app;
+DELETE FROM pay_channel;
+DELETE FROM pay_order;
+DELETE FROM pay_refund;
+
 -- bpm 开头的 DB
 DELETE FROM "bpm_form";

+ 125 - 0
yudao-admin-server/src/test/resources/sql/create_tables.sql

@@ -474,6 +474,131 @@ CREATE TABLE IF NOT EXISTS "sys_tenant" (
 ) COMMENT '租户';
 
 
+CREATE TABLE IF NOT EXISTS "pay_merchant"
+(
+    "id" number NOT NULL GENERATED BY DEFAULT AS IDENTITY,
+    "no"          varchar(32) NOT NULL,
+    "name"        varchar(64) NOT NULL,
+    "short_name"  varchar(64) NOT NULL,
+    "status"      tinyint     NOT NULL,
+    "remark"      varchar(255)         DEFAULT NULL,
+    "creator"     varchar(64)          DEFAULT '',
+    "create_time" datetime    NOT NULL DEFAULT CURRENT_TIMESTAMP,
+    "updater"     varchar(64)          DEFAULT '',
+    "update_time" datetime    NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    "deleted"     bit(1)      NOT NULL DEFAULT FALSE,
+    PRIMARY KEY ("id")
+) COMMENT '支付商户信息';
+
+CREATE TABLE IF NOT EXISTS "pay_app"
+(
+    "id" number NOT NULL GENERATED BY DEFAULT AS IDENTITY,
+    "name"              varchar(64)   NOT NULL,
+    "status"            tinyint       NOT NULL,
+    "remark"            varchar(255)           DEFAULT NULL,
+    `pay_notify_url`    varchar(1024) NOT NULL,
+    `refund_notify_url` varchar(1024) NOT NULL,
+    `merchant_id`       bigint(20)    NOT NULL,
+    "creator"           varchar(64)            DEFAULT '',
+    "create_time"       datetime      NOT NULL DEFAULT CURRENT_TIMESTAMP,
+    "updater"           varchar(64)            DEFAULT '',
+    "update_time"       datetime      NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    "deleted"           bit(1)        NOT NULL DEFAULT FALSE,
+    PRIMARY KEY ("id")
+) COMMENT = '支付应用信息';
+
+CREATE TABLE IF NOT EXISTS "pay_channel"
+(
+    "id" number NOT NULL GENERATED BY DEFAULT AS IDENTITY,
+    "code"        varchar(32)    NOT NULL,
+    "status"      tinyint(4)     NOT NULL,
+    "remark"      varchar(255)            DEFAULT NULL,
+    "fee_rate"    double         NOT NULL DEFAULT 0,
+    "merchant_id" bigint(20)     NOT NULL,
+    "app_id"      bigint(20)     NOT NULL,
+    "config"      varchar(10240) NOT NULL,
+    "creator"     varchar(64)    NULL     DEFAULT '',
+    "create_time" datetime       NOT NULL DEFAULT CURRENT_TIMESTAMP,
+    "updater"     varchar(64)    NULL     DEFAULT '',
+    "update_time" datetime       NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    "deleted"     bit(1)         NOT NULL DEFAULT FALSE,
+    PRIMARY KEY ("id")
+) COMMENT = '支付渠道';
+
+CREATE TABLE IF NOT EXISTS `pay_order`
+(
+    "id" number NOT NULL GENERATED BY DEFAULT AS IDENTITY,
+    `merchant_id`          bigint(20)    NOT NULL,
+    `app_id`               bigint(20)    NOT NULL,
+    `channel_id`           bigint(20)             DEFAULT NULL,
+    `channel_code`         varchar(32)            DEFAULT NULL,
+    `merchant_order_id`    varchar(64)   NOT NULL,
+    `subject`              varchar(32)   NOT NULL,
+    `body`                 varchar(128)  NOT NULL,
+    `notify_url`           varchar(1024) NOT NULL,
+    `notify_status`        tinyint(4)    NOT NULL,
+    `amount`               bigint(20)    NOT NULL,
+    `channel_fee_rate`     double                 DEFAULT 0,
+    `channel_fee_amount`   bigint(20)             DEFAULT 0,
+    `status`               tinyint(4)    NOT NULL,
+    `user_ip`              varchar(50)   NOT NULL,
+    `expire_time`          datetime(0)   NOT NULL DEFAULT CURRENT_TIMESTAMP,
+    `success_time`         datetime(0)            DEFAULT CURRENT_TIMESTAMP,
+    `notify_time`          datetime(0)            DEFAULT CURRENT_TIMESTAMP,
+    `success_extension_id` bigint(20)             DEFAULT NULL COMMENT '支付成功的订单拓展单编号',
+    `refund_status`        tinyint(4)    NOT NULL,
+    `refund_times`         tinyint(4)    NOT NULL,
+    `refund_amount`        bigint(20)    NOT NULL,
+    `channel_user_id`      varchar(255)           DEFAULT NULL,
+    `channel_order_no`     varchar(64)            DEFAULT NULL,
+    `creator`              varchar(64)            DEFAULT '',
+    `create_time`          datetime(0)   NOT NULL DEFAULT CURRENT_TIMESTAMP,
+    `updater`              varchar(64)            DEFAULT '',
+    `update_time`          datetime(0)   NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    `deleted`              bit(1)        NOT NULL DEFAULT FALSE,
+    PRIMARY KEY ("id")
+) COMMENT = '支付订单';
+
+
+
+CREATE TABLE IF NOT EXISTS `pay_refund`
+(
+    "id" number NOT NULL GENERATED BY DEFAULT AS IDENTITY,
+    `req_no`             varchar(64)   NOT NULL,
+    `merchant_id`        bigint(20)    NOT NULL,
+    `app_id`             bigint(20)    NOT NULL,
+    `channel_id`         bigint(20)    NOT NULL,
+    `channel_code`       varchar(32)   NOT NULL,
+    `order_id`           bigint(20)    NOT NULL,
+    `trade_no`           varchar(64)   NOT NULL,
+    `merchant_order_id`  varchar(64)   NOT NULL,
+    `merchant_refund_no` varchar(64)   NOT NULL,
+    `notify_url`         varchar(1024) NOT NULL,
+    `notify_status`      tinyint(4)    NOT NULL,
+    `status`             tinyint(4)    NOT NULL,
+    `type`               tinyint(4)    NOT NULL,
+    `pay_amount`         bigint(20)    NOT NULL,
+    `refund_amount`      bigint(20)    NOT NULL,
+    `reason`             varchar(256)  NOT NULL,
+    `user_ip`            varchar(50)   NULL     DEFAULT NULL,
+    `channel_order_no`   varchar(64)   NOT NULL,
+    `channel_refund_no`  varchar(64)   NULL     DEFAULT NULL,
+    `channel_error_code` varchar(128)  NULL     DEFAULT NULL,
+    `channel_error_msg`  varchar(256)  NULL     DEFAULT NULL,
+    `channel_extras`     varchar(1024) NULL     DEFAULT NULL,
+    `expire_time`        datetime(0)   NULL     DEFAULT NULL,
+    `success_time`       datetime(0)   NULL     DEFAULT NULL,
+    `notify_time`        datetime(0)   NULL     DEFAULT NULL,
+    `creator`            varchar(64)   NULL     DEFAULT '',
+    `create_time`        datetime(0)   NOT NULL DEFAULT CURRENT_TIMESTAMP,
+    `updater`            varchar(64)   NULL     DEFAULT '',
+    `update_time`        datetime(0)   NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    `deleted`            bit(1)        NOT NULL DEFAULT FALSE,
+    PRIMARY KEY ("id")
+) COMMENT = '退款订单';
+
+
+
 CREATE TABLE IF NOT EXISTS "bpm_form" (
     "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
     "name" varchar(63) NOT NULL,

+ 1 - 1
yudao-admin-ui/package.json

@@ -42,7 +42,7 @@
     "clipboard": "2.0.6",
     "core-js": "3.19.1",
     "echarts": "4.9.0",
-    "element-ui": "2.15.0",
+    "element-ui": "^2.15.6",
     "file-saver": "2.0.4",
     "fuse.js": "6.4.3",
     "highlight.js": "9.18.5",

+ 78 - 0
yudao-admin-ui/src/api/pay/app.js

@@ -0,0 +1,78 @@
+import request from '@/utils/request'
+
+// 创建支付应用信息
+export function createApp(data) {
+  return request({
+    url: '/pay/app/create',
+    method: 'post',
+    data: data
+  })
+}
+
+// 更新支付应用信息
+export function updateApp(data) {
+  return request({
+    url: '/pay/app/update',
+    method: 'put',
+    data: data
+  })
+}
+
+// 支付应用信息状态修改
+export function changeAppStatus(id, status) {
+  const data = {
+    id,
+    status
+  }
+  return request({
+    url: '/pay/app/update-status',
+    method: 'put',
+    data: data
+  })
+}
+
+// 删除支付应用信息
+export function deleteApp(id) {
+  return request({
+    url: '/pay/app/delete?id=' + id,
+    method: 'delete'
+  })
+}
+
+// 获得支付应用信息
+export function getApp(id) {
+  return request({
+    url: '/pay/app/get?id=' + id,
+    method: 'get'
+  })
+}
+
+// 获得支付应用信息分页
+export function getAppPage(query) {
+  return request({
+    url: '/pay/app/page',
+    method: 'get',
+    params: query
+  })
+}
+
+// 导出支付应用信息 Excel
+export function exportAppExcel(query) {
+  return request({
+    url: '/pay/app/export-excel',
+    method: 'get',
+    params: query,
+    responseType: 'blob'
+  })
+}
+
+// 根据商ID称搜索应用列表
+export function getAppListByMerchantId(merchantId) {
+  return request({
+    url: '/pay/app/list-merchant-id',
+    params:{
+      merchantId:merchantId
+    },
+    method: 'get'
+  })
+}

+ 71 - 0
yudao-admin-ui/src/api/pay/channel.js

@@ -0,0 +1,71 @@
+import request from '@/utils/request'
+
+// 创建支付渠道
+export function createChannel(data) {
+  return request({
+    url: '/pay/channel/create',
+    method: 'post',
+    data: data
+  })
+}
+
+
+// 更新支付渠道
+export function updateChannel(data) {
+  return request({
+    url: '/pay/channel/update',
+    method: 'put',
+    data: data
+  })
+}
+
+// 删除支付渠道
+export function deleteChannel(id) {
+  return request({
+    url: '/pay/channel/delete?id=' + id,
+    method: 'delete'
+  })
+}
+
+// 获得支付渠道
+// export function getChannel(id) {
+//   return request({
+//     url: '/pay/channel/get?id=' + id,
+//     method: 'get'
+//   })
+// }
+
+
+
+// 获得支付渠道分页
+export function getChannelPage(query) {
+  return request({
+    url: '/pay/channel/page',
+    method: 'get',
+    params: query
+  })
+}
+
+// 导出支付渠道Excel
+export function exportChannelExcel(query) {
+  return request({
+    url: '/pay/channel/export-excel',
+    method: 'get',
+    params: query,
+    responseType: 'blob'
+  })
+}
+
+// 获得支付渠道
+export function getChannel(merchantId,appId,code) {
+  return request({
+    url: '/pay/channel/get-channel',
+    params:{
+      merchantId:merchantId,
+      appId:appId,
+      code:code
+    },
+    method: 'get'
+  })
+}
+

+ 77 - 0
yudao-admin-ui/src/api/pay/merchant.js

@@ -0,0 +1,77 @@
+import request from '@/utils/request'
+
+// 创建支付商户信息
+export function createMerchant(data) {
+  return request({
+    url: '/pay/merchant/create',
+    method: 'post',
+    data: data
+  })
+}
+
+// 更新支付商户信息
+export function updateMerchant(data) {
+  return request({
+    url: '/pay/merchant/update',
+    method: 'put',
+    data: data
+  })
+}
+
+// 支付商户状态修改
+export function changeMerchantStatus(id, status) {
+  const data = {
+    id,
+    status
+  }
+  return request({
+    url: '/pay/merchant/update-status',
+    method: 'put',
+    data: data
+  })
+}
+
+// 删除支付商户信息
+export function deleteMerchant(id) {
+  return request({
+    url: '/pay/merchant/delete?id=' + id,
+    method: 'delete'
+  })
+}
+
+// 获得支付商户信息
+export function getMerchant(id) {
+  return request({
+    url: '/pay/merchant/get?id=' + id,
+    method: 'get'
+  })
+}
+// 根据商户名称搜索商户列表
+export function getMerchantListByName(name) {
+  return request({
+    url: '/pay/merchant/list-by-name',
+    params:{
+      name:name
+    },
+    method: 'get'
+  })
+}
+
+// 获得支付商户信息分页
+export function getMerchantPage(query) {
+  return request({
+    url: '/pay/merchant/page',
+    method: 'get',
+    params: query
+  })
+}
+
+// 导出支付商户信息 Excel
+export function exportMerchantExcel(query) {
+  return request({
+    url: '/pay/merchant/export-excel',
+    method: 'get',
+    params: query,
+    responseType: 'blob'
+  })
+}

+ 54 - 0
yudao-admin-ui/src/api/pay/order.js

@@ -0,0 +1,54 @@
+import request from '@/utils/request'
+
+// 创建支付订单
+export function createOrder(data) {
+  return request({
+    url: '/pay/order/create',
+    method: 'post',
+    data: data
+  })
+}
+
+// 更新支付订单
+export function updateOrder(data) {
+  return request({
+    url: '/pay/order/update',
+    method: 'put',
+    data: data
+  })
+}
+
+// 删除支付订单
+export function deleteOrder(id) {
+  return request({
+    url: '/pay/order/delete?id=' + id,
+    method: 'delete'
+  })
+}
+
+// 获得支付订单
+export function getOrder(id) {
+  return request({
+    url: '/pay/order/get?id=' + id,
+    method: 'get'
+  })
+}
+
+// 获得支付订单分页
+export function getOrderPage(query) {
+  return request({
+    url: '/pay/order/page',
+    method: 'get',
+    params: query
+  })
+}
+
+// 导出支付订单 Excel
+export function exportOrderExcel(query) {
+  return request({
+    url: '/pay/order/export-excel',
+    method: 'get',
+    params: query,
+    responseType: 'blob'
+  })
+}

+ 54 - 0
yudao-admin-ui/src/api/pay/refund.js

@@ -0,0 +1,54 @@
+import request from '@/utils/request'
+
+// 创建退款订单
+export function createRefund(data) {
+  return request({
+    url: '/pay/refund/create',
+    method: 'post',
+    data: data
+  })
+}
+
+// 更新退款订单
+export function updateRefund(data) {
+  return request({
+    url: '/pay/refund/update',
+    method: 'put',
+    data: data
+  })
+}
+
+// 删除退款订单
+export function deleteRefund(id) {
+  return request({
+    url: '/pay/refund/delete?id=' + id,
+    method: 'delete'
+  })
+}
+
+// 获得退款订单
+export function getRefund(id) {
+  return request({
+    url: '/pay/refund/get?id=' + id,
+    method: 'get'
+  })
+}
+
+// 获得退款订单分页
+export function getRefundPage(query) {
+  return request({
+    url: '/pay/refund/page',
+    method: 'get',
+    params: query
+  })
+}
+
+// 导出退款订单 Excel
+export function exportRefundExcel(query) {
+  return request({
+    url: '/pay/refund/export-excel',
+    method: 'get',
+    params: query,
+    responseType: 'blob'
+  })
+}

File diff suppressed because it is too large
+ 1 - 0
yudao-admin-ui/src/assets/icons/svg/config.svg


+ 2 - 0
yudao-admin-ui/src/assets/icons/svg/merchant.svg

@@ -0,0 +1,2 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1635907181185" class="icon" viewBox="0 0 1184 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3259" xmlns:xlink="http://www.w3.org/1999/xlink" width="231.25" height="200"><defs><style type="text/css">@font-face { font-family: element-icons; src: url("chrome-extension://moombeodfomdpjnpocobemoiaemednkg/fonts/element-icons.woff") format("woff"), url("chrome-extension://moombeodfomdpjnpocobemoiaemednkg/fonts/element-icons.ttf ") format("truetype"); }
+</style></defs><path d="M1005.633722 616.006249c-5.311502-0.479955-10.431022-1.27988-15.518545-2.143799-1.087898-0.127988-2.271787-0.287973-3.359685-0.479955-2.399775-0.415961-4.671562-1.055901-7.007343-1.631847-9.951067-2.143799-19.678155-4.831547-28.989282-8.351217l0 133.235509-731.355435 0 0-133.235509c-9.375121 3.551667-19.006218 6.303409-29.053276 8.511202-2.335781 0.415961-4.607568 1.055901-6.943349 1.535856-1.087898 0.191982-2.175796 0.31997-3.359685 0.511952-5.11952 0.767928-10.271037 1.535856-15.518545 2.079805-5.983439 0.479955-12.094866 0.863919-18.270287 0.863919-12.47883 0-24.701684-1.215886-36.540574-3.327688l0 357.310502c0 29.309252 24.573696 53.11502 54.874855 53.11502l841.073149 0c30.237165 0 54.810861-23.773771 54.810861-53.11502l0-357.342499c-11.83889 2.143799-24.061744 3.327688-36.540574 3.327688-6.07943 0-12.254851-0.351967-18.23829-0.863919z" p-id="3260"></path><path d="M182.83086 109.749711l804.500578 0c30.301159 0 54.874855-24.573696 54.874855-54.874855s-24.573696-54.874855-54.874855-54.874855l-804.500578 0c-30.301159 0-54.874855 24.573696-54.874855 54.874855 0.031997 30.301159 24.605693 54.874855 54.874855 54.874855z" p-id="3261"></path><path d="M1067.387932 164.592569l-964.549573 0-102.838359 256.007999c0 80.728432 65.529857 146.258288 146.258288 146.258288s146.258288-65.529857 146.258288-146.258288c0 80.728432 65.529857 146.258288 146.258288 146.258288s146.258288-65.529857 146.258288-146.258288c0 80.728432 65.529857 146.258288 146.258288 146.258288s146.258288-65.529857 146.258288-146.258288c0 80.728432 65.529857 146.258288 146.258288 146.258288s146.258288-65.529857 146.258288-146.258288l-102.806362-256.007999z" p-id="3262"></path></svg>

File diff suppressed because it is too large
+ 1 - 0
yudao-admin-ui/src/assets/icons/svg/order.svg


+ 2 - 0
yudao-admin-ui/src/assets/icons/svg/pay.svg

@@ -0,0 +1,2 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1635906769564" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2420" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css">@font-face { font-family: element-icons; src: url("chrome-extension://moombeodfomdpjnpocobemoiaemednkg/fonts/element-icons.woff") format("woff"), url("chrome-extension://moombeodfomdpjnpocobemoiaemednkg/fonts/element-icons.ttf ") format("truetype"); }
+</style></defs><path d="M512 0C230.4 0 0 230.4 0 512s230.4 512 512 512 512-230.4 512-512S793.6 0 512 0z m0 921.6c-225.28 0-409.6-184.32-409.6-409.6s184.32-409.6 409.6-409.6 409.6 184.32 409.6 409.6-184.32 409.6-409.6 409.6z" p-id="2421"></path><path d="M665.6 537.6h-102.4v-51.2h102.4c25.6 0 51.2-25.6 51.2-51.2s-25.6-51.2-51.2-51.2h-30.72l20.48-20.48c20.48-20.48 20.48-51.2 0-71.68s-51.2-20.48-71.68 0L512 363.52 440.32 296.96c-20.48-20.48-51.2-20.48-71.68 0s-20.48 51.2 0 71.68l20.48 20.48H358.4c-25.6 0-51.2 25.6-51.2 51.2s25.6 51.2 51.2 51.2h102.4v51.2H358.4c-25.6 0-51.2 25.6-51.2 51.2s25.6 51.2 51.2 51.2h102.4v56.32c0 25.6 20.48 46.08 46.08 46.08h10.24c25.6 0 46.08-20.48 46.08-46.08v-56.32h102.4c25.6 0 51.2-25.6 51.2-51.2s-25.6-56.32-51.2-56.32z" p-id="2422"></path></svg>

+ 2 - 0
yudao-admin-ui/src/assets/icons/svg/percentSign.svg

@@ -0,0 +1,2 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1636341153732" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2282" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css">@font-face { font-family: element-icons; src: url("chrome-extension://moombeodfomdpjnpocobemoiaemednkg/fonts/element-icons.woff") format("woff"), url("chrome-extension://moombeodfomdpjnpocobemoiaemednkg/fonts/element-icons.ttf ") format("truetype"); }
+</style></defs><path d="M294.912 874.666667a21.333333 21.333333 0 0 1-17.194667-33.962667l469.333334-640a21.333333 21.333333 0 0 1 34.432 25.258667l-469.333334 640a21.248 21.248 0 0 1-17.237333 8.704zM358.912 533.333333a161.28 161.28 0 0 1-149.333333-170.666666 161.28 161.28 0 0 1 149.333333-170.666667 161.28 161.28 0 0 1 149.333333 170.666667 161.28 161.28 0 0 1-149.333333 170.666666z m0-298.666666a118.912 118.912 0 0 0-106.666667 128 118.912 118.912 0 0 0 106.666667 128 118.912 118.912 0 0 0 106.666667-128 118.912 118.912 0 0 0-106.666667-128zM700.245333 874.666667a161.28 161.28 0 0 1-149.333333-170.666667 161.28 161.28 0 0 1 149.333333-170.666667 161.28 161.28 0 0 1 149.333334 170.666667 161.28 161.28 0 0 1-149.333334 170.666667z m0-298.666667a118.912 118.912 0 0 0-106.666666 128 118.912 118.912 0 0 0 106.666666 128 118.912 118.912 0 0 0 106.666667-128 118.912 118.912 0 0 0-106.666667-128z" fill="#757575" p-id="2283"></path></svg>

+ 136 - 1
yudao-admin-ui/src/utils/constants.js

@@ -16,7 +16,7 @@ export const SysCommonStatusEnum = {
  * 菜单的类型枚举
  */
 export const SysMenuTypeEnum = {
-  DIR : 1, // 目录
+  DIR: 1, // 目录
   MENU: 2, // 菜单
   BUTTON: 3 // 按钮
 }
@@ -90,3 +90,138 @@ export const SysUserSocialTypeEnum = {
     img: "https://cdn.jsdelivr.net/gh/justauth/justauth-oauth-logo@1.11/wechat_enterprise.png",
   }
 }
+
+/**
+ * 支付渠道枚举
+ */
+export const PayChannelEnum = {
+  WX_PUB: {
+    "code": "wx_pub",
+    "name": "微信 JSAPI 支付",
+  },
+  WX_LITE: {
+    "code": "wx_lite",
+    "name": "微信小程序支付"
+  },
+  WX_APP: {
+    "code": "wx_app",
+    "name": "微信 APP 支付"
+  },
+  ALIPAY_PC: {
+    "code": "alipay_pc",
+    "name": "支付宝 PC 网站支付"
+  },
+  ALIPAY_WAP: {
+    "code": "alipay_wap",
+    "name": "支付宝 WAP 网站支付"
+  },
+  ALIPAY_APP: {
+    "code": "alipay_app",
+    "name": "支付宝 APP 支付"
+  },
+  ALIPAY_QR: {
+    "code": "alipay_qr",
+    "name": "支付宝扫码支付"
+  },
+}
+
+/**
+ * 支付类型枚举
+ */
+export const PayType = {
+  WECHAT: "WECHAT",
+  ALIPAY: "ALIPAY"
+}
+
+/**
+ * 支付订单状态枚举
+ */
+export const payOrderStatusEnum = {
+  WAITING: {
+    status: 0,
+    name: '未支付'
+  },
+  SUCCESS: {
+    status: 10,
+    name: '已支付'
+  },
+  CLOSED: {
+    status: 20,
+    name: '未支付'
+  }
+}
+
+/**
+ * 支付订单回调状态枚举
+ */
+export const payOrderNotifyStatusEnum = {
+  NO: {
+    status: 0,
+    name: '未通知'
+  },
+  SUCCESS: {
+    status: 10,
+    name: '通知成功'
+  },
+  FAILURE: {
+    status: 20,
+    name: '通知失败'
+  }
+}
+
+/**
+ * 支付订单退款状态枚举
+ */
+export const payOrderRefundStatusEnum = {
+  NO: {
+    status: 0,
+    name: '未退款'
+  },
+  SOME: {
+    status: 10,
+    name: '部分退款'
+  },
+  ALL: {
+    status: 20,
+    name: '全部退款'
+  }
+}
+
+/**
+ * 支付退款订单状态枚举
+ */
+export const payRefundStatusEnum = {
+  CREATE:{
+    status:0,
+    name: '退款订单生成'
+  },
+  SUCCESS:{
+    status:1,
+    name: '退款成功'
+  },
+  FAILURE:{
+    status:2,
+    name: '退款失败'
+  },
+  PROCESSING_NOTIFY:{
+    status:3,
+    name: '退款中,渠道通知结果'
+  },
+  PROCESSING_QUERY:{
+    status:4,
+    name: '退款中,系统查询结果'
+  },
+  UNKNOWN_RETRY:{
+    status:5,
+    name: '状态未知,请重试'
+  },
+  UNKNOWN_QUERY:{
+    status:6,
+    name: '状态未知,系统查询结果'
+  },
+  CLOSE:{
+    status:99,
+    name: '退款关闭'
+  }
+}
+

+ 23 - 2
yudao-admin-ui/src/utils/dict.js

@@ -41,8 +41,29 @@ export const DICT_TYPE = {
   BPM_PROCESS_INSTANCE_STATUS: 'bpm_process_instance_status',
   BPM_PROCESS_INSTANCE_RESULT: 'bpm_process_instance_result',
   BPM_TASK_ASSIGN_SCRIPT: 'bpm_task_assign_script',
-  OA_LEAVE_STATUS: 'flow_status', // todo 芋艿:可以删除
-  BPM_OA_LEAVE_TYPE: 'bpm_oa_leave_type'
+  BPM_OA_LEAVE_TYPE: 'bpm_oa_leave_type',
+
+  // pay
+  // 微信渠道版本
+  PAY_CHANNEL_WECHAT_VERSION:'pay_channel_wechat_version',
+  // 支付渠道支付宝算法类型
+  PAY_CHANNEL_ALIPAY_SIGN_TYPE:'pay_channel_alipay_sign_type',
+  // 支付宝公钥类型
+  PAY_CHANNEL_ALIPAY_MODE:'pay_channel_alipay_mode',
+  // 支付宝网关地址
+  PAY_CHANNEL_ALIPAY_SERVER_TYPE:'pay_channel_alipay_server_type',
+  // 支付渠道编码类型
+  PAY_CHANNEL_CODE_TYPE: 'pay_channel_code_type',
+  // 商户支付订单回调状态
+  PAY_ORDER_NOTIFY_STATUS: 'pay_order_notify_status',
+  // 商户支付订单状态
+  PAY_ORDER_STATUS: 'pay_order_status',
+  // 商户支付订单退款状态
+  PAY_ORDER_REFUND_STATUS: 'pay_order_refund_status',
+  // 退款订单状态
+  PAY_REFUND_ORDER_STATUS: 'pay_refund_order_status',
+  // 退款订单类别
+  PAY_REFUND_ORDER_TYPE: 'pay_refund_order_type',
 }
 
 /**

+ 115 - 92
yudao-admin-ui/src/utils/ruoyi.js

@@ -7,66 +7,68 @@ const baseURL = process.env.VUE_APP_BASE_API
 
 // 日期格式化
 export function parseTime(time, pattern) {
-	if (arguments.length === 0 || !time) {
-		return null
-	}
-	const format = pattern || '{y}-{m}-{d} {h}:{i}:{s}'
-	let date
-	if (typeof time === 'object') {
-		date = time
-	} else {
-		if ((typeof time === 'string') && (/^[0-9]+$/.test(time))) {
-			time = parseInt(time)
-		} else if (typeof time === 'string') {
-			time = time.replace(new RegExp(/-/gm), '/');
-		}
-		if ((typeof time === 'number') && (time.toString().length === 10)) {
-			time = time * 1000
-		}
-		date = new Date(time)
-	}
-	const formatObj = {
-		y: date.getFullYear(),
-		m: date.getMonth() + 1,
-		d: date.getDate(),
-		h: date.getHours(),
-		i: date.getMinutes(),
-		s: date.getSeconds(),
-		a: date.getDay()
-	}
-	const time_str = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => {
-		let value = formatObj[key]
-		// Note: getDay() returns 0 on Sunday
-		if (key === 'a') { return ['日', '一', '二', '三', '四', '五', '六'][value] }
-		if (result.length > 0 && value < 10) {
-			value = '0' + value
-		}
-		return value || 0
-	})
-	return time_str
+  if (arguments.length === 0 || !time) {
+    return null
+  }
+  const format = pattern || '{y}-{m}-{d} {h}:{i}:{s}'
+  let date
+  if (typeof time === 'object') {
+    date = time
+  } else {
+    if ((typeof time === 'string') && (/^[0-9]+$/.test(time))) {
+      time = parseInt(time)
+    } else if (typeof time === 'string') {
+      time = time.replace(new RegExp(/-/gm), '/');
+    }
+    if ((typeof time === 'number') && (time.toString().length === 10)) {
+      time = time * 1000
+    }
+    date = new Date(time)
+  }
+  const formatObj = {
+    y: date.getFullYear(),
+    m: date.getMonth() + 1,
+    d: date.getDate(),
+    h: date.getHours(),
+    i: date.getMinutes(),
+    s: date.getSeconds(),
+    a: date.getDay()
+  }
+  const time_str = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => {
+    let value = formatObj[key]
+    // Note: getDay() returns 0 on Sunday
+    if (key === 'a') {
+      return ['日', '一', '二', '三', '四', '五', '六'][value]
+    }
+    if (result.length > 0 && value < 10) {
+      value = '0' + value
+    }
+    return value || 0
+  })
+  return time_str
 }
 
 // 表单重置
 export function resetForm(refName) {
-	if (this.$refs[refName]) {
-		this.$refs[refName].resetFields();
-	}
+  if (this.$refs[refName]) {
+    this.$refs[refName].resetFields();
+  }
 }
 
 // 添加日期范围
 export function addDateRange(params, dateRange, propName) {
   const search = params;
   search.params = {};
-	if (null != dateRange && '' !== dateRange) {
-		if (typeof(propName) === "undefined") {
-			search["beginTime"] = dateRange[0];
-			search["endTime"] = dateRange[1];
-		} else {
-			search["begin" + propName] = dateRange[0];
-			search["end" + propName] = dateRange[1];
-		}
-	}
-	return search;
+  if (null != dateRange && '' !== dateRange) {
+    if (typeof (propName) === "undefined") {
+      search["beginTime"] = dateRange[0];
+      search["endTime"] = dateRange[1];
+    } else {
+      search["begin" + propName] = dateRange[0];
+      search["end" + propName] = dateRange[1];
+    }
+  }
+  return search;
 }
 
 /**
@@ -98,21 +100,21 @@ export function addBeginAndEndTime(params, dateRange, propName) {
   return params;
 }
 
-// 回显数据字典
+// 回显数据字典 原若依所保留,请使用 dict.js 中的新方法
 export function selectDictLabel(datas, value) {
-	var actions = [];
-	Object.keys(datas).some((key) => {
-		if (datas[key].dictValue == ('' + value)) {
-			actions.push(datas[key].dictLabel);
-			return true;
-		}
-	})
-	return actions.join('');
+  var actions = [];
+  Object.keys(datas).some((key) => {
+    if (datas[key].dictValue == ('' + value)) {
+      actions.push(datas[key].dictLabel);
+      return true;
+    }
+  })
+  return actions.join('');
 }
 
 // 通用下载方法
 export function download(fileName) {
-	window.location.href = baseURL + "/common/download?fileName=" + encodeURI(fileName) + "&delete=" + true;
+  window.location.href = baseURL + "/common/download?fileName=" + encodeURI(fileName) + "&delete=" + true;
 }
 
 // 下载 Excel 方法
@@ -147,7 +149,7 @@ function download0(data, fileName, mineType) {
   window.URL = window.URL || window.webkitURL;
   let href = URL.createObjectURL(blob);
   let downA = document.createElement("a");
-  downA.href =  href;
+  downA.href = href;
   downA.download = fileName;
   downA.click();
   // 销毁超连接
@@ -156,24 +158,24 @@ function download0(data, fileName, mineType) {
 
 // 字符串格式化(%s )
 export function sprintf(str) {
-	var args = arguments, flag = true, i = 1;
-	str = str.replace(/%s/g, function () {
-		var arg = args[i++];
-		if (typeof arg === 'undefined') {
-			flag = false;
-			return '';
-		}
-		return arg;
-	});
-	return flag ? str : '';
+  var args = arguments, flag = true, i = 1;
+  str = str.replace(/%s/g, function () {
+    var arg = args[i++];
+    if (typeof arg === 'undefined') {
+      flag = false;
+      return '';
+    }
+    return arg;
+  });
+  return flag ? str : '';
 }
 
 // 转换字符串,undefined,null等转化为""
 export function praseStrEmpty(str) {
-	if (!str || str == "undefined" || str == "null") {
-		return "";
-	}
-	return str;
+  if (!str || str == "undefined" || str == "null") {
+    return "";
+  }
+  return str;
 }
 
 /**
@@ -185,21 +187,42 @@ export function praseStrEmpty(str) {
  * @param {*} rootId 根Id 默认 0
  */
 export function handleTree(data, id, parentId, children, rootId) {
-	id = id || 'id'
-	parentId = parentId || 'parentId'
-	children = children || 'children'
-	rootId = rootId || Math.min.apply(Math, data.map(item => { return item[parentId] })) || 0
-	//对源数据深度克隆
-	const cloneData = JSON.parse(JSON.stringify(data))
-	//循环所有项
-	const treeData = cloneData.filter(father => {
-		let branchArr = cloneData.filter(child => {
-			//返回每一项的子级数组
-			return father[id] === child[parentId]
-		});
-		branchArr.length > 0 ? father.children = branchArr : '';
-		//返回第一层
-		return father[parentId] === rootId;
-	});
-	return treeData !== '' ? treeData : data;
+  id = id || 'id'
+  parentId = parentId || 'parentId'
+  children = children || 'children'
+  rootId = rootId || Math.min.apply(Math, data.map(item => {
+    return item[parentId]
+  })) || 0
+  //对源数据深度克隆
+  const cloneData = JSON.parse(JSON.stringify(data))
+  //循环所有项
+  const treeData = cloneData.filter(father => {
+    let branchArr = cloneData.filter(child => {
+      //返回每一项的子级数组
+      return father[id] === child[parentId]
+    });
+    branchArr.length > 0 ? father.children = branchArr : '';
+    //返回第一层
+    return father[parentId] === rootId;
+  });
+  return treeData !== '' ? treeData : data;
+}
+
+/**
+ * 获取当前时间
+ * @param timeStr 时分秒 字符串 格式为 xx:xx:xx
+ */
+export function getNowDateTime(timeStr) {
+  let now = new Date();
+  let year = now.getFullYear(); //得到年份
+  let month = (now.getMonth() + 1).toString().padStart(2, "0"); //得到月份
+  let day = now.getDate().toString().padStart(2, "0"); //得到日期
+
+  if (timeStr != null) {
+    return `${year}-${month}-${day} ${timeStr}`;
+  }
+  let hours = now.getHours().toString().padStart(2, "0") // 得到小时;
+  let minutes = now.getMinutes().toString().padStart(2, "0") // 得到分钟;
+  let seconds = now.getSeconds().toString().padStart(2, "0") // 得到秒;
+  return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
 }

+ 358 - 0
yudao-admin-ui/src/views/pay/app/components/aliPayChannelForm.vue

@@ -0,0 +1,358 @@
+<template>
+  <div>
+    <el-dialog :visible.sync="transferParam.aliPayOpen" :title="title" @closed="close" append-to-body width="800px">
+      <el-form ref="aliPayForm" :model="form" :rules="rules" size="medium" label-width="100px"
+               v-loading="transferParam.loading">
+        <el-form-item label-width="180px" label="渠道费率" prop="feeRate">
+          <el-input v-model="form.feeRate" placeholder="请输入渠道费率" clearable :style="{width: '100%'}">
+            <template slot="append">%</template>
+          </el-input>
+        </el-form-item>
+        <el-form-item label-width="180px" label="开放平台APPID" prop="aliPayConfig.appId">
+          <el-input v-model="form.aliPayConfig.appId" placeholder="请输入开放平台APPID" clearable :style="{width: '100%'}">
+          </el-input>
+        </el-form-item>
+        <el-form-item label-width="180px" label="渠道状态" prop="status">
+          <el-radio-group v-model="form.status" size="medium">
+            <el-radio v-for="dict in statusDictDatas" :key="parseInt(dict.value)" :label="parseInt(dict.value)">
+              {{ dict.label }}
+            </el-radio>
+          </el-radio-group>
+        </el-form-item>
+        <el-form-item label-width="180px" label="网关地址" prop="aliPayConfig.serverUrl">
+          <el-radio-group v-model="form.aliPayConfig.serverUrl" size="medium">
+            <el-radio v-for="dict in aliPayServerDatas" :key="dict.value" :label="dict.value">
+              {{ dict.label }}
+            </el-radio>
+          </el-radio-group>
+        </el-form-item>
+        <el-form-item label-width="180px" label="算法类型" prop="aliPayConfig.signType">
+          <el-radio-group v-model="form.aliPayConfig.signType" size="medium">
+            <el-radio v-for="dict in aliPaySignTypeDatas" :key="dict.value" :label="dict.value">
+              {{ dict.label }}
+            </el-radio>
+          </el-radio-group>
+        </el-form-item>
+        <el-form-item label-width="180px" label="公钥类型" prop="aliPayConfig.mode">
+          <el-radio-group v-model="form.aliPayConfig.mode" size="medium">
+            <el-radio v-for="dict in aliPayModeDatas" :key="parseInt(dict.value)" :label="parseInt(dict.value)">
+              {{ dict.label }}
+            </el-radio>
+          </el-radio-group>
+        </el-form-item>
+        <div v-if="form.aliPayConfig.mode === 1">
+          <el-form-item label-width="180px" label="商户私钥" prop="aliPayConfig.privateKey">
+            <el-input type="textarea" :autosize="{minRows: 8, maxRows: 8}" v-model="form.aliPayConfig.privateKey"
+                      placeholder="请输入商户私钥" clearable :style="{width: '100%'}">
+            </el-input>
+          </el-form-item>
+          <el-form-item label-width="180px" label="支付宝公钥字符串" prop="aliPayConfig.alipayPublicKey">
+            <el-input
+              type="textarea"
+              :autosize="{minRows: 8, maxRows: 8}"
+              v-model="form.aliPayConfig.alipayPublicKey"
+              placeholder="请输入支付宝公钥字符串" clearable
+              :style="{width: '100%'}">
+            </el-input>
+          </el-form-item>
+        </div>
+        <div v-if="form.aliPayConfig.mode === 2">
+          <el-form-item label-width="180px" label="商户公钥应用证书" prop="aliPayConfig.appCertContent">
+            <el-input v-model="form.aliPayConfig.appCertContent" type="textarea"
+                      placeholder="请上传商户公钥应用证书"
+                      readonly :autosize="{minRows: 8, maxRows: 8}" :style="{width: '100%'}"></el-input>
+          </el-form-item>
+          <el-form-item label-width="180px" label="">
+            <el-upload
+              action=""
+              ref="privateKeyContentFile"
+              :limit="1"
+              :accept="fileAccept"
+              :http-request="appCertUpload"
+              :before-upload="fileBeforeUpload">
+              <el-button size="small" type="primary" icon="el-icon-upload">点击上传</el-button>
+            </el-upload>
+          </el-form-item>
+          <el-form-item label-width="180px" label="支付宝公钥证书" prop="aliPayConfig.alipayPublicCertContent">
+            <el-input v-model="form.aliPayConfig.alipayPublicCertContent" type="textarea"
+                      placeholder="请上传支付宝公钥证书"
+                      readonly :autosize="{minRows: 8, maxRows: 8}" :style="{width: '100%'}"></el-input>
+          </el-form-item>
+          <el-form-item label-width="180px" label="">
+            <el-upload
+              ref="privateCertContentFile"
+              action=""
+              :limit="1"
+              :accept="fileAccept"
+              :before-upload="fileBeforeUpload"
+              :http-request="alipayPublicCertUpload">
+              <el-button size="small" type="primary" icon="el-icon-upload">点击上传</el-button>
+            </el-upload>
+          </el-form-item>
+          <el-form-item label-width="180px" label="根证书" prop="aliPayConfig.rootCertContent">
+            <el-input
+              v-model="form.aliPayConfig.rootCertContent"
+              type="textarea"
+              placeholder="请上传根证书"
+              readonly :autosize="{minRows: 8, maxRows: 8}"
+              :style="{width: '100%'}">
+            </el-input>
+          </el-form-item>
+          <el-form-item label-width="180px" label="">
+            <el-upload
+              ref="privateCertContentFile"
+              :limit="1"
+              :accept="fileAccept"
+              action=""
+              :before-upload="fileBeforeUpload"
+              :http-request="rootCertUpload">
+              <el-button size="small" type="primary" icon="el-icon-upload">点击上传</el-button>
+            </el-upload>
+          </el-form-item>
+        </div>
+        <el-form-item label-width="180px" label="备注" prop="remark">
+          <el-input v-model="form.remark" :style="{width: '100%'}"></el-input>
+        </el-form-item>
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button @click="close">取消</el-button>
+        <el-button type="primary" @click="handleConfirm">确定</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+<script>
+import {DICT_TYPE, getDictDatas} from "@/utils/dict";
+import {createChannel, getChannel, updateChannel} from "@/api/pay/channel";
+
+const defaultForm = {
+  code: '',
+  status: null,
+  remark: '',
+  feeRate: null,
+  appId: '',
+  merchantId: null,
+  aliPayConfig: {
+    appId: '',
+    serverUrl: null,
+    signType: '',
+    mode: null,
+    privateKey: '',
+    alipayPublicKey: '',
+    appCertContent: '',
+    alipayPublicCertContent: '',
+    rootCertContent: ''
+  }
+};
+
+export default {
+  name: "aliPayChannelForm",
+  components: {},
+  props: {
+    // 传输的参数
+    transferParam: {
+      // 加载动画
+      "loading": false,
+      // 是否修改
+      "edit": false,
+      // 是否显示
+      "aliPayOpen": false,
+      // 应用ID
+      "appId": null,
+      // 渠道编码
+      "payCode": null,
+      // 商户对象
+      "payMerchant": {
+        // 编号
+        "id": null,
+        // 名称
+        "name": null
+      },
+    }
+  },
+  data() {
+    return {
+      title:'',
+      form: JSON.parse(JSON.stringify(defaultForm)),
+      rules: {
+        feeRate: [{
+          required: true,
+          message: '请输入渠道费率',
+          trigger: 'blur'
+        }],
+        'aliPayConfig.appId': [{
+          required: true,
+          message: '请输入开放平台上创建的应用的 ID',
+          trigger: 'blur'
+        }],
+        status: [{
+          required: true,
+          message: '渠道状态不能为空',
+          trigger: 'blur'
+        }],
+        'aliPayConfig.serverUrl': [{
+          required: true,
+          message: '请传入网关地址',
+          trigger: 'blur'
+        }],
+        'aliPayConfig.signType': [{
+          required: true,
+          message: '请传入签名算法类型',
+          trigger: 'blur'
+        }],
+        'aliPayConfig.mode': [{
+          required: true,
+          message: '公钥类型不能为空',
+          trigger: 'blur'
+        }],
+        'aliPayConfig.privateKey': [{
+          required: true,
+          message: '请输入商户私钥',
+          trigger: 'blur'
+        }],
+        'aliPayConfig.alipayPublicKey': [{
+          required: true,
+          message: '请输入支付宝公钥字符串',
+          trigger: 'blur'
+        }],
+        'aliPayConfig.appCertContent': [{
+          required: true,
+          message: '请上传商户公钥应用证书',
+          trigger: 'blur'
+        }],
+        'aliPayConfig.alipayPublicCertContent': [{
+          required: true,
+          message: '请上传支付宝公钥证书',
+          trigger: 'blur'
+        }],
+        'aliPayConfig.rootCertContent': [{
+          required: true,
+          message: '请上传指定根证书',
+          trigger: 'blur'
+        }],
+      },
+      fileAccept: ".crt",
+      // 渠道状态 数据字典
+      statusDictDatas: getDictDatas(DICT_TYPE.SYS_COMMON_STATUS),
+      // 支付宝加密方式
+      aliPaySignTypeDatas: getDictDatas(DICT_TYPE.PAY_CHANNEL_ALIPAY_SIGN_TYPE),
+      // 版本状态 数据字典
+      aliPayModeDatas: getDictDatas(DICT_TYPE.PAY_CHANNEL_ALIPAY_MODE),
+      // 支付宝网关地址
+      aliPayServerDatas: getDictDatas(DICT_TYPE.PAY_CHANNEL_ALIPAY_SERVER_TYPE),
+    }
+  },
+  watch: {
+    transferParam: {
+      deep: true,  // 深度监听
+      handler(newVal) {
+        if (newVal.aliPayOpen) {
+          this.form.code = newVal.payCode;
+          this.form.appId = newVal.appId;
+          this.form.merchantId = newVal.payMerchant.id;
+          // 只有在初次进来为编辑 并且为加载中的时候才回去请求数据
+          if (newVal.edit === true && newVal.loading) {
+            this.title = "编辑支付渠道";
+            this.init();
+          } else {
+            this.title = "创建支付渠道";
+          }
+        }
+      }
+    }
+  },
+
+  methods: {
+    init() {
+      getChannel(this.transferParam.payMerchant.id, this.transferParam.appId, this.transferParam.payCode)
+        .then(response => {
+          this.form.id = response.data.id;
+          this.form.feeRate = response.data.feeRate;
+          this.form.status = response.data.status;
+          this.form.remark = response.data.remark;
+
+          let config = JSON.parse(response.data.config);
+          this.form.aliPayConfig.appId = config.appId;
+          this.form.aliPayConfig.serverUrl = config.serverUrl;
+          this.form.aliPayConfig.signType = config.signType;
+          this.form.aliPayConfig.mode = config.mode;
+          this.form.aliPayConfig.privateKey = config.privateKey;
+          this.form.aliPayConfig.alipayPublicKey = config.alipayPublicKey;
+          this.form.aliPayConfig.appCertContent = config.appCertContent;
+          this.form.aliPayConfig.alipayPublicCertContent = config.alipayPublicCertContent;
+          this.form.aliPayConfig.rootCertContent = config.rootCertContent;
+          this.transferParam.loading = false;
+        })
+    },
+    close() {
+      this.transferParam.aliPayOpen = false;
+      this.form = JSON.parse(JSON.stringify(defaultForm));
+    },
+    handleConfirm() {
+      this.$refs['aliPayForm'].validate(valid => {
+        if (!valid) {
+          return
+        }
+        let data = this.form;
+        data.config = JSON.stringify(this.form.aliPayConfig);
+        if (this.transferParam.edit) {
+          updateChannel(data).then(response => {
+            if (response.code === 0) {
+              this.msgSuccess("修改成功");
+              this.close();
+            }
+
+          })
+        } else {
+
+          createChannel(data).then(response => {
+            if (response.code === 0) {
+              this.msgSuccess("新增成功");
+              this.$parent.refreshTable();
+              this.close();
+            }
+          });
+        }
+      });
+    },
+    fileBeforeUpload(file) {
+      let format = '.' + file.name.split(".")[1];
+      if (format !== this.fileAccept) {
+        this.$message.error('请上传指定格式"' + this.fileAccept + '"文件');
+        return false;
+      }
+      let isRightSize = file.size / 1024 / 1024 < 2
+      if (!isRightSize) {
+        this.$message.error('文件大小超过 2MB')
+      }
+      return isRightSize
+    },
+    appCertUpload(event) {
+      const readFile = new FileReader()
+      readFile.onload = (e) => {
+        this.form.aliPayConfig.appCertContent = e.target.result
+      }
+      readFile.readAsText(event.file);
+    },
+    alipayPublicCertUpload(event) {
+      const readFile = new FileReader()
+      readFile.onload = (e) => {
+        this.form.aliPayConfig.alipayPublicCertContent = e.target.result
+      }
+      readFile.readAsText(event.file);
+    },
+    rootCertUpload(event) {
+      const readFile = new FileReader()
+      readFile.onload = (e) => {
+        this.form.aliPayConfig.rootCertContent = e.target.result
+      }
+      readFile.readAsText(event.file);
+    },
+
+  }
+}
+
+</script>
+<style scoped>
+
+</style>

+ 299 - 0
yudao-admin-ui/src/views/pay/app/components/wechatChannelForm.vue

@@ -0,0 +1,299 @@
+<template>
+  <div>
+    <el-dialog :visible.sync="transferParam.wechatOpen" :title="title" @close="close" append-to-body width="800px">
+      <el-form ref="wechatJsApiForm" :model="form" :rules="rules" size="medium" label-width="100px"
+               v-loading="transferParam.loading">
+        <el-form-item label-width="180px" label="渠道费率" prop="feeRate">
+          <el-input v-model="form.feeRate" placeholder="请输入渠道费率" clearable :style="{width: '100%'}">
+            <template slot="append">%</template>
+          </el-input>
+        </el-form-item>
+        <el-form-item label-width="180px" label="公众号APPID" prop="weChatConfig.appId">
+          <el-input v-model="form.weChatConfig.appId" placeholder="请输入公众号APPID" clearable :style="{width: '100%'}">
+          </el-input>
+        </el-form-item>
+        <el-form-item label-width="180px" label="商户号" prop="weChatConfig.mchId">
+          <el-input v-model="form.weChatConfig.mchId" :style="{width: '100%'}"></el-input>
+        </el-form-item>
+        <el-form-item label-width="180px" label="渠道状态" prop="status">
+          <el-radio-group v-model="form.status" size="medium">
+            <el-radio v-for="dict in statusDictDatas" :key="parseInt(dict.value)" :label="parseInt(dict.value)">
+              {{ dict.label }}
+            </el-radio>
+          </el-radio-group>
+        </el-form-item>
+        <el-form-item label-width="180px" label="API 版本" prop="weChatConfig.apiVersion">
+          <el-radio-group v-model="form.weChatConfig.apiVersion" size="medium">
+            <el-radio v-for="dict in versionDictDatas" :key="dict.value" :label="dict.value">
+              {{ dict.label }}
+            </el-radio>
+          </el-radio-group>
+        </el-form-item>
+        <el-form-item label-width="180px" label="商户秘钥" prop="weChatConfig.mchKey"
+                      v-if="form.weChatConfig.apiVersion === 'v2'">
+          <el-input v-model="form.weChatConfig.mchKey" placeholder="请输入商户秘钥" clearable
+                    :style="{width: '100%'}" type="textarea" :autosize="{minRows: 8, maxRows: 8}"></el-input>
+        </el-form-item>
+        <div v-if="form.weChatConfig.apiVersion === 'v3'">
+          <el-form-item label-width="180px" label="API V3秘钥" prop="weChatConfig.apiV3Key">
+            <el-input v-model="form.weChatConfig.apiV3Key" placeholder="请输入API V3秘钥" clearable
+                      :style="{width: '100%'}" type="textarea" :autosize="{minRows: 8, maxRows: 8}"></el-input>
+          </el-form-item>
+          <el-form-item label-width="180px" label="apiclient_key.perm证书" prop="weChatConfig.privateKeyContent">
+            <el-input v-model="form.weChatConfig.privateKeyContent" type="textarea"
+                      placeholder="请上传apiclient_key.perm证书"
+                      readonly :autosize="{minRows: 8, maxRows: 8}" :style="{width: '100%'}"></el-input>
+          </el-form-item>
+          <el-form-item label-width="180px" label="" prop="privateKeyContentFile">
+            <el-upload ref="privateKeyContentFile"
+                       :limit="1"
+                       :accept="fileAccept"
+                       :headers="header"
+                       action=""
+                       :before-upload="pemFileBeforeUpload"
+                       :http-request="privateKeyUpload"
+            >
+              <el-button size="small" type="primary" icon="el-icon-upload">点击上传</el-button>
+            </el-upload>
+          </el-form-item>
+          <el-form-item label-width="180px" label="apiclient_cert.perm证书" prop="weChatConfig.privateCertContent">
+            <el-input v-model="form.weChatConfig.privateCertContent" type="textarea"
+                      placeholder="请上传apiclient_cert.perm证书"
+                      readonly :autosize="{minRows: 8, maxRows: 8}" :style="{width: '100%'}"></el-input>
+          </el-form-item>
+          <el-form-item label-width="180px" label="" prop="privateCertContentFile">
+            <el-upload ref="privateCertContentFile"
+                       :limit="1"
+                       :accept="fileAccept"
+                       :headers="header"
+                       action=""
+                       :before-upload="pemFileBeforeUpload"
+                       :http-request="privateCertUpload"
+            >
+              <el-button size="small" type="primary" icon="el-icon-upload">点击上传</el-button>
+            </el-upload>
+          </el-form-item>
+        </div>
+        <el-form-item label-width="180px" label="备注" prop="remark">
+          <el-input v-model="form.remark" :style="{width: '100%'}"></el-input>
+        </el-form-item>
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button @click="close">取消</el-button>
+        <el-button type="primary" @click="handleConfirm">确定</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+<script>
+import {DICT_TYPE, getDictDatas} from "@/utils/dict";
+import {createChannel, getChannel, updateChannel} from "@/api/pay/channel";
+
+const defaultForm = {
+  code: '',
+  status: null,
+  remark: '',
+  feeRate: null,
+  appId: '',
+  merchantId: null,
+  weChatConfig: {
+    appId: '',
+    mchId: '',
+    apiVersion: '',
+    mchKey: '',
+    privateKeyContent: '',
+    privateCertContent: '',
+    apiV3Key:'',
+  }
+}
+
+export default {
+  name: "wechatChannelForm",
+  components: {},
+  props: {
+    // 传输的参数
+    transferParam: {
+      // 加载动画
+      "loading": false,
+      // 是否修改
+      "edit": false,
+      // 是否显示
+      "wechatOpen": false,
+      // 应用ID
+      "appId": null,
+      // 渠道编码
+      "payCode": null,
+      // 商户对象
+      "payMerchant": {
+        // 编号
+        "id": null,
+        // 名称
+        "name": null
+      },
+    }
+  },
+  data() {
+    return {
+      title:'',
+      form: JSON.parse(JSON.stringify(defaultForm)),
+      rules: {
+        feeRate: [{
+          required: true,
+          message: '请输入渠道费率',
+          trigger: 'blur'
+        }],
+        'weChatConfig.mchId': [{
+          required: true,
+          message: '请传入商户号',
+          trigger: 'blur'
+        }],
+        'weChatConfig.appId': [{
+          required: true,
+          message: '请输入公众号APPID',
+          trigger: 'blur'
+        }],
+        status: [{
+          required: true,
+          message: '渠道状态不能为空',
+          trigger: 'blur'
+        }],
+        'weChatConfig.apiVersion': [{
+          required: true,
+          message: 'API版本不能为空',
+          trigger: 'blur'
+        }],
+        'weChatConfig.mchKey': [{
+          required: true,
+          message: '请输入商户秘钥',
+          trigger: 'blur'
+        }],
+        'weChatConfig.privateKeyContent': [{
+          required: true,
+          message: '请上传apiclient_key.perm证书',
+          trigger: 'blur'
+        }],
+        'weChatConfig.privateCertContent': [{
+          required: true,
+          message: '请上传apiclient_cert.perm证书',
+          trigger: 'blur'
+        }],
+        'weChatConfig.apiV3Key': [{
+          required: true,
+          message: '请上传apiV3秘钥值',
+          trigger: 'blur'
+        }],
+      },
+      // 文件上传的header
+      header: {
+        "Authorization": null
+      },
+      fileAccept: ".pem",
+      // 渠道状态 数据字典
+      statusDictDatas: getDictDatas(DICT_TYPE.SYS_COMMON_STATUS),
+      versionDictDatas: getDictDatas(DICT_TYPE.PAY_CHANNEL_WECHAT_VERSION),
+    }
+  },
+  watch: {
+    transferParam: {
+      deep: true,  // 深度监听
+      handler(newVal) {
+        if (newVal.wechatOpen) {
+          this.form.code = newVal.payCode;
+          this.form.appId = newVal.appId;
+          this.form.merchantId = newVal.payMerchant.id;
+          // 只有在初次进来为编辑 并且为加载中的时候才回去请求数据
+          if (newVal.edit && newVal.loading) {
+            this.title = "编辑支付渠道";
+            this.init();
+          } else {
+            this.title = "创建支付渠道";
+          }
+        }
+      }
+    }
+  },
+  methods: {
+    init() {
+      getChannel(this.transferParam.payMerchant.id, this.transferParam.appId, this.transferParam.payCode)
+        .then(response => {
+          this.form.id = response.data.id;
+          this.form.feeRate = response.data.feeRate;
+          this.form.appId = response.data.appId;
+          this.form.status = response.data.status;
+          this.form.remark = response.data.remark;
+
+          let config = JSON.parse(response.data.config);
+          this.form.weChatConfig.appId = config.appId;
+          this.form.weChatConfig.apiVersion = config.apiVersion;
+          this.form.weChatConfig.mchId = config.mchId;
+          this.form.weChatConfig.mchKey = config.mchKey;
+          this.form.weChatConfig.privateKeyContent = config.privateKeyContent;
+          this.form.weChatConfig.privateCertContent = config.privateCertContent;
+          this.form.weChatConfig.apiV3Key = config.apiV3Key;
+          this.transferParam.loading = false;
+        })
+    },
+    close() {
+      this.transferParam.wechatOpen = false;
+      this.form = JSON.parse(JSON.stringify(defaultForm));
+    },
+    handleConfirm() {
+      this.$refs['wechatJsApiForm'].validate(valid => {
+        if (!valid) {
+          return
+        }
+        let data = this.form;
+        data.config = JSON.stringify(this.form.weChatConfig);
+        if (this.transferParam.edit) {
+          updateChannel(data).then(response => {
+            if (response.code === 0) {
+              this.msgSuccess("修改成功");
+              this.close();
+            }
+
+          })
+        } else {
+
+          createChannel(data).then(response => {
+            if (response.code === 0) {
+              this.msgSuccess("新增成功");
+              this.$parent.refreshTable();
+              this.close();
+            }
+          });
+        }
+      });
+    },
+    pemFileBeforeUpload(file) {
+      let format = '.' + file.name.split(".")[1];
+      if (format !== this.fileAccept) {
+        this.$message.error('请上传指定格式"' + this.fileAccept + '"文件');
+        return false;
+      }
+      let isRightSize = file.size / 1024 / 1024 < 2
+      if (!isRightSize) {
+        this.$message.error('文件大小超过 2MB')
+      }
+      return isRightSize
+    },
+    privateKeyUpload(event) {
+      const readFile = new FileReader()
+      readFile.onload = (e) => {
+        this.form.weChatConfig.privateKeyContent = e.target.result
+      }
+      readFile.readAsText(event.file);
+    },
+    privateCertUpload(event) {
+      const readFile = new FileReader()
+      readFile.onload = (e) => {
+        this.form.weChatConfig.privateCertContent = e.target.result
+      }
+      readFile.readAsText(event.file);
+    }
+  }
+}
+
+</script>
+<style scoped>
+
+</style>

+ 492 - 0
yudao-admin-ui/src/views/pay/app/index.vue

@@ -0,0 +1,492 @@
+<template>
+  <div class="app-container">
+
+    <!-- 搜索工作栏 -->
+    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
+      <el-form-item label="应用名" prop="name">
+        <el-input v-model="queryParams.name" placeholder="请输入应用名" clearable size="small"
+                  @keyup.enter.native="handleQuery"/>
+      </el-form-item>
+      <el-form-item label="商户名称" prop="merchantName">
+        <el-input v-model="queryParams.merchantName" placeholder="请输入商户名称" clearable size="small"
+                  @keyup.enter.native="handleQuery"/>
+      </el-form-item>
+      <el-form-item label="开启状态" prop="status">
+        <el-select v-model="queryParams.status" placeholder="请选择开启状态" clearable size="small">
+          <el-option v-for="dict in statusDictDatas" :key="parseInt(dict.value)" :label="dict.label"
+                     :value="parseInt(dict.value)"/>
+        </el-select>
+      </el-form-item>
+      <el-form-item>
+        <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
+        <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
+      </el-form-item>
+    </el-form>
+
+    <!-- 操作工具栏 -->
+    <el-row :gutter="10" class="mb8">
+      <el-col :span="1.5">
+        <el-button type="primary" plain icon="el-icon-plus" size="mini" @click="handleAdd"
+                   v-hasPermi="['pay:app:create']">新增
+        </el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button type="warning" plain icon="el-icon-download" size="mini" @click="handleExport"
+                   v-hasPermi="['pay:app:export']">导出
+        </el-button>
+      </el-col>
+      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
+    </el-row>
+
+    <!-- 列表 -->
+    <el-table v-loading="loading" :data="list">
+      <el-table-column label="应用编号" align="center" prop="id"/>
+      <el-table-column label="应用名" align="center" prop="name"/>
+      <el-table-column label="开启状态" align="center" prop="status">
+        <template slot-scope="scope">
+          <el-switch v-model="scope.row.status" :active-value="0" :inactive-value="1"
+                     @change="handleStatusChange(scope.row)"/>
+        </template>
+      </el-table-column>
+      <el-table-column label="商户名称" align="center" prop="payMerchant.name"/>
+      <el-table-column label="创建时间" align="center" prop="createTime" width="180">
+        <template slot-scope="scope">
+          <span>{{ parseTime(scope.row.createTime) }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="支付宝配置" align="center">
+        <el-table-column :label="payChannelEnum.ALIPAY_APP.name" align="center">
+          <template slot-scope="scope">
+            <el-button type="success" icon="el-icon-check" circle
+                       v-if="judgeChannelExist(scope.row.channelCodes,payChannelEnum.ALIPAY_APP.code)"
+                       @click="handleUpdateChannel(scope.row,payChannelEnum.ALIPAY_APP.code,payType.ALIPAY)">
+            </el-button>
+            <el-button v-else
+                       type="danger" icon="el-icon-close" circle
+                       @click="handleCreateChannel(scope.row,payChannelEnum.ALIPAY_APP.code,payType.ALIPAY)">
+            </el-button>
+          </template>
+        </el-table-column>
+        <el-table-column :label="payChannelEnum.ALIPAY_PC.name" align="center">
+          <template slot-scope="scope">
+            <el-button type="success" icon="el-icon-check" circle
+                       v-if="judgeChannelExist(scope.row.channelCodes,payChannelEnum.ALIPAY_PC.code)"
+                       @click="handleUpdateChannel(scope.row,payChannelEnum.ALIPAY_APP.code,payType.ALIPAY)">
+            </el-button>
+            <el-button v-else
+                       type="danger" icon="el-icon-close" circle
+                       @click="handleCreateChannel(scope.row,payChannelEnum.ALIPAY_PC.code,payType.ALIPAY)">
+            </el-button>
+          </template>
+        </el-table-column>
+        <el-table-column :label="payChannelEnum.ALIPAY_WAP.name" align="center">
+          <template slot-scope="scope">
+            <el-button type="success" icon="el-icon-check" circle
+                       v-if="judgeChannelExist(scope.row.channelCodes,payChannelEnum.ALIPAY_WAP.code)"
+                       @click="handleUpdateChannel(scope.row,payChannelEnum.ALIPAY_APP.code,payType.ALIPAY)">
+            </el-button>
+            <el-button v-else
+                       type="danger" icon="el-icon-close" circle
+                       @click="handleCreateChannel(scope.row,payChannelEnum.ALIPAY_WAP.code,payType.ALIPAY)">
+            </el-button>
+          </template>
+        </el-table-column>
+        <el-table-column :label="payChannelEnum.ALIPAY_QR.name" align="center">
+          <template slot-scope="scope">
+            <el-button type="success" icon="el-icon-check" circle
+                       v-if="judgeChannelExist(scope.row.channelCodes,payChannelEnum.ALIPAY_QR.code)"
+                       @click="handleUpdateChannel(scope.row,payChannelEnum.ALIPAY_APP.code,payType.ALIPAY)">
+            </el-button>
+            <el-button v-else
+                       type="danger" icon="el-icon-close" circle
+                       @click="handleCreateChannel(scope.row,payChannelEnum.ALIPAY_QR.code,payType.ALIPAY)">
+            </el-button>
+          </template>
+        </el-table-column>
+      </el-table-column>
+      <el-table-column label="微信配置" align="center">
+        <el-table-column :label="payChannelEnum.WX_LITE.name" align="center">
+          <template slot-scope="scope">
+            <el-button type="success" icon="el-icon-check" circle
+                       v-if="judgeChannelExist(scope.row.channelCodes,payChannelEnum.WX_LITE.code)"
+                       @click="handleUpdateChannel(scope.row,payChannelEnum.ALIPAY_APP.code,payType.WECHAT)">
+            </el-button>
+            <el-button v-else
+                       type="danger" icon="el-icon-close" circle
+                       @click="handleCreateChannel(scope.row,payChannelEnum.WX_LITE.code,payType.WECHAT)">
+            </el-button>
+          </template>
+        </el-table-column>
+        <el-table-column :label="payChannelEnum.WX_PUB.name" align="center">
+          <template slot-scope="scope">
+            <el-button type="success" icon="el-icon-check" circle
+                       v-if="judgeChannelExist(scope.row.channelCodes,payChannelEnum.WX_PUB.code)"
+                       @click="handleUpdateChannel(scope.row,payChannelEnum.ALIPAY_APP.code,payType.WECHAT)">
+            </el-button>
+            <el-button v-else
+                       type="danger" icon="el-icon-close" circle
+                       @click="handleCreateChannel(scope.row,payChannelEnum.WX_PUB.code,payType.WECHAT)">
+            </el-button>
+          </template>
+        </el-table-column>
+        <el-table-column :label="payChannelEnum.WX_APP.name" align="center">
+          <template slot-scope="scope">
+            <el-button type="success" icon="el-icon-check" circle
+                       v-if="judgeChannelExist(scope.row.channelCodes,payChannelEnum.WX_APP.code)"
+                       @click="handleUpdateChannel(scope.row,payChannelEnum.ALIPAY_APP.code,payType.WECHAT)">
+            </el-button>
+            <el-button v-else
+                       type="danger" icon="el-icon-close" circle
+                       @click="handleCreateChannel(scope.row,payChannelEnum.WX_APP.code,payType.WECHAT)">
+            </el-button>
+          </template>
+        </el-table-column>
+      </el-table-column>
+      <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
+        <template slot-scope="scope">
+          <el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)"
+                     v-hasPermi="['pay:app:update']">修改
+          </el-button>
+          <el-button size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)"
+                     v-hasPermi="['pay:app:delete']">删除
+          </el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+    <!-- 分页组件 -->
+    <pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNo" :limit.sync="queryParams.pageSize"
+                @pagination="getList"/>
+
+    <!-- 对话框(添加 / 修改) -->
+    <el-dialog :title="title" :visible.sync="open" width="800px" append-to-body>
+      <el-form ref="form" :model="form" :rules="rules" label-width="160px">
+        <el-form-item label="应用名" prop="name">
+          <el-input v-model="form.name" placeholder="请输入应用名"/>
+        </el-form-item>
+        <el-form-item label="所属商户" prop="merchantId">
+          <el-select
+            v-model="form.merchantId"
+            filterable
+            remote
+            reserve-keyword
+            placeholder="请选择所属商户"
+            :remote-method="handleGetMerchantListByName"
+            :loading="loading">
+            <el-option
+              v-for="item in merchantList"
+              :key="item.id"
+              :label="item.name"
+              :value="item.id">
+            </el-option>
+          </el-select>
+        </el-form-item>
+        <el-form-item label="开启状态" prop="status">
+          <el-radio-group v-model="form.status">
+            <el-radio v-for="dict in statusDictDatas" :key="parseInt(dict.value)" :label="parseInt(dict.value)">
+              {{ dict.label }}
+            </el-radio>
+          </el-radio-group>
+        </el-form-item>
+
+        <el-form-item label="支付结果的回调地址" prop="payNotifyUrl">
+          <el-input v-model="form.payNotifyUrl" placeholder="请输入支付结果的回调地址"/>
+        </el-form-item>
+        <el-form-item label="退款结果的回调地址" prop="refundNotifyUrl">
+          <el-input v-model="form.refundNotifyUrl" placeholder="请输入退款结果的回调地址"/>
+        </el-form-item>
+
+        <el-form-item label="备注" prop="remark">
+          <el-input v-model="form.remark" placeholder="请输入备注"/>
+        </el-form-item>
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button type="primary" @click="submitForm">确 定</el-button>
+        <el-button @click="cancel">取 消</el-button>
+      </div>
+    </el-dialog>
+    <wechat-channel-form :transferParam="channelParam"></wechat-channel-form>
+    <ali-pay-channel-form :transferParam="channelParam"></ali-pay-channel-form>
+  </div>
+</template>
+
+<script>
+import {createApp, updateApp, changeAppStatus, deleteApp, getApp, getAppPage, exportAppExcel} from "@/api/pay/app";
+import {DICT_TYPE, getDictDatas} from "@/utils/dict";
+import {PayType, PayChannelEnum, SysCommonStatusEnum} from "@/utils/constants";
+import {getMerchantListByName} from "@/api/pay/merchant";
+import wechatChannelForm from "@/views/pay/app/components/wechatChannelForm";
+import aliPayChannelForm from "@/views/pay/app/components/aliPayChannelForm";
+
+export default {
+  name: "App",
+  components: {
+    "wechatChannelForm": wechatChannelForm,
+    "aliPayChannelForm": aliPayChannelForm
+  },
+  data() {
+    return {
+      // 遮罩层
+      loading: true,
+      // 显示搜索条件
+      showSearch: true,
+      // 总条数
+      total: 0,
+      // 支付应用信息列表
+      list: [],
+      // 弹出层标题
+      title: "",
+      // 是否显示弹出层
+      open: false,
+      dateRangeCreateTime: [],
+      // 查询参数
+      queryParams: {
+        pageNo: 1,
+        pageSize: 10,
+        name: null,
+        status: null,
+        remark: null,
+        payNotifyUrl: null,
+        refundNotifyUrl: null,
+        merchantName: null,
+      },
+      // 表单参数
+      form: {},
+      // 表单校验
+      rules: {
+        name: [{required: true, message: "应用名不能为空", trigger: "blur"}],
+        status: [{required: true, message: "开启状态不能为空", trigger: "blur"}],
+        payNotifyUrl: [{required: true, message: "支付结果的回调地址不能为空", trigger: "blur"}],
+        refundNotifyUrl: [{required: true, message: "退款结果的回调地址不能为空", trigger: "blur"}],
+        merchantId: [{required: true, message: "商户编号不能为空", trigger: "blur"}],
+      },
+      // 数据字典
+      statusDictDatas: getDictDatas(DICT_TYPE.SYS_COMMON_STATUS),
+      sysCommonStatusEnum: SysCommonStatusEnum,
+      // 支付渠道枚举
+      payChannelEnum: PayChannelEnum,
+      // 支付类型
+      payType: PayType,
+      // 商户列表
+      merchantList: [],
+      // 是否显示支付窗口
+      payOpen: false,
+      // 微信组件传参参数
+      channelParam: {
+        // 是否修改
+        "edit": false,
+        // 微信是否显示
+        "wechatOpen": false,
+        // 支付宝是否显示
+        "aliPayOpen": false,
+        // 应用ID
+        "appId": null,
+        // 渠道编码
+        "payCode": null,
+        // 商户对象
+        "payMerchant": {
+          // 编号
+          "id": null,
+          // 名称
+          "name": null
+        },
+      }
+    };
+  },
+  created() {
+    this.getList();
+    this.handleGetMerchantListByName(null);
+  },
+  methods: {
+    /** 查询列表 */
+    getList() {
+      this.loading = true;
+      // 处理查询参数
+      let params = {...this.queryParams};
+      this.addBeginAndEndTime(params, this.dateRangeCreateTime, 'createTime');
+      // 执行查询
+      getAppPage(params).then(response => {
+        this.list = response.data.list;
+        console.log(this.list);
+        this.total = response.data.total;
+        this.loading = false;
+      });
+    },
+    /** 取消按钮 */
+    cancel() {
+      this.open = false;
+      this.reset();
+    },
+    /** 表单重置 */
+    reset() {
+      this.form = {
+        id: undefined,
+        name: undefined,
+        status: undefined,
+        remark: undefined,
+        payNotifyUrl: undefined,
+        refundNotifyUrl: undefined,
+        merchantId: undefined,
+      };
+      this.resetForm("form");
+    },
+    /** 搜索按钮操作 */
+    handleQuery() {
+      this.queryParams.pageNo = 1;
+      this.getList();
+    },
+    /** 重置按钮操作 */
+    resetQuery() {
+      this.dateRangeCreateTime = [];
+      this.resetForm("queryForm");
+      this.handleQuery();
+    },
+    /** 新增按钮操作 */
+    handleAdd() {
+      this.reset();
+      this.open = true;
+      this.title = "添加支付应用信息";
+    },
+    /** 修改按钮操作 */
+    handleUpdate(row) {
+      this.reset();
+      const id = row.id;
+      getApp(id).then(response => {
+        this.form = response.data;
+        this.open = true;
+        this.title = "修改支付应用信息";
+      });
+    },
+    // 用户状态修改
+    handleStatusChange(row) {
+      let text = row.status === SysCommonStatusEnum.ENABLE ? "启用" : "停用";
+      this.$confirm('确认要"' + text + '""' + row.name + '"应用吗?', "警告", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(function () {
+        return changeAppStatus(row.id, row.status);
+      }).then(() => {
+        this.msgSuccess(text + "成功");
+      }).catch(function () {
+        row.status = row.status === SysCommonStatusEnum.ENABLE ? SysCommonStatusEnum.DISABLE
+          : SysCommonStatusEnum.ENABLE;
+      });
+    },
+    /** 提交按钮 */
+    submitForm() {
+      this.$refs["form"].validate(valid => {
+        if (!valid) {
+          return;
+        }
+        // 修改的提交
+        if (this.form.id != null) {
+          updateApp(this.form).then(response => {
+            this.msgSuccess("修改成功");
+            this.open = false;
+            this.getList();
+          });
+          return;
+        }
+        // 添加的提交
+        createApp(this.form).then(response => {
+          this.msgSuccess("新增成功");
+          this.open = false;
+          this.getList();
+        });
+      });
+    },
+    /** 删除按钮操作 */
+    handleDelete(row) {
+      const id = row.id;
+      this.$confirm('是否确认删除支付应用信息编号为"' + id + '"的数据项?', "警告", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(function () {
+        return deleteApp(id);
+      }).then(() => {
+        this.getList();
+        this.msgSuccess("删除成功");
+      }).catch(() => {
+        this.$message({
+          type: 'info',
+          message: '已取消删除'
+        });
+      });
+    },
+    /** 导出按钮操作 */
+    handleExport() {
+      // 处理查询参数
+      let params = {...this.queryParams};
+      params.pageNo = undefined;
+      params.pageSize = undefined;
+      this.addBeginAndEndTime(params, this.dateRangeCreateTime, 'createTime');
+      // 执行导出
+      this.$confirm('是否确认导出所有支付应用信息数据项?', "警告", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(function () {
+        return exportAppExcel(params);
+      }).then(response => {
+        this.downloadExcel(response, '支付应用信息.xls');
+      })
+    },
+    /**
+     * 根据商户名称模糊匹配商户信息
+     * @param name 商户名称
+     */
+    handleGetMerchantListByName(name) {
+      getMerchantListByName(name).then(response => {
+        this.merchantList = response.data;
+      });
+    },
+    /**
+     * 修改支付渠道信息
+     */
+    handleUpdateChannel(row, payCode, type) {
+      this.settingChannelParam(row, payCode, type)
+      this.channelParam.edit = true;
+      this.channelParam.loading = true;
+
+    },
+    /**
+     * 新增支付渠道信息
+     */
+    handleCreateChannel(row, payCode, type) {
+      this.settingChannelParam(row, payCode, type)
+      this.channelParam.edit = false;
+      this.channelParam.loading = false;
+    },
+    /**
+     * 设置支付渠道信息
+     */
+    settingChannelParam(row, payCode, type) {
+      if (type === PayType.WECHAT) {
+        this.channelParam.wechatOpen = true;
+        this.channelParam.aliPayOpen = false;
+      }
+      if (type === PayType.ALIPAY) {
+        this.channelParam.aliPayOpen = true;
+        this.channelParam.wechatOpen = false;
+      }
+      this.channelParam.edit = false;
+      this.channelParam.loading = false;
+      this.channelParam.appId = row.id;
+      this.channelParam.payCode = payCode;
+      this.channelParam.payMerchant = row.payMerchant;
+    },
+    /**
+     * 根据渠道编码判断渠道列表中是否存在
+     * @param channels 渠道列表
+     * @param channelCode 渠道编码
+     */
+    judgeChannelExist(channels, channelCode) {
+      return channels.indexOf(channelCode) !== -1;
+    },
+    refreshTable() {
+      this.getList();
+    }
+  }
+};
+</script>

+ 296 - 0
yudao-admin-ui/src/views/pay/merchant/index.vue

@@ -0,0 +1,296 @@
+<template>
+  <div class="app-container">
+
+    <!-- 搜索工作栏 -->
+    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
+      <el-form-item label="商户号" prop="no">
+        <el-input v-model="queryParams.no" placeholder="请输入商户号" clearable size="small" @keyup.enter.native="handleQuery"/>
+      </el-form-item>
+      <el-form-item label="商户全称" prop="name">
+        <el-input v-model="queryParams.name" placeholder="请输入商户全称" clearable size="small" @keyup.enter.native="handleQuery"/>
+      </el-form-item>
+      <el-form-item label="商户简称" prop="shortName">
+        <el-input v-model="queryParams.shortName" placeholder="请输入商户简称" clearable size="small" @keyup.enter.native="handleQuery"/>
+      </el-form-item>
+      <el-form-item label="开启状态" prop="status">
+        <el-select v-model="queryParams.status" placeholder="请选择开启状态" clearable size="small">
+          <el-option v-for="dict in statusDictDatas" :key="parseInt(dict.value)" :label="dict.label" :value="parseInt(dict.value)"/>
+        </el-select>
+      </el-form-item>
+      <el-form-item label="备注" prop="remark">
+        <el-input v-model="queryParams.remark" placeholder="请输入备注" clearable size="small" @keyup.enter.native="handleQuery"/>
+      </el-form-item>
+      <el-form-item label="创建时间">
+        <el-date-picker v-model="dateRangeCreateTime" size="small" style="width: 240px" value-format="yyyy-MM-dd"
+                        type="daterange" range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期" />
+      </el-form-item>
+      <el-form-item>
+        <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
+        <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
+      </el-form-item>
+    </el-form>
+
+    <!-- 操作工具栏 -->
+    <el-row :gutter="10" class="mb8">
+      <el-col :span="1.5">
+        <el-button type="primary" plain icon="el-icon-plus" size="mini" @click="handleAdd"
+                   v-hasPermi="['pay:merchant:create']">新增</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button type="warning" plain icon="el-icon-download" size="mini" @click="handleExport"
+                   v-hasPermi="['pay:merchant:export']">导出</el-button>
+      </el-col>
+      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
+    </el-row>
+
+    <!-- 列表 -->
+    <el-table v-loading="loading" :data="list">
+      <el-table-column label="商户编号" align="center" prop="id" />
+      <el-table-column label="商户号" align="center" prop="no" />
+      <el-table-column label="商户全称" align="center" prop="name" />
+      <el-table-column label="商户简称" align="center" prop="shortName" />
+      <el-table-column label="开启状态" align="center" prop="status" >
+        <template slot-scope="scope">
+          <el-switch v-model="scope.row.status" :active-value="0" :inactive-value="1" @change="handleStatusChange(scope.row)" />
+        </template>
+      </el-table-column>
+      <el-table-column label="备注" align="center" prop="remark" />
+      <el-table-column label="创建时间" align="center" prop="createTime" width="180">
+        <template slot-scope="scope">
+          <span>{{ parseTime(scope.row.createTime) }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
+        <template slot-scope="scope">
+          <el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)"
+                     v-hasPermi="['pay:merchant:update']">修改</el-button>
+          <el-button size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)"
+                     v-hasPermi="['pay:merchant:delete']">删除</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+    <!-- 分页组件 -->
+    <pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNo" :limit.sync="queryParams.pageSize"
+                @pagination="getList"/>
+
+    <!-- 对话框(添加 / 修改) -->
+    <el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
+      <el-form ref="form" :model="form" :rules="rules" label-width="80px">
+<!--        <el-form-item label="商户号" prop="no">-->
+<!--          <el-input v-model="form.no" placeholder="请输入商户号" />-->
+<!--        </el-form-item>-->
+        <el-form-item label="商户全称" prop="name">
+          <el-input v-model="form.name" placeholder="请输入商户全称" />
+        </el-form-item>
+        <el-form-item label="商户简称" prop="shortName">
+          <el-input v-model="form.shortName" placeholder="请输入商户简称" />
+        </el-form-item>
+        <el-form-item label="开启状态" prop="status">
+          <el-radio-group v-model="form.status">
+            <el-radio v-for="dict in statusDictDatas" :key="parseInt(dict.value)" :label="parseInt(dict.value)">
+              {{dict.label}}</el-radio>
+          </el-radio-group>
+        </el-form-item>
+        <el-form-item label="备注" prop="remark">
+          <el-input v-model="form.remark" placeholder="请输入备注" />
+        </el-form-item>
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button type="primary" @click="submitForm">确 定</el-button>
+        <el-button @click="cancel">取 消</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import {
+  createMerchant,
+  updateMerchant,
+  changeMerchantStatus,
+  deleteMerchant,
+  getMerchant,
+  getMerchantPage,
+  exportMerchantExcel
+} from "@/api/pay/merchant";
+import {DICT_TYPE, getDictDatas} from "@/utils/dict";
+import {SysCommonStatusEnum} from "@/utils/constants";
+
+export default {
+  name: "Merchant",
+  components: {
+  },
+  data() {
+    return {
+      // 遮罩层
+      loading: true,
+      // 显示搜索条件
+      showSearch: true,
+      // 总条数
+      total: 0,
+      // 支付商户信息列表
+      list: [],
+      // 弹出层标题
+      title: "",
+      // 是否显示弹出层
+      open: false,
+      dateRangeCreateTime: [],
+      // 查询参数
+      queryParams: {
+        pageNo: 1,
+        pageSize: 10,
+        no: null,
+        name: null,
+        shortName: null,
+        status: null,
+        remark: null,
+      },
+      // 表单参数
+      form: {},
+      // 表单校验
+      rules: {
+        no: [{ required: true, message: "商户号不能为空", trigger: "blur" }],
+        name: [{ required: true, message: "商户全称不能为空", trigger: "blur" }],
+        shortName: [{ required: true, message: "商户简称不能为空", trigger: "blur" }],
+        status: [{ required: true, message: "开启状态不能为空", trigger: "blur" }],
+      },
+      // 商户状态数据字典
+      statusDictDatas: getDictDatas(DICT_TYPE.SYS_COMMON_STATUS)
+    };
+  },
+  created() {
+    this.getList();
+  },
+  methods: {
+    /** 查询列表 */
+    getList() {
+      this.loading = true;
+      // 处理查询参数
+      let params = {...this.queryParams};
+      this.addBeginAndEndTime(params, this.dateRangeCreateTime, 'createTime');
+      // 执行查询
+      getMerchantPage(params).then(response => {
+        console.log(response.data);
+        this.list = response.data.list;
+        this.total = response.data.total;
+        this.loading = false;
+      });
+    },
+    /** 取消按钮 */
+    cancel() {
+      this.open = false;
+      this.reset();
+    },
+    /** 表单重置 */
+    reset() {
+      this.form = {
+        id: undefined,
+        no: undefined,
+        name: undefined,
+        shortName: undefined,
+        status: undefined,
+        remark: undefined,
+      };
+      this.resetForm("form");
+    },
+    /** 搜索按钮操作 */
+    handleQuery() {
+      this.queryParams.pageNo = 1;
+      this.getList();
+    },
+    /** 重置按钮操作 */
+    resetQuery() {
+      this.dateRangeCreateTime = [];
+      this.resetForm("queryForm");
+      this.handleQuery();
+    },
+    /** 新增按钮操作 */
+    handleAdd() {
+      this.reset();
+      this.open = true;
+      this.title = "添加支付商户信息";
+    },
+    /** 修改按钮操作 */
+    handleUpdate(row) {
+      this.reset();
+      const id = row.id;
+      getMerchant(id).then(response => {
+        this.form = response.data;
+        this.open = true;
+        this.title = "修改支付商户信息";
+      });
+    },
+    // 用户状态修改
+    handleStatusChange(row) {
+      let text = row.status === SysCommonStatusEnum.ENABLE ? "启用" : "停用";
+      this.$confirm('确认要"' + text + '""' + row.name + '"商户吗?', "警告", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(function() {
+        return changeMerchantStatus(row.id, row.status);
+      }).then(() => {
+        this.msgSuccess(text + "成功");
+      }).catch(function() {
+        row.status = row.status === SysCommonStatusEnum.ENABLE ? SysCommonStatusEnum.DISABLE
+          : SysCommonStatusEnum.ENABLE;
+      });
+    },
+    /** 提交按钮 */
+    submitForm() {
+      this.$refs["form"].validate(valid => {
+        if (!valid) {
+          return;
+        }
+        // 修改的提交
+        if (this.form.id != null) {
+          updateMerchant(this.form).then(response => {
+            this.msgSuccess("修改成功");
+            this.open = false;
+            this.getList();
+          });
+          return;
+        }
+        // 添加的提交
+        createMerchant(this.form).then(response => {
+          this.msgSuccess("新增成功");
+          this.open = false;
+          this.getList();
+        });
+      });
+    },
+    /** 删除按钮操作 */
+    handleDelete(row) {
+      const id = row.id;
+      this.$confirm('是否确认删除支付商户信息编号为"' + id + '"的数据项?', "警告", {
+          confirmButtonText: "确定",
+          cancelButtonText: "取消",
+          type: "warning"
+        }).then(function() {
+          return deleteMerchant(id);
+        }).then(() => {
+          this.getList();
+          this.msgSuccess("删除成功");
+        })
+    },
+    /** 导出按钮操作 */
+    handleExport() {
+      // 处理查询参数
+      let params = {...this.queryParams};
+      params.pageNo = undefined;
+      params.pageSize = undefined;
+      this.addBeginAndEndTime(params, this.dateRangeCreateTime, 'createTime');
+      // 执行导出
+      this.$confirm('是否确认导出所有支付商户信息数据项?', "警告", {
+          confirmButtonText: "确定",
+          cancelButtonText: "取消",
+          type: "warning"
+        }).then(function() {
+          return exportMerchantExcel(params);
+        }).then(response => {
+          this.downloadExcel(response, '支付商户信息.xls');
+        })
+    }
+  }
+};
+</script>

Some files were not shown because too many files changed in this diff