Browse Source

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

# Conflicts:
#	yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderServiceImpl.java
puhui999 2 years ago
parent
commit
a73e078593
95 changed files with 2597 additions and 2328 deletions
  1. 0 1
      yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/config/YudaoPayAutoConfiguration.java
  2. 9 7
      yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/order/PayOrderRespDTO.java
  3. 68 0
      yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/refund/PayRefundRespDTO.java
  4. 16 1
      yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/AbstractPayClient.java
  5. 21 24
      yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AbstractAlipayPayClient.java
  6. 1 1
      yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayAppPayClient.java
  7. 1 1
      yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayBarPayClient.java
  8. 1 1
      yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayPcPayClient.java
  9. 1 1
      yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayQrPayClient.java
  10. 1 1
      yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayWapPayClient.java
  11. 35 66
      yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/weixin/AbstractWxPayClient.java
  12. 1 1
      yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/weixin/WxBarPayClient.java
  13. 2 2
      yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/weixin/WxNativePayClient.java
  14. 2 2
      yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/weixin/WxPubPayClient.java
  15. 2 1
      yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/enums/order/PayOrderStatusRespEnum.java
  16. 5 1
      yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/enums/refund/PayRefundStatusRespEnum.java
  17. 39 0
      yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/convert/MoneyConvert.java
  18. 5 1
      yudao-framework/yudao-spring-boot-starter-test/src/main/java/cn/iocoder/yudao/framework/test/core/util/RandomUtils.java
  19. 2 1
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderServiceImpl.java
  20. 5 6
      yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/DictTypeConstants.java
  21. 31 33
      yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/ErrorCodeConstants.java
  22. 5 5
      yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/notify/PayNotifyStatusEnum.java
  23. 0 0
      yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/notify/PayNotifyTypeEnum.java
  24. 14 1
      yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/order/PayOrderStatusEnum.java
  25. 4 0
      yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/refund/PayRefundStatusEnum.java
  26. 8 0
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/app/PayAppController.java
  27. 50 4
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/notify/PayNotifyController.java
  28. 45 0
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/notify/vo/PayNotifyTaskBaseVO.java
  29. 54 0
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/notify/vo/PayNotifyTaskDetailRespVO.java
  30. 39 0
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/notify/vo/PayNotifyTaskPageReqVO.java
  31. 22 0
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/notify/vo/PayNotifyTaskRespVO.java
  32. 22 66
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/PayOrderController.java
  33. 20 33
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/vo/PayOrderBaseVO.java
  34. 9 8
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/vo/PayOrderDetailsRespVO.java
  35. 25 46
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/vo/PayOrderExcelVO.java
  36. 8 59
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/vo/PayOrderExportReqVO.java
  37. 2 9
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/vo/PayOrderPageItemRespVO.java
  38. 8 59
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/vo/PayOrderPageReqVO.java
  39. 0 0
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/vo/PayOrderRespVO.java
  40. 18 59
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/refund/PayRefundController.java
  41. 31 58
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/refund/vo/PayRefundBaseVO.java
  42. 0 11
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/refund/vo/PayRefundCreateReqVO.java
  43. 12 7
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/refund/vo/PayRefundDetailsRespVO.java
  44. 24 45
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/refund/vo/PayRefundExcelVO.java
  45. 9 60
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/refund/vo/PayRefundExportReqVO.java
  46. 2 5
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/refund/vo/PayRefundPageItemRespVO.java
  47. 9 60
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/refund/vo/PayRefundPageReqVO.java
  48. 0 22
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/refund/vo/PayRefundRespVO.java
  49. 0 16
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/refund/vo/PayRefundUpdateReqVO.java
  50. 43 0
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/convert/notify/PayNotifyTaskConvert.java
  51. 27 53
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/convert/order/PayOrderConvert.java
  52. 30 61
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/convert/refund/PayRefundConvert.java
  53. 1 1
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/notify/PayNotifyTaskDO.java
  54. 8 24
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/order/PayOrderDO.java
  55. 1 0
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/order/PayOrderExtensionDO.java
  56. 1 8
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/refund/PayRefundDO.java
  57. 7 0
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/notify/PayNotifyLogMapper.java
  58. 20 6
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/notify/PayNotifyTaskMapper.java
  59. 25 37
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/order/PayOrderMapper.java
  60. 20 19
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/refund/PayRefundMapper.java
  61. 8 0
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/redis/RedisKeyConstants.java
  62. 31 0
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/redis/no/PayNoRedisDAO.java
  63. 0 29
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/enums/order/PayOrderNotifyStatusEnum.java
  64. 0 29
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/enums/order/PayOrderRefundStatusEnum.java
  65. 9 0
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/config/PayConfiguration.java
  66. 16 1
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/config/PayProperties.java
  67. 4 0
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/package-info.java
  68. 21 14
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/app/PayAppService.java
  69. 10 9
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/app/PayAppServiceImpl.java
  70. 5 6
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/channel/PayChannelServiceImpl.java
  71. 17 17
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/demo/PayDemoOrderServiceImpl.java
  72. 32 3
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/notify/PayNotifyService.java
  73. 28 11
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/notify/PayNotifyServiceImpl.java
  74. 0 47
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/order/PayOrderExtensionService.java
  75. 0 34
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/order/PayOrderExtensionServiceImpl.java
  76. 13 30
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/order/PayOrderService.java
  77. 87 93
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/order/PayOrderServiceImpl.java
  78. 54 29
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/refund/PayRefundServiceImpl.java
  79. 27 0
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/util/MoneyUtils.java
  80. 6 6
      yudao-module-pay/yudao-module-pay-biz/src/test/java/cn/iocoder/yudao/module/pay/service/app/PayAppServiceTest.java
  81. 132 4
      yudao-module-pay/yudao-module-pay-biz/src/test/java/cn/iocoder/yudao/module/pay/service/channel/PayChannelServiceTest.java
  82. 455 89
      yudao-module-pay/yudao-module-pay-biz/src/test/java/cn/iocoder/yudao/module/pay/service/order/PayOrderServiceTest.java
  83. 9 24
      yudao-module-pay/yudao-module-pay-biz/src/test/java/cn/iocoder/yudao/module/pay/service/refund/PayRefundServiceTest.java
  84. 1 0
      yudao-module-pay/yudao-module-pay-biz/src/test/resources/sql/clean.sql
  85. 26 8
      yudao-module-pay/yudao-module-pay-biz/src/test/resources/sql/create_tables.sql
  86. 9 22
      yudao-ui-admin/src/api/pay/app.js
  87. 18 0
      yudao-ui-admin/src/api/pay/notify.js
  88. 0 36
      yudao-ui-admin/src/utils/constants.js
  89. 4 7
      yudao-ui-admin/src/utils/dict.js
  90. 0 349
      yudao-ui-admin/src/views/pay/app/components/aliPayChannelForm.vue
  91. 268 0
      yudao-ui-admin/src/views/pay/app/components/alipayChannelForm.vue
  92. 11 18
      yudao-ui-admin/src/views/pay/app/index.vue
  93. 221 0
      yudao-ui-admin/src/views/pay/notify/index.vue
  94. 107 185
      yudao-ui-admin/src/views/pay/order/index.vue
  95. 97 234
      yudao-ui-admin/src/views/pay/refund/index.vue

+ 0 - 1
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/config/YudaoPayAutoConfiguration.java

@@ -12,7 +12,6 @@ import org.springframework.context.annotation.Bean;
  * @author 芋道源码
  */
 @AutoConfiguration
-@EnableConfigurationProperties(PayProperties.class)
 public class YudaoPayAutoConfiguration {
 
     @Bean

+ 9 - 7
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/order/PayOrderRespDTO.java

@@ -79,14 +79,16 @@ public class PayOrderRespDTO {
     /**
      * 创建【WAITING】状态的订单返回
      */
-    public PayOrderRespDTO(String displayMode, String displayContent,
-                           String outTradeNo, Object rawData) {
-        this.status = PayOrderStatusRespEnum.WAITING.getStatus();
-        this.displayMode = displayMode;
-        this.displayContent = displayContent;
+    public static PayOrderRespDTO waitingOf(String displayMode, String displayContent,
+                                            String outTradeNo, Object rawData) {
+        PayOrderRespDTO respDTO = new PayOrderRespDTO();
+        respDTO.status = PayOrderStatusRespEnum.WAITING.getStatus();
+        respDTO.displayMode = displayMode;
+        respDTO.displayContent = displayContent;
         // 相对通用的字段
-        this.outTradeNo = outTradeNo;
-        this.rawData = rawData;
+        respDTO.outTradeNo = outTradeNo;
+        respDTO.rawData = rawData;
+        return respDTO;
     }
 
     /**

+ 68 - 0
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/refund/PayRefundRespDTO.java

@@ -1,5 +1,6 @@
 package cn.iocoder.yudao.framework.pay.core.client.dto.refund;
 
+import cn.iocoder.yudao.framework.pay.core.client.exception.PayException;
 import cn.iocoder.yudao.framework.pay.core.enums.refund.PayRefundStatusRespEnum;
 import lombok.Data;
 
@@ -44,4 +45,71 @@ public class PayRefundRespDTO {
      */
     private Object rawData;
 
+    /**
+     * 调用渠道的错误码
+     *
+     * 注意:这里返回的是业务异常,而是不系统异常。
+     * 如果是系统异常,则会抛出 {@link PayException}
+     */
+    private String channelErrorCode;
+    /**
+     * 调用渠道报错时,错误信息
+     */
+    private String channelErrorMsg;
+
+    private PayRefundRespDTO() {
+    }
+
+    /**
+     * 创建【WAITING】状态的退款返回
+     */
+    public static PayRefundRespDTO waitingOf(String channelRefundNo,
+                                             String outRefundNo, Object rawData) {
+        PayRefundRespDTO respDTO = new PayRefundRespDTO();
+        respDTO.status = PayRefundStatusRespEnum.WAITING.getStatus();
+        respDTO.channelRefundNo = channelRefundNo;
+        // 相对通用的字段
+        respDTO.outRefundNo = outRefundNo;
+        respDTO.rawData = rawData;
+        return respDTO;
+    }
+
+    /**
+     * 创建【SUCCESS】状态的退款返回
+     */
+    public static PayRefundRespDTO successOf(String channelRefundNo, LocalDateTime successTime,
+                                             String outRefundNo, Object rawData) {
+        PayRefundRespDTO respDTO = new PayRefundRespDTO();
+        respDTO.status = PayRefundStatusRespEnum.SUCCESS.getStatus();
+        respDTO.channelRefundNo = channelRefundNo;
+        respDTO.successTime = successTime;
+        // 相对通用的字段
+        respDTO.outRefundNo = outRefundNo;
+        respDTO.rawData = rawData;
+        return respDTO;
+    }
+
+    /**
+     * 创建【FAILURE】状态的退款返回
+     */
+    public static PayRefundRespDTO failureOf(String outRefundNo, Object rawData) {
+        return failureOf(null, null,
+                outRefundNo, rawData);
+    }
+
+    /**
+     * 创建【FAILURE】状态的退款返回
+     */
+    public static PayRefundRespDTO failureOf(String channelErrorCode, String channelErrorMsg,
+                                             String outRefundNo, Object rawData) {
+        PayRefundRespDTO respDTO = new PayRefundRespDTO();
+        respDTO.status = PayRefundStatusRespEnum.FAILURE.getStatus();
+        respDTO.channelErrorCode = channelErrorCode;
+        respDTO.channelErrorMsg = channelErrorMsg;
+        // 相对通用的字段
+        respDTO.outRefundNo = outRefundNo;
+        respDTO.rawData = rawData;
+        return respDTO;
+    }
+
 }

+ 16 - 1
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/AbstractPayClient.java

@@ -98,7 +98,8 @@ public abstract class AbstractPayClient<Config extends PayClientConfig> implemen
         try {
             return doParseOrderNotify(params, body);
         } catch (Throwable ex) {
-            log.error("[parseOrderNotify][params({}) body({}) 解析失败]", params, body, ex);
+            log.error("[parseOrderNotify][客户端({}) params({}) body({}) 解析失败]",
+                    getId(), params, body, ex);
             throw buildPayException(ex);
         }
     }
@@ -129,6 +130,20 @@ public abstract class AbstractPayClient<Config extends PayClientConfig> implemen
 
     protected abstract PayRefundRespDTO doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) throws Throwable;
 
+    @Override
+    public PayRefundRespDTO parseRefundNotify(Map<String, String> params, String body) {
+        try {
+            return doParseRefundNotify(params, body);
+        } catch (Throwable ex) {
+            log.error("[parseRefundNotify][客户端({}) params({}) body({}) 解析失败]",
+                    getId(), params, body, ex);
+            throw buildPayException(ex);
+        }
+    }
+
+    protected abstract PayRefundRespDTO doParseRefundNotify(Map<String, String> params, String body)
+            throws Throwable;
+
     // ========== 各种工具方法 ==========
 
     private PayException buildPayException(Throwable ex) {

+ 21 - 24
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AbstractAlipayPayClient.java

@@ -3,8 +3,10 @@ package cn.iocoder.yudao.framework.pay.core.client.impl.alipay;
 import cn.hutool.core.bean.BeanUtil;
 import cn.hutool.core.date.LocalDateTimeUtil;
 import cn.hutool.core.lang.Assert;
+import cn.hutool.core.map.MapUtil;
 import cn.hutool.core.util.StrUtil;
 import cn.hutool.http.HttpUtil;
+import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
 import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderRespDTO;
 import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO;
 import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundRespDTO;
@@ -29,7 +31,6 @@ import java.util.Objects;
 import java.util.function.Supplier;
 
 import static cn.hutool.core.date.DatePattern.NORM_DATETIME_FORMATTER;
-import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
 
 /**
  * 支付宝抽象类,实现支付宝统一的接口、以及部分实现(退款)
@@ -74,10 +75,15 @@ public abstract class AbstractAlipayPayClient extends AbstractPayClient<AlipayPa
                 StandardCharsets.UTF_8.name(), config.getSignType());
 
         // 2. 解析订单的状态
+        // 额外说明:支付宝不仅仅支付成功会回调,再各种触发支付单数据变化时,都会进行回调,所以这里 status 的解析会写的比较复杂
         String tradeStatus = bodyObj.get("trade_status");
         Integer status = Objects.equals("WAIT_BUYER_PAY", tradeStatus) ? PayOrderStatusRespEnum.WAITING.getStatus()
-                : Objects.equals("TRADE_SUCCESS", tradeStatus) ? PayOrderStatusRespEnum.SUCCESS.getStatus()
+                : ObjectUtils.equalsAny(tradeStatus, "TRADE_FINISHED", "TRADE_SUCCESS") ? PayOrderStatusRespEnum.SUCCESS.getStatus()
                 : Objects.equals("TRADE_CLOSED", tradeStatus) ? PayOrderStatusRespEnum.CLOSED.getStatus() : null;
+        // 特殊逻辑: 支付宝没有退款成功的状态,所以,如果有退款金额,我们认为是退款成功
+        if (MapUtil.getDouble(bodyObj, "refund_fee", 0D) > 0) {
+            status = PayOrderStatusRespEnum.REFUND.getStatus();
+        }
         Assert.notNull(status, (Supplier<Throwable>) () -> {
             throw new IllegalArgumentException(StrUtil.format("body({}) 的 trade_status 不正确", body));
         });
@@ -94,7 +100,7 @@ public abstract class AbstractAlipayPayClient extends AbstractPayClient<AlipayPa
      * @return 退款请求 Response
      */
     @Override
-    protected PayRefundRespDTO doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO)  {
+    protected PayRefundRespDTO doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) throws AlipayApiException {
         // 1.1 构建 AlipayTradeRefundModel 请求
         AlipayTradeRefundModel model = new AlipayTradeRefundModel();
         model.setOutTradeNo(reqDTO.getOutTradeNo());
@@ -104,31 +110,22 @@ public abstract class AbstractAlipayPayClient extends AbstractPayClient<AlipayPa
         // 1.2 构建 AlipayTradePayRequest 请求
         AlipayTradeRefundRequest request = new AlipayTradeRefundRequest();
         request.setBizModel(model);
-        try {
-            // 2.1 执行请求
-            AlipayTradeRefundResponse response =  client.execute(request);
-            // 2.2 创建返回结果
-            PayRefundRespDTO refund = new PayRefundRespDTO()
-                    .setOutRefundNo(reqDTO.getOutRefundNo())
-                    .setRawData(response);
-            // 支付宝只要退款调用返回 success,就认为退款成功,不需要回调。具体可见 parseNotify 方法的说明。
-            // 另外,支付宝没有退款单号,所以不用设置
-            if (response.isSuccess()) {
-                refund.setStatus(PayOrderStatusRespEnum.SUCCESS.getStatus())
-                        .setSuccessTime(LocalDateTimeUtil.of(response.getGmtRefundPay()));
-                Assert.notNull(refund.getSuccessTime(), "退款成功时间不能为空");
-            } else {
-                refund.setStatus(PayOrderStatusRespEnum.CLOSED.getStatus());
-            }
-            return refund;
-        } catch (AlipayApiException e) {
-            log.error("[doUnifiedRefund][request({}) 发起退款异常]", toJsonString(reqDTO), e);
-            return null;
+
+        // 2.1 执行请求
+        AlipayTradeRefundResponse response =  client.execute(request);
+        // 2.2 创建返回结果
+        // 支付宝只要退款调用返回 success,就认为退款成功,不需要回调。具体可见 parseNotify 方法的说明。
+        // 另外,支付宝没有退款单号,所以不用设置
+        if (response.isSuccess()) {
+            return PayRefundRespDTO.successOf(null, LocalDateTimeUtil.of(response.getGmtRefundPay()),
+                    reqDTO.getOutRefundNo(), response);
+        } else {
+            return PayRefundRespDTO.failureOf(reqDTO.getOutRefundNo(), response);
         }
     }
 
     @Override
-    public PayRefundRespDTO parseRefundNotify(Map<String, String> params, String body) {
+    public PayRefundRespDTO doParseRefundNotify(Map<String, String> params, String body) {
         // 补充说明:支付宝退款时,没有回调,这点和微信支付是不同的。并且,退款分成部分退款、和全部退款。
         // ① 部分退款:是会有回调,但是它回调的是订单状态的同步回调,不是退款订单的回调
         // ② 全部退款:Wap 支付有订单状态的同步回调,但是 PC/扫码又没有

+ 1 - 1
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayAppPayClient.java

@@ -52,7 +52,7 @@ public class AlipayAppPayClient extends AbstractAlipayPayClient {
         if (!response.isSuccess()) {
             return buildClosedPayOrderRespDTO(reqDTO, response);
         }
-        return new PayOrderRespDTO(displayMode, "",
+        return PayOrderRespDTO.waitingOf(displayMode, "",
                 reqDTO.getOutTradeNo(), response);
     }
 

+ 1 - 1
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayBarPayClient.java

@@ -62,7 +62,7 @@ public class AlipayBarPayClient extends AbstractAlipayPayClient {
         if (!response.isSuccess()) {
             return buildClosedPayOrderRespDTO(reqDTO, response);
         }
-        return new PayOrderRespDTO(displayMode, "",
+        return PayOrderRespDTO.waitingOf(displayMode, "",
                 reqDTO.getOutTradeNo(), response);
     }
 

+ 1 - 1
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayPcPayClient.java

@@ -63,7 +63,7 @@ public class AlipayPcPayClient extends AbstractAlipayPayClient {
         if (!response.isSuccess()) {
             return buildClosedPayOrderRespDTO(reqDTO, response);
         }
-        return new PayOrderRespDTO(displayMode, response.getBody(),
+        return PayOrderRespDTO.waitingOf(displayMode, response.getBody(),
                 reqDTO.getOutTradeNo(), response);
     }
 

+ 1 - 1
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayQrPayClient.java

@@ -50,7 +50,7 @@ public class AlipayQrPayClient extends AbstractAlipayPayClient {
         if (!response.isSuccess()) {
             return buildClosedPayOrderRespDTO(reqDTO, response);
         }
-        return new PayOrderRespDTO(displayMode, response.getQrCode(),
+        return PayOrderRespDTO.waitingOf(displayMode, response.getQrCode(),
                 reqDTO.getOutTradeNo(), response);
     }
 

+ 1 - 1
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayWapPayClient.java

@@ -52,7 +52,7 @@ public class AlipayWapPayClient extends AbstractAlipayPayClient {
         if (!response.isSuccess()) {
             return buildClosedPayOrderRespDTO(reqDTO, response);
         }
-        return new PayOrderRespDTO(displayMode, response.getBody(),
+        return PayOrderRespDTO.waitingOf(displayMode, response.getBody(),
                 reqDTO.getOutTradeNo(), response);
     }
 

+ 35 - 66
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/weixin/AbstractWxPayClient.java

@@ -12,7 +12,6 @@ import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundRespDTO;
 import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedReqDTO;
 import cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient;
 import cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderStatusRespEnum;
-import cn.iocoder.yudao.framework.pay.core.enums.refund.PayRefundStatusRespEnum;
 import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyResult;
 import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyV3Result;
 import com.github.binarywang.wxpay.bean.notify.WxPayRefundNotifyResult;
@@ -131,6 +130,7 @@ public abstract class AbstractWxPayClient extends AbstractPayClient<WxPayClientC
         // 1. 解析回调
         WxPayOrderNotifyResult response = client.parseOrderNotifyResult(body);
         // 2. 构建结果
+        // 微信支付的回调,只有 SUCCESS 支付成功、CLOSED 支付失败两种情况,无需像支付宝一样解析的比较复杂
         Integer status = Objects.equals(response.getResultCode(), "SUCCESS") ?
                 PayOrderStatusRespEnum.SUCCESS.getStatus() : PayOrderStatusRespEnum.CLOSED.getStatus();
         return new PayOrderRespDTO(status, response.getTransactionId(), response.getOpenid(), parseDateV2(response.getTimeEnd()),
@@ -142,6 +142,7 @@ public abstract class AbstractWxPayClient extends AbstractPayClient<WxPayClientC
         WxPayOrderNotifyV3Result response = client.parseOrderNotifyV3Result(body, null);
         WxPayOrderNotifyV3Result.DecryptNotifyResult result = response.getResult();
         // 2. 构建结果
+        // 微信支付的回调,只有 SUCCESS 支付成功、CLOSED 支付失败两种情况,无需像支付宝一样解析的比较复杂
         Integer status = Objects.equals(result.getTradeState(), "SUCCESS") ?
                 PayOrderStatusRespEnum.SUCCESS.getStatus() : PayOrderStatusRespEnum.CLOSED.getStatus();
         String openid = result.getPayer() != null ? result.getPayer().getOpenid() : null;
@@ -163,9 +164,10 @@ public abstract class AbstractWxPayClient extends AbstractPayClient<WxPayClientC
                     throw new IllegalArgumentException(String.format("未知的 API 版本(%s)", config.getApiVersion()));
             }
         } catch (WxPayException e) {
-            // todo 芋艿:异常的处理;
-//            throw buildUnifiedOrderException(null, e);
-            return null;
+            String errorCode = getErrorCode(e);
+            String errorMessage = getErrorMessage(e);
+            return PayRefundRespDTO.failureOf(errorCode, errorMessage,
+                    reqDTO.getOutTradeNo(), e.getXmlString());
         }
     }
 
@@ -181,17 +183,11 @@ public abstract class AbstractWxPayClient extends AbstractPayClient<WxPayClientC
         // 2.1 执行请求
         WxPayRefundResult response = client.refundV2(request);
         // 2.2 创建返回结果
-        PayRefundRespDTO refund = new PayRefundRespDTO()
-                .setOutRefundNo(reqDTO.getOutRefundNo())
-                .setRawData(response);
         if (Objects.equals("SUCCESS", response.getResultCode())) {
-            refund.setStatus(PayRefundStatusRespEnum.WAITING.getStatus())
-                    .setChannelRefundNo(response.getRefundId());
-        } else {
-            refund.setStatus(PayRefundStatusRespEnum.FAILURE.getStatus());
+            return PayRefundRespDTO.waitingOf(response.getRefundId(),
+                    reqDTO.getOutRefundNo(), response);
         }
-        // TODO 芋艿;异常的处理;
-        return refund;
+        return PayRefundRespDTO.failureOf(reqDTO.getOutRefundNo(), response);
     }
 
     private PayRefundRespDTO doUnifiedRefundV3(PayRefundUnifiedReqDTO reqDTO) throws Throwable {
@@ -206,78 +202,51 @@ public abstract class AbstractWxPayClient extends AbstractPayClient<WxPayClientC
         // 2.1 执行请求
         WxPayRefundV3Result response = client.refundV3(request);
         // 2.2 创建返回结果
-        PayRefundRespDTO refund = new PayRefundRespDTO()
-                .setOutRefundNo(reqDTO.getOutRefundNo())
-                .setRawData(response);
         if (Objects.equals("SUCCESS", response.getStatus())) {
-            refund.setStatus(PayRefundStatusRespEnum.SUCCESS.getStatus())
-                    .setChannelRefundNo(response.getRefundId())
-                    .setSuccessTime(parseDateV3(response.getSuccessTime()));
-        } else if (Objects.equals("PROCESSING", response.getStatus())) {
-            refund.setStatus(PayRefundStatusRespEnum.WAITING.getStatus())
-                    .setChannelRefundNo(response.getRefundId());
-        } else {
-            refund.setStatus(PayRefundStatusRespEnum.FAILURE.getStatus());
+            return PayRefundRespDTO.successOf(response.getRefundId(), parseDateV3(response.getSuccessTime()),
+                    reqDTO.getOutRefundNo(), response);
         }
-        // TODO 芋艿;异常的处理;
-        return refund;
+        if (Objects.equals("PROCESSING", response.getStatus())) {
+            return PayRefundRespDTO.waitingOf(response.getRefundId(),
+                    reqDTO.getOutRefundNo(), response);
+        }
+        return PayRefundRespDTO.failureOf(reqDTO.getOutRefundNo(), response);
     }
 
     @Override
-    public PayRefundRespDTO parseRefundNotify(Map<String, String> params, String body) {
-        try {
-            // 微信支付 v2 回调结果处理
-            switch (config.getApiVersion()) {
-                case API_VERSION_V2:
-                    return parseRefundNotifyV2(body);
-                case WxPayClientConfig.API_VERSION_V3:
-                    return parseRefundNotifyV3(body);
-                default:
-                    throw new IllegalArgumentException(String.format("未知的 API 版本(%s)", config.getApiVersion()));
-            }
-        } catch (WxPayException e) {
-            log.error("[parseNotify][params({}) body({}) 解析失败]", params, body, e);
-            throw new RuntimeException(e);
-            // TODO 芋艿:缺一个异常翻译
+    public PayRefundRespDTO doParseRefundNotify(Map<String, String> params, String body) throws WxPayException {
+        switch (config.getApiVersion()) {
+            case API_VERSION_V2:
+                return doParseRefundNotifyV2(body);
+            case WxPayClientConfig.API_VERSION_V3:
+                return parseRefundNotifyV3(body);
+            default:
+                throw new IllegalArgumentException(String.format("未知的 API 版本(%s)", config.getApiVersion()));
         }
     }
 
-    @SuppressWarnings("DuplicatedCode")
-    private PayRefundRespDTO parseRefundNotifyV2(String body) throws WxPayException {
+    private PayRefundRespDTO doParseRefundNotifyV2(String body) throws WxPayException {
         // 1. 解析回调
         WxPayRefundNotifyResult response = client.parseRefundNotifyResult(body);
-        WxPayRefundNotifyResult.ReqInfo responseResult = response.getReqInfo();
+        WxPayRefundNotifyResult.ReqInfo result = response.getReqInfo();
         // 2. 构建结果
-        PayRefundRespDTO notify = new PayRefundRespDTO()
-                .setChannelRefundNo(responseResult.getRefundId())
-                .setOutRefundNo(responseResult.getOutRefundNo())
-                .setRawData(response);
-        if (Objects.equals("SUCCESS", responseResult.getRefundStatus())) {
-            notify.setStatus(PayRefundStatusRespEnum.SUCCESS.getStatus())
-                    .setSuccessTime(parseDateV2B(responseResult.getSuccessTime()));
-        } else {
-            notify.setStatus(PayRefundStatusRespEnum.FAILURE.getStatus());
+        if (Objects.equals("SUCCESS", result.getRefundStatus())) {
+            return PayRefundRespDTO.successOf(result.getRefundId(), parseDateV2B(result.getSuccessTime()),
+                    result.getOutRefundNo(), response);
         }
-        return notify;
+        return PayRefundRespDTO.failureOf(result.getOutRefundNo(), response);
     }
 
-    @SuppressWarnings("DuplicatedCode")
     private PayRefundRespDTO parseRefundNotifyV3(String body) throws WxPayException {
         // 1. 解析回调
         WxPayRefundNotifyV3Result response = client.parseRefundNotifyV3Result(body, null);
-        WxPayRefundNotifyV3Result.DecryptNotifyResult responseResult = response.getResult();
+        WxPayRefundNotifyV3Result.DecryptNotifyResult result = response.getResult();
         // 2. 构建结果
-        PayRefundRespDTO notify = new PayRefundRespDTO()
-                .setChannelRefundNo(responseResult.getRefundId())
-                .setOutRefundNo(responseResult.getOutRefundNo())
-                .setRawData(response);
-        if (Objects.equals("SUCCESS", responseResult.getRefundStatus())) {
-            notify.setStatus(PayRefundStatusRespEnum.SUCCESS.getStatus())
-                    .setSuccessTime(parseDateV3(responseResult.getSuccessTime()));
-        } else {
-            notify.setStatus(PayRefundStatusRespEnum.FAILURE.getStatus());
+        if (Objects.equals("SUCCESS", result.getRefundStatus())) {
+            return PayRefundRespDTO.successOf(result.getRefundId(), parseDateV3(result.getSuccessTime()),
+                    result.getOutRefundNo(), response);
         }
-        return notify;
+        return PayRefundRespDTO.failureOf(result.getOutRefundNo(), response);
     }
 
     // ========== 各种工具方法 ==========

+ 1 - 1
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/weixin/WxBarPayClient.java

@@ -67,7 +67,7 @@ public class WxBarPayClient extends AbstractWxPayClient {
         for (int i = 1; i < Byte.MAX_VALUE; i++) {
             try {
                 WxPayMicropayResult response = client.micropay(request);
-                // 支付成功,例如说:1)用户输入了密码;2)
+                // 支付成功,例如说:1)用户输入了密码;2)用户免密支付
                 return new PayOrderRespDTO(response.getTransactionId(), response.getOpenid(), parseDateV2(response.getTimeEnd()),
                         response.getOutTradeNo(), response)
                         .setDisplayMode(PayOrderDisplayModeEnum.BAR_CODE.getMode());

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

@@ -48,7 +48,7 @@ public class WxNativePayClient extends AbstractWxPayClient {
         WxPayNativeOrderResult response = client.createOrder(request);
 
         // 转换结果
-        return new PayOrderRespDTO(PayOrderDisplayModeEnum.QR_CODE.getMode(), response.getCodeUrl(),
+        return PayOrderRespDTO.waitingOf(PayOrderDisplayModeEnum.QR_CODE.getMode(), response.getCodeUrl(),
                 reqDTO.getOutTradeNo(), response);
     }
 
@@ -66,7 +66,7 @@ public class WxNativePayClient extends AbstractWxPayClient {
         String response = client.createOrderV3(TradeTypeEnum.NATIVE, request);
 
         // 转换结果
-        return new PayOrderRespDTO(PayOrderDisplayModeEnum.QR_CODE.getMode(), response,
+        return PayOrderRespDTO.waitingOf(PayOrderDisplayModeEnum.QR_CODE.getMode(), response,
                 reqDTO.getOutTradeNo(), response);
     }
 

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

@@ -58,7 +58,7 @@ public class WxPubPayClient extends AbstractWxPayClient {
         WxPayMpOrderResult response = client.createOrder(request);
 
         // 转换结果
-        return new PayOrderRespDTO(PayOrderDisplayModeEnum.APP.getMode(), toJsonString(response),
+        return PayOrderRespDTO.waitingOf(PayOrderDisplayModeEnum.APP.getMode(), toJsonString(response),
                 reqDTO.getOutTradeNo(), response);
     }
 
@@ -77,7 +77,7 @@ public class WxPubPayClient extends AbstractWxPayClient {
         WxPayUnifiedOrderV3Result.JsapiResult response = client.createOrderV3(TradeTypeEnum.JSAPI, request);
 
         // 转换结果
-        return new PayOrderRespDTO(PayOrderDisplayModeEnum.APP.getMode(), toJsonString(response),
+        return PayOrderRespDTO.waitingOf(PayOrderDisplayModeEnum.APP.getMode(), toJsonString(response),
                 reqDTO.getOutTradeNo(), response);
     }
 

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

@@ -16,7 +16,8 @@ public enum PayOrderStatusRespEnum {
 
     WAITING(0, "未支付"),
     SUCCESS(10, "支付成功"),
-    CLOSED(20, "支付关闭"), // 未付款交易超时关闭,或支付完成后全额退款
+    REFUND(20, "已退款"),
+    CLOSED(30, "支付关闭"),
     ;
 
     private final Integer status;

+ 5 - 1
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/enums/refund/PayRefundStatusRespEnum.java

@@ -14,7 +14,7 @@ import java.util.Objects;
 @AllArgsConstructor
 public enum PayRefundStatusRespEnum {
 
-    WAITING(0, "退款"),
+    WAITING(0, "等待退款"),
     SUCCESS(10, "退款成功"),
     FAILURE(20, "退款失败");
 
@@ -25,4 +25,8 @@ public enum PayRefundStatusRespEnum {
         return Objects.equals(status, SUCCESS.getStatus());
     }
 
+    public static boolean isFailure(Integer status) {
+        return Objects.equals(status, FAILURE.getStatus());
+    }
+
 }

+ 39 - 0
yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/convert/MoneyConvert.java

@@ -0,0 +1,39 @@
+package cn.iocoder.yudao.framework.excel.core.convert;
+
+import com.alibaba.excel.converters.Converter;
+import com.alibaba.excel.enums.CellDataTypeEnum;
+import com.alibaba.excel.metadata.GlobalConfiguration;
+import com.alibaba.excel.metadata.data.WriteCellData;
+import com.alibaba.excel.metadata.property.ExcelContentProperty;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+
+/**
+ * 金额转换器
+ *
+ * 金额单位:分
+ *
+ * @author 芋道源码
+ */
+public class MoneyConvert implements Converter<Integer> {
+
+    @Override
+    public Class<?> supportJavaTypeKey() {
+        throw new UnsupportedOperationException("暂不支持,也不需要");
+    }
+
+    @Override
+    public CellDataTypeEnum supportExcelTypeKey() {
+        throw new UnsupportedOperationException("暂不支持,也不需要");
+    }
+
+    @Override
+    public WriteCellData<String> convertToExcelData(Integer value, ExcelContentProperty contentProperty,
+                                                    GlobalConfiguration globalConfiguration) {
+        BigDecimal result = BigDecimal.valueOf(value)
+                .divide(new BigDecimal(100), 2, RoundingMode.HALF_UP);
+        return new WriteCellData<>(result.toString());
+    }
+
+}

+ 5 - 1
yudao-framework/yudao-spring-boot-starter-test/src/main/java/cn/iocoder/yudao/framework/test/core/util/RandomUtils.java

@@ -55,6 +55,9 @@ public class RandomUtils {
             }
             return RandomUtil.randomInt();
         });
+        // LocalDateTime
+        PODAM_FACTORY.getStrategy().addOrReplaceTypeManufacturer(LocalDateTime.class,
+                (dataProviderStrategy, attributeMetadata, map) -> randomLocalDateTime());
         // Boolean
         PODAM_FACTORY.getStrategy().addOrReplaceTypeManufacturer(Boolean.class, (dataProviderStrategy, attributeMetadata, map) -> {
             // 如果是 deleted 的字段,返回非删除
@@ -82,7 +85,8 @@ public class RandomUtils {
     }
 
     public static LocalDateTime randomLocalDateTime() {
-        return LocalDateTimeUtil.of(randomDate());
+        // 设置 Nano 为零的原因,避免 MySQL、H2 存储不到时间戳
+        return LocalDateTimeUtil.of(randomDate()).withNano(0);
     }
 
     public static Short randomShort() {

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

@@ -63,6 +63,7 @@ import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.
 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.getSumValue;
 import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
 import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.PAY_ORDER_NOT_FOUND;
+import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.ORDER_NOT_FOUND;
 import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.*;
 
 /**
@@ -344,7 +345,7 @@ public class TradeOrderServiceImpl implements TradeOrderService {
         PayOrderRespDTO payOrder = payOrderApi.getOrder(payOrderId);
         if (payOrder == null) {
             log.error("[validateOrderPaid][order({}) payOrder({}) 不存在,请进行处理!]", id, payOrderId);
-            throw exception(PAY_ORDER_NOT_FOUND);
+            throw exception(cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.ORDER_NOT_FOUND);
         }
         // 校验支付单已支付
         if (!PayOrderStatusEnum.isSuccess(payOrder.getStatus())) {

+ 5 - 6
yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/DictTypeConstants.java

@@ -7,13 +7,12 @@ package cn.iocoder.yudao.module.pay.enums;
  */
 public interface DictTypeConstants {
 
-    String CHANNEL_CODE = "pay_channel_code"; // 支付-渠道名
+    String CHANNEL_CODE = "pay_channel_code"; // 支付渠道编码
 
-    String ORDER_STATUS = "pay_order_status"; // 支付-订单-订单状态
-    String ORDER_NOTIFY_STATUS = "pay_order_notify_status"; // 支付-订单-订单回调商户状态
+    String ORDER_STATUS = "pay_order_status"; // 支付渠道
 
-    String ORDER_REFUND_STATUS = "pay_order_refund_status"; // 支付-订单-订单退款状态
-    String REFUND_ORDER_STATUS = "pay_refund_order_status"; // 支付-退款订单-退款状态
-    String REFUND_ORDER_TYPE = "pay_refund_order_type"; // 支付-退款订单-退款类别
+    String REFUND_STATUS = "pay_order_status"; // 退款状态
+
+    String NOTIFY_STATUS = "pay_notify_status"; // 回调状态
 
 }

+ 31 - 33
yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/ErrorCodeConstants.java

@@ -10,48 +10,46 @@ import cn.iocoder.yudao.framework.common.exception.ErrorCode;
 public interface ErrorCodeConstants {
 
     // ========== APP 模块 1007000000 ==========
-    ErrorCode PAY_APP_NOT_FOUND = new ErrorCode(1007000000, "App 不存在");
-    ErrorCode PAY_APP_IS_DISABLE = new ErrorCode(1007000002, "App 已经被禁用");
-    ErrorCode PAY_APP_EXIST_ORDER_CANT_DELETE =  new ErrorCode(1007000003, "支付应用存在支付订单,无法删除");
-    ErrorCode PAY_APP_EXIST_REFUND_CANT_DELETE =  new ErrorCode(1007000004, "支付应用存在退款订单,无法删除");
+    ErrorCode APP_NOT_FOUND = new ErrorCode(1007000000, "App 不存在");
+    ErrorCode APP_IS_DISABLE = new ErrorCode(1007000002, "App 已经被禁用");
+    ErrorCode APP_EXIST_ORDER_CANT_DELETE =  new ErrorCode(1007000003, "支付应用存在支付订单,无法删除");
+    ErrorCode APP_EXIST_REFUND_CANT_DELETE =  new ErrorCode(1007000004, "支付应用存在退款订单,无法删除");
 
     // ========== CHANNEL 模块 1007001000 ==========
-    ErrorCode PAY_CHANNEL_NOT_FOUND = new ErrorCode(1007001000, "支付渠道的配置不存在");
-    ErrorCode PAY_CHANNEL_IS_DISABLE = new ErrorCode(1007001001, "支付渠道已经禁用");
-    ErrorCode PAY_CHANNEL_CLIENT_NOT_FOUND = new ErrorCode(1007001002, "支付渠道的客户端不存在");
-    ErrorCode CHANNEL_NOT_EXISTS = new ErrorCode(1007001003, "支付渠道不存在");
-    ErrorCode CHANNEL_EXIST_SAME_CHANNEL_ERROR = new ErrorCode(1007001005, "已存在相同的渠道");
+    ErrorCode CHANNEL_NOT_FOUND = new ErrorCode(1007001000, "支付渠道的配置不存在");
+    ErrorCode CHANNEL_IS_DISABLE = new ErrorCode(1007001001, "支付渠道已经禁用");
+    ErrorCode CHANNEL_EXIST_SAME_CHANNEL_ERROR = new ErrorCode(1007001004, "已存在相同的渠道");
 
     // ========== ORDER 模块 1007002000 ==========
-    ErrorCode PAY_ORDER_NOT_FOUND = new ErrorCode(1007002000, "支付订单不存在");
-    ErrorCode PAY_ORDER_STATUS_IS_NOT_WAITING = new ErrorCode(1007002001, "支付订单不处于待支付");
-    ErrorCode PAY_ORDER_STATUS_IS_NOT_SUCCESS = new ErrorCode(1007002002, "支付订单不处于已支付");
-    ErrorCode PAY_ORDER_IS_EXPIRED = new ErrorCode(1007002003, "支付订单已经过期");
-    ErrorCode PAY_ORDER_SUBMIT_CHANNEL_ERROR = new ErrorCode(1007002004, "发起支付报错,错误码:{},错误提示:{}");
+    ErrorCode ORDER_NOT_FOUND = new ErrorCode(1007002000, "支付订单不存在");
+    ErrorCode ORDER_STATUS_IS_NOT_WAITING = new ErrorCode(1007002001, "支付订单不处于待支付");
+    ErrorCode ORDER_STATUS_IS_NOT_SUCCESS = new ErrorCode(1007002002, "支付订单不处于已支付");
+    ErrorCode ORDER_IS_EXPIRED = new ErrorCode(1007002003, "支付订单已经过期");
+    ErrorCode ORDER_SUBMIT_CHANNEL_ERROR = new ErrorCode(1007002004, "发起支付报错,错误码:{},错误提示:{}");
+    ErrorCode ORDER_REFUND_FAIL_STATUS_ERROR = new ErrorCode(1007002005, "支付订单退款失败,原因:状态不是已支付或已退款");
 
     // ========== ORDER 模块(拓展单) 1007003000 ==========
-    ErrorCode PAY_ORDER_EXTENSION_NOT_FOUND = new ErrorCode(1007003000, "支付交易拓展单不存在");
-    ErrorCode PAY_ORDER_EXTENSION_STATUS_IS_NOT_WAITING = new ErrorCode(1007003001, "支付交易拓展单不处于待支付");
+    ErrorCode ORDER_EXTENSION_NOT_FOUND = new ErrorCode(1007003000, "支付交易拓展单不存在");
+    ErrorCode ORDER_EXTENSION_STATUS_IS_NOT_WAITING = new ErrorCode(1007003001, "支付交易拓展单不处于待支付");
 
     // ========== 支付模块(退款) 1007006000 ==========
-    ErrorCode PAY_REFUND_PRICE_EXCEED = new ErrorCode(1007006000, "退款金额超过订单可退款金额");
-    ErrorCode PAY_REFUND_ALL_REFUNDED = new ErrorCode(1007006001, "订单已经全额退款");
-    ErrorCode PAY_REFUND_HAS_REFUNDING = new ErrorCode(1007006002, "已经有退款在处理中");
-    ErrorCode PAY_REFUND_EXISTS = new ErrorCode(1007006003, "已经存在退款单");
-    ErrorCode PAY_REFUND_NOT_FOUND = new ErrorCode(1007006004, "支付退款单不存在");
-    ErrorCode PAY_REFUND_STATUS_IS_NOT_WAITING = new ErrorCode(1007006005, "支付退款单不处于待退款");
+    ErrorCode REFUND_PRICE_EXCEED = new ErrorCode(1007006000, "退款金额超过订单可退款金额");
+    ErrorCode REFUND_HAS_REFUNDING = new ErrorCode(1007006002, "已经有退款在处理中");
+    ErrorCode REFUND_EXISTS = new ErrorCode(1007006003, "已经存在退款单");
+    ErrorCode REFUND_NOT_FOUND = new ErrorCode(1007006004, "支付退款单不存在");
+    ErrorCode REFUND_STATUS_IS_NOT_WAITING = new ErrorCode(1007006005, "支付退款单不处于待退款");
 
     // ========== 示例订单 1007900000 ==========
-    ErrorCode PAY_DEMO_ORDER_NOT_FOUND = new ErrorCode(1007900000, "示例订单不存在");
-    ErrorCode PAY_DEMO_ORDER_UPDATE_PAID_STATUS_NOT_UNPAID = new ErrorCode(1007900001, "示例订单更新支付状态失败,订单不是【未支付】状态");
-    ErrorCode PAY_DEMO_ORDER_UPDATE_PAID_FAIL_PAY_ORDER_ID_ERROR = new ErrorCode(1007900002, "示例订单更新支付状态失败,支付单编号不匹配");
-    ErrorCode PAY_DEMO_ORDER_UPDATE_PAID_FAIL_PAY_ORDER_STATUS_NOT_SUCCESS = new ErrorCode(1007900003, "示例订单更新支付状态失败,支付单状态不是【支付成功】状态");
-    ErrorCode PAY_DEMO_ORDER_UPDATE_PAID_FAIL_PAY_PRICE_NOT_MATCH = new ErrorCode(1007900004, "示例订单更新支付状态失败,支付单金额不匹配");
-    ErrorCode PAY_DEMO_ORDER_REFUND_FAIL_NOT_PAID = new ErrorCode(1007900005, "发起退款失败,示例订单未支付");
-    ErrorCode PAY_DEMO_ORDER_REFUND_FAIL_REFUNDED = new ErrorCode(1007900006, "发起退款失败,示例订单已退款");
-    ErrorCode PAY_DEMO_ORDER_REFUND_FAIL_REFUND_NOT_FOUND = new ErrorCode(1007900007, "发起退款失败,退款订单不存在");
-    ErrorCode PAY_DEMO_ORDER_REFUND_FAIL_REFUND_NOT_SUCCESS = new ErrorCode(1007900008, "发起退款失败,退款订单未退款成功");
-    ErrorCode PAY_DEMO_ORDER_REFUND_FAIL_REFUND_ORDER_ID_ERROR = new ErrorCode(1007900009, "发起退款失败,退款单编号不匹配");
-    ErrorCode PAY_DEMO_ORDER_REFUND_FAIL_REFUND_PRICE_NOT_MATCH = new ErrorCode(1007900010, "发起退款失败,退款单金额不匹配");
+    ErrorCode DEMO_ORDER_NOT_FOUND = new ErrorCode(1007900000, "示例订单不存在");
+    ErrorCode DEMO_ORDER_UPDATE_PAID_STATUS_NOT_UNPAID = new ErrorCode(1007900001, "示例订单更新支付状态失败,订单不是【未支付】状态");
+    ErrorCode DEMO_ORDER_UPDATE_PAID_FAIL_PAY_ORDER_ID_ERROR = new ErrorCode(1007900002, "示例订单更新支付状态失败,支付单编号不匹配");
+    ErrorCode DEMO_ORDER_UPDATE_PAID_FAIL_PAY_ORDER_STATUS_NOT_SUCCESS = new ErrorCode(1007900003, "示例订单更新支付状态失败,支付单状态不是【支付成功】状态");
+    ErrorCode DEMO_ORDER_UPDATE_PAID_FAIL_PAY_PRICE_NOT_MATCH = new ErrorCode(1007900004, "示例订单更新支付状态失败,支付单金额不匹配");
+    ErrorCode DEMO_ORDER_REFUND_FAIL_NOT_PAID = new ErrorCode(1007900005, "发起退款失败,示例订单未支付");
+    ErrorCode DEMO_ORDER_REFUND_FAIL_REFUNDED = new ErrorCode(1007900006, "发起退款失败,示例订单已退款");
+    ErrorCode DEMO_ORDER_REFUND_FAIL_REFUND_NOT_FOUND = new ErrorCode(1007900007, "发起退款失败,退款订单不存在");
+    ErrorCode DEMO_ORDER_REFUND_FAIL_REFUND_NOT_SUCCESS = new ErrorCode(1007900008, "发起退款失败,退款订单未退款成功");
+    ErrorCode DEMO_ORDER_REFUND_FAIL_REFUND_ORDER_ID_ERROR = new ErrorCode(1007900009, "发起退款失败,退款单编号不匹配");
+    ErrorCode DEMO_ORDER_REFUND_FAIL_REFUND_PRICE_NOT_MATCH = new ErrorCode(1007900010, "发起退款失败,退款单金额不匹配");
 
 }

+ 5 - 5
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/enums/notify/PayNotifyStatusEnum.java → yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/notify/PayNotifyStatusEnum.java

@@ -12,11 +12,11 @@ import lombok.Getter;
 @AllArgsConstructor
 public enum PayNotifyStatusEnum {
 
-    WAITING(1, "等待通知"),
-    SUCCESS(2, "通知成功"),
-    FAILURE(3, "通知失败"), // 多次尝试,彻底失败
-    REQUEST_SUCCESS(4, "请求成功,但是结果失败"),
-    REQUEST_FAILURE(5, "请求失败"),
+    WAITING(0, "等待通知"),
+    SUCCESS(10, "通知成功"),
+    FAILURE(20, "通知失败"), // 多次尝试,彻底失败
+    REQUEST_SUCCESS(21, "请求成功,但是结果失败"),
+    REQUEST_FAILURE(22, "请求失败"),
 
     ;
 

+ 0 - 0
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/enums/notify/PayNotifyTypeEnum.java → yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/notify/PayNotifyTypeEnum.java


+ 14 - 1
yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/order/PayOrderStatusEnum.java

@@ -1,6 +1,7 @@
 package cn.iocoder.yudao.module.pay.enums.order;
 
 import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
+import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
 import lombok.AllArgsConstructor;
 import lombok.Getter;
 
@@ -17,7 +18,8 @@ public enum PayOrderStatusEnum implements IntArrayValuable {
 
     WAITING(0, "未支付"),
     SUCCESS(10, "支付成功"),
-    CLOSED(20, "支付关闭"), // 未付款交易超时关闭,或支付完成后全额退款
+    REFUND(20, "已退款"),
+    CLOSED(30, "支付关闭"), // 注意:全部退款后,还是 REFUND 状态
     ;
 
     private final Integer status;
@@ -38,6 +40,17 @@ public enum PayOrderStatusEnum implements IntArrayValuable {
         return Objects.equals(status, SUCCESS.getStatus());
     }
 
+    /**
+     * 判断是否支付成功或者已退款
+     *
+     * @param status 状态
+     * @return 是否支付成功或者已退款
+     */
+    public static boolean isSuccessOrRefund(Integer status) {
+        return ObjectUtils.equalsAny(status,
+                SUCCESS.getStatus(), REFUND.getStatus());
+    }
+
     /**
      * 判断是否支付关闭
      *

+ 4 - 0
yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/refund/PayRefundStatusEnum.java

@@ -25,4 +25,8 @@ public enum PayRefundStatusEnum {
         return Objects.equals(status, SUCCESS.getStatus());
     }
 
+    public static boolean isFailure(Integer status) {
+        return Objects.equals(status, FAILURE.getStatus());
+    }
+
 }

+ 8 - 0
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/app/PayAppController.java

@@ -97,4 +97,12 @@ public class PayAppController {
         return success(PayAppConvert.INSTANCE.convertPage(pageResult, channels));
     }
 
+    @GetMapping("/list")
+    @Operation(summary = "获得应用列表")
+    @PreAuthorize("@ss.hasPermission('pay:merchant:query')")
+    public CommonResult<List<PayAppRespVO>> getAppList() {
+        List<PayAppDO> appListDO = appService.getAppList();
+        return success(PayAppConvert.INSTANCE.convertList(appListDO));
+    }
+
 }

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

@@ -1,26 +1,43 @@
 package cn.iocoder.yudao.module.pay.controller.admin.notify;
 
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
 import cn.iocoder.yudao.framework.pay.core.client.PayClient;
 import cn.iocoder.yudao.framework.pay.core.client.PayClientFactory;
 import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderRespDTO;
 import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundRespDTO;
+import cn.iocoder.yudao.module.pay.controller.admin.notify.vo.PayNotifyTaskDetailRespVO;
+import cn.iocoder.yudao.module.pay.controller.admin.notify.vo.PayNotifyTaskPageReqVO;
+import cn.iocoder.yudao.module.pay.controller.admin.notify.vo.PayNotifyTaskRespVO;
+import cn.iocoder.yudao.module.pay.convert.notify.PayNotifyTaskConvert;
+import cn.iocoder.yudao.module.pay.dal.dataobject.app.PayAppDO;
+import cn.iocoder.yudao.module.pay.dal.dataobject.notify.PayNotifyLogDO;
+import cn.iocoder.yudao.module.pay.dal.dataobject.notify.PayNotifyTaskDO;
+import cn.iocoder.yudao.module.pay.service.app.PayAppService;
+import cn.iocoder.yudao.module.pay.service.notify.PayNotifyService;
 import cn.iocoder.yudao.module.pay.service.order.PayOrderService;
 import cn.iocoder.yudao.module.pay.service.refund.PayRefundService;
 import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
 import io.swagger.v3.oas.annotations.tags.Tag;
 import lombok.extern.slf4j.Slf4j;
+import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.*;
 
 import javax.annotation.Resource;
 import javax.annotation.security.PermitAll;
+import javax.validation.Valid;
+import java.util.List;
 import java.util.Map;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
-import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.PAY_CHANNEL_CLIENT_NOT_FOUND;
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
+import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.CHANNEL_NOT_FOUND;
 
-@Tag(name = "管理后台 - 支付通知")
+@Tag(name = "管理后台 - 回调通知")
 @RestController
 @RequestMapping("/pay/notify")
 @Validated
@@ -31,6 +48,10 @@ public class PayNotifyController {
     private PayOrderService orderService;
     @Resource
     private PayRefundService refundService;
+    @Resource
+    private PayNotifyService notifyService;
+    @Resource
+    private PayAppService appService;
 
     @Resource
     private PayClientFactory payClientFactory;
@@ -47,7 +68,7 @@ public class PayNotifyController {
         PayClient payClient = payClientFactory.getPayClient(channelId);
         if (payClient == null) {
             log.error("[notifyCallback][渠道编号({}) 找不到对应的支付客户端]", channelId);
-            throw exception(PAY_CHANNEL_CLIENT_NOT_FOUND);
+            throw exception(CHANNEL_NOT_FOUND);
         }
 
         // 2. 解析通知数据
@@ -68,7 +89,7 @@ public class PayNotifyController {
         PayClient payClient = payClientFactory.getPayClient(channelId);
         if (payClient == null) {
             log.error("[notifyCallback][渠道编号({}) 找不到对应的支付客户端]", channelId);
-            throw exception(PAY_CHANNEL_CLIENT_NOT_FOUND);
+            throw exception(CHANNEL_NOT_FOUND);
         }
 
         // 2. 解析通知数据
@@ -77,4 +98,29 @@ public class PayNotifyController {
         return "success";
     }
 
+    @GetMapping("/get-detail")
+    @Operation(summary = "获得回调通知的明细")
+    @Parameter(name = "id", description = "编号", required = true, example = "1024")
+    @PreAuthorize("@ss.hasPermission('pay:notify:query')")
+    public CommonResult<PayNotifyTaskDetailRespVO> getNotifyTaskDetail(@RequestParam("id") Long id) {
+        PayNotifyTaskDO task = notifyService.getNotifyTask(id);
+        if (task == null) {
+            return success(null);
+        }
+        // 拼接返回
+        PayAppDO app = appService.getApp(task.getAppId());
+        List<PayNotifyLogDO> logs = notifyService.getNotifyLogList(id);
+        return success(PayNotifyTaskConvert.INSTANCE.convert(task, app, logs));
+    }
+
+    @GetMapping("/page")
+    @Operation(summary = "获得回调通知分页")
+    @PreAuthorize("@ss.hasPermission('pay:notify:query')")
+    public CommonResult<PageResult<PayNotifyTaskRespVO>> getNotifyTaskPage(@Valid PayNotifyTaskPageReqVO pageVO) {
+        PageResult<PayNotifyTaskDO> pageResult = notifyService.getNotifyTaskPage(pageVO);
+        // 拼接返回
+        Map<Long, PayAppDO> appMap = appService.getAppMap(convertList(pageResult.getList(), PayNotifyTaskDO::getAppId));
+        return success(PayNotifyTaskConvert.INSTANCE.convertPage(pageResult, appMap));
+    }
+
 }

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

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

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

@@ -0,0 +1,54 @@
+
+package cn.iocoder.yudao.module.pay.controller.admin.notify.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+@Schema(description = "管理后台 - 回调通知的明细 Response VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class PayNotifyTaskDetailRespVO extends PayNotifyTaskBaseVO {
+
+    @Schema(description = "任务编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "3380")
+    private Long id;
+
+    @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
+    private LocalDateTime createTime;
+
+    @Schema(description = "更新时间", requiredMode = Schema.RequiredMode.REQUIRED)
+    private LocalDateTime updateTime;
+
+    @Schema(description = "应用名称", example = "wx_pay")
+    private String appName;
+
+    @Schema(description = "回调日志列表")
+    private List<Log> logs;
+
+    @Schema(description = "管理后台 - 回调日志")
+    @Data
+    public static class Log {
+
+        @Schema(description = "日志编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "8848")
+        private Long id;
+
+        @Schema(description = "通知状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+        private Byte status;
+
+        @Schema(description = "当前通知次数", requiredMode = Schema.RequiredMode.REQUIRED)
+        private Byte notifyTimes;
+
+        @Schema(description = "HTTP 响应结果", requiredMode = Schema.RequiredMode.REQUIRED)
+        private String response;
+
+        @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
+        private LocalDateTime createTime;
+
+    }
+
+}

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

@@ -0,0 +1,39 @@
+package cn.iocoder.yudao.module.pay.controller.admin.notify.vo;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.time.LocalDateTime;
+
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
+@Schema(description = "管理后台 - 回调通知分页 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class PayNotifyTaskPageReqVO extends PageParam {
+
+    @Schema(description = "应用编号", example = "10636")
+    private Long appId;
+
+    @Schema(description = "通知类型", example = "2")
+    private Byte type;
+
+    @Schema(description = "数据编号", example = "6722")
+    private Long dataId;
+
+    @Schema(description = "通知状态", example = "1")
+    private Byte status;
+
+    @Schema(description = "商户订单编号", example = "26697")
+    private String merchantOrderId;
+
+    @Schema(description = "创建时间")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    private LocalDateTime[] createTime;
+
+}

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

@@ -0,0 +1,22 @@
+package cn.iocoder.yudao.module.pay.controller.admin.notify.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.*;
+import java.time.LocalDateTime;
+
+@Schema(description = "管理后台 - 回调通知 Response VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class PayNotifyTaskRespVO extends PayNotifyTaskBaseVO {
+
+    @Schema(description = "任务编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "3380")
+    private Long id;
+
+    @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
+    private LocalDateTime createTime;
+
+    @Schema(description = "应用名称", example = "wx_pay")
+    private String  appName;
+
+}

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

@@ -1,20 +1,16 @@
 package cn.iocoder.yudao.module.pay.controller.admin.order;
 
 import cn.hutool.core.collection.CollectionUtil;
-import cn.hutool.core.util.ObjectUtil;
 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.channel.PayChannelEnum;
 import cn.iocoder.yudao.module.pay.controller.admin.order.vo.*;
 import cn.iocoder.yudao.module.pay.convert.order.PayOrderConvert;
 import cn.iocoder.yudao.module.pay.dal.dataobject.app.PayAppDO;
 import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderDO;
 import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderExtensionDO;
 import cn.iocoder.yudao.module.pay.service.app.PayAppService;
-import cn.iocoder.yudao.module.pay.service.order.PayOrderExtensionService;
 import cn.iocoder.yudao.module.pay.service.order.PayOrderService;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.Parameter;
@@ -32,6 +28,7 @@ import java.util.List;
 import java.util.Map;
 
 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
 import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getClientIP;
 import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT;
 
@@ -42,9 +39,7 @@ import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.E
 public class PayOrderController {
 
     @Resource
-    private PayOrderService payOrderService;
-    @Resource
-    private PayOrderExtensionService orderExtensionService;
+    private PayOrderService orderService;
     @Resource
     private PayAppService appService;
 
@@ -53,40 +48,29 @@ public class PayOrderController {
     @Parameter(name = "id", description = "编号", required = true, example = "1024")
     @PreAuthorize("@ss.hasPermission('pay:order:query')")
     public CommonResult<PayOrderRespVO> getOrder(@RequestParam("id") Long id) {
-        return success(PayOrderConvert.INSTANCE.convert(payOrderService.getOrder(id)));
+        return success(PayOrderConvert.INSTANCE.convert(orderService.getOrder(id)));
     }
 
-    // TODO 芋艿:看看怎么优化下;
     @GetMapping("/get-detail")
     @Operation(summary = "获得支付订单详情")
     @Parameter(name = "id", description = "编号", required = true, example = "1024")
     @PreAuthorize("@ss.hasPermission('pay:order:query')")
     public CommonResult<PayOrderDetailsRespVO> getOrderDetail(@RequestParam("id") Long id) {
-        PayOrderDO order = payOrderService.getOrder(id);
-        if (ObjectUtil.isNull(order)) {
-            return success(new PayOrderDetailsRespVO());
-        }
-
-        PayAppDO appDO = appService.getApp(order.getAppId());
-        PayChannelEnum channelEnum = PayChannelEnum.getByCode(order.getChannelCode());
-
-        // TODO @aquan:文案,都是前端 format;
-        PayOrderDetailsRespVO respVO = PayOrderConvert.INSTANCE.orderDetailConvert(order);
-        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));
+        PayOrderDO order = orderService.getOrder(id);
+        if (order == null) {
+            return success(null);
         }
 
-        return success(respVO);
+        // 拼接返回
+        PayAppDO app = appService.getApp(order.getAppId());
+        PayOrderExtensionDO orderExtension = orderService.getOrderExtension(order.getExtensionId());
+        return success(PayOrderConvert.INSTANCE.convert(order, orderExtension, app));
     }
 
     @PostMapping("/submit")
     @Operation(summary = "提交支付订单")
     public CommonResult<PayOrderSubmitRespVO> submitPayOrder(@RequestBody PayOrderSubmitReqVO reqVO) {
-        PayOrderSubmitRespVO respVO = payOrderService.submitOrder(reqVO, getClientIP());
+        PayOrderSubmitRespVO respVO = orderService.submitOrder(reqVO, getClientIP());
         return success(respVO);
     }
 
@@ -94,62 +78,34 @@ public class PayOrderController {
     @Operation(summary = "获得支付订单分页")
     @PreAuthorize("@ss.hasPermission('pay:order:query')")
     public CommonResult<PageResult<PayOrderPageItemRespVO>> getOrderPage(@Valid PayOrderPageReqVO pageVO) {
-        PageResult<PayOrderDO> pageResult = payOrderService.getOrderPage(pageVO);
+        PageResult<PayOrderDO> pageResult = orderService.getOrderPage(pageVO);
         if (CollectionUtil.isEmpty(pageResult.getList())) {
             return success(new PageResult<>(pageResult.getTotal()));
         }
 
-        // 处理应用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 -> {
-            PayAppDO appDO = appMap.get(c.getAppId());
-            PayChannelEnum channelEnum = PayChannelEnum.getByCode(c.getChannelCode());
-
-            PayOrderPageItemRespVO orderItem = PayOrderConvert.INSTANCE.pageConvertItemPage(c);
-            orderItem.setAppName(ObjectUtil.isNotNull(appDO) ? appDO.getName() : "未知应用");
-            orderItem.setChannelCodeName(ObjectUtil.isNotNull(channelEnum) ? channelEnum.getName() : "未知渠道");
-            pageList.add(orderItem);
-        });
-        return success(new PageResult<>(pageList, pageResult.getTotal()));
+        // 拼接返回
+        Map<Long, PayAppDO> appMap = appService.getAppMap(convertList(pageResult.getList(), PayOrderDO::getAppId));
+        return success(PayOrderConvert.INSTANCE.convertPage(pageResult, appMap));
     }
 
     @GetMapping("/export-excel")
-    @Operation(summary = "导出支付订单Excel")
+    @Operation(summary = "导出支付订单 Excel")
     @PreAuthorize("@ss.hasPermission('pay:order:export')")
     @OperateLog(type = EXPORT)
     public void exportOrderExcel(@Valid PayOrderExportReqVO exportReqVO,
             HttpServletResponse response) throws IOException {
-        List<PayOrderDO> list = payOrderService.getOrderList(exportReqVO);
+        List<PayOrderDO> list = orderService.getOrderList(exportReqVO);
         if (CollectionUtil.isEmpty(list)) {
             ExcelUtils.write(response, "支付订单.xls", "数据",
                     PayOrderExcelVO.class, new ArrayList<>());
+            return;
         }
 
-        // 处理应用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 -> {
-            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.setAppName(ObjectUtil.isNotNull(appDO) ? appDO.getName() : "未知应用");
-            excelItem.setChannelCodeName(ObjectUtil.isNotNull(channelEnum) ? channelEnum.getName() : "未知渠道");
-            excelItem.setNo(ObjectUtil.isNotNull(orderExtensionDO) ? orderExtensionDO.getNo() : "");
-            excelDatum.add(excelItem);
-        });
-
+        // 拼接返回
+        Map<Long, PayAppDO> appMap = appService.getAppMap(convertList(list, PayOrderDO::getAppId));
+        List<PayOrderExcelVO> excelList = PayOrderConvert.INSTANCE.convertList(list, appMap);
         // 导出 Excel
-        ExcelUtils.write(response, "支付订单.xls", "数据", PayOrderExcelVO.class, excelDatum);
+        ExcelUtils.write(response, "支付订单.xls", "数据", PayOrderExcelVO.class, excelList);
     }
 
 }

+ 20 - 33
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/vo/PayOrderBaseVO.java

@@ -17,51 +17,47 @@ import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_
 @Data
 public class PayOrderBaseVO {
 
-    @Schema(description = "应用编号", requiredMode = Schema.RequiredMode.REQUIRED)
+    @Schema(description = "应用编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
     @NotNull(message = "应用编号不能为空")
     private Long appId;
 
-    @Schema(description = "渠道编号")
+    @Schema(description = "渠道编号", example = "2048")
     private Long channelId;
 
-    @Schema(description = "渠道编码")
+    @Schema(description = "渠道编码", example = "wx_app")
     private String channelCode;
 
-    @Schema(description = "商户订单编号", requiredMode = Schema.RequiredMode.REQUIRED)
+    @Schema(description = "商户订单编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "888")
     @NotNull(message = "商户订单编号不能为空")
     private String merchantOrderId;
 
-    @Schema(description = "商品标题", requiredMode = Schema.RequiredMode.REQUIRED)
+    @Schema(description = "商品标题", requiredMode = Schema.RequiredMode.REQUIRED, example = "土豆")
     @NotNull(message = "商品标题不能为空")
     private String subject;
 
-    @Schema(description = "商品描述", requiredMode = Schema.RequiredMode.REQUIRED)
+    @Schema(description = "商品描述", requiredMode = Schema.RequiredMode.REQUIRED, example = "我是土豆")
     @NotNull(message = "商品描述不能为空")
     private String body;
 
-    @Schema(description = "异步通知地址", requiredMode = Schema.RequiredMode.REQUIRED)
+    @Schema(description = "异步通知地址", requiredMode = Schema.RequiredMode.REQUIRED, example = "http://127.0.0.1:48080/pay/notify")
     @NotNull(message = "异步通知地址不能为空")
     private String notifyUrl;
 
-    @Schema(description = "通知商户支付结果的回调状态", requiredMode = Schema.RequiredMode.REQUIRED)
-    @NotNull(message = "通知商户支付结果的回调状态不能为空")
-    private Integer notifyStatus;
-
-    @Schema(description = "支付金额,单位:分", requiredMode = Schema.RequiredMode.REQUIRED)
+    @Schema(description = "支付金额,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
     @NotNull(message = "支付金额,单位:分不能为空")
     private Long price;
 
-    @Schema(description = "渠道手续费,单位:百分比")
+    @Schema(description = "渠道手续费,单位:百分比", example = "10")
     private Double channelFeeRate;
 
-    @Schema(description = "渠道手续金额,单位:分")
-    private Long channelFeePrice;
+    @Schema(description = "渠道手续金额,单位:分", example = "100")
+    private Integer channelFeePrice;
 
-    @Schema(description = "支付状态", requiredMode = Schema.RequiredMode.REQUIRED)
+    @Schema(description = "支付状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
     @NotNull(message = "支付状态不能为空")
     private Integer status;
 
-    @Schema(description = "用户 IP", requiredMode = Schema.RequiredMode.REQUIRED)
+    @Schema(description = "用户 IP", requiredMode = Schema.RequiredMode.REQUIRED, example = "127.0.0.1")
     @NotNull(message = "用户 IP不能为空")
     private String userIp;
 
@@ -74,29 +70,20 @@ public class PayOrderBaseVO {
     @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
     private LocalDateTime successTime;
 
-    @Schema(description = "订单支付通知时间")
-    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
-    private LocalDateTime notifyTime;
-
-    @Schema(description = "支付成功的订单拓展单编号")
-    private Long successExtensionId;
-
-    @Schema(description = "退款状态", requiredMode = Schema.RequiredMode.REQUIRED)
-    @NotNull(message = "退款状态不能为空")
-    private Integer refundStatus;
+    @Schema(description = "支付成功的订单拓展单编号", example = "50")
+    private Long extensionId;
 
-    @Schema(description = "退款次数", requiredMode = Schema.RequiredMode.REQUIRED)
-    @NotNull(message = "退款次数不能为空")
-    private Integer refundTimes;
+    @Schema(description = "支付订单号", example = "2048888")
+    private String no;
 
-    @Schema(description = "退款总金额,单位:分", requiredMode = Schema.RequiredMode.REQUIRED)
+    @Schema(description = "退款总金额,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
     @NotNull(message = "退款总金额,单位:分不能为空")
     private Long refundPrice;
 
-    @Schema(description = "渠道用户编号")
+    @Schema(description = "渠道用户编号", example = "2048")
     private String channelUserId;
 
-    @Schema(description = "渠道订单号")
+    @Schema(description = "渠道订单号", example = "4096")
     private String channelOrderNo;
 
 }

+ 9 - 8
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/vo/PayOrderDetailsRespVO.java

@@ -13,32 +13,33 @@ import java.time.LocalDateTime;
 @ToString(callSuper = true)
 public class PayOrderDetailsRespVO extends PayOrderBaseVO {
 
-    @Schema(description = "支付订单编号")
+    @Schema(description = "支付订单编号", required = true, example = "1024")
     private Long id;
 
-    @Schema(description = "应用名称")
+    @Schema(description = "应用名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋道源码")
     private String appName;
 
-    @Schema(description = "渠道编号名称")
-    private String channelCodeName;
-
-    @Schema(description = "创建时间")
+    @Schema(description = "创建时间", required = true)
     private LocalDateTime createTime;
 
+    @Schema(description = "更新时间", required = true)
+    private LocalDateTime updateTime;
+
     /**
      * 支付订单扩展
      */
-    private PayOrderExtension payOrderExtension;
+    private PayOrderExtension extension;
 
     @Data
     @Schema(description = "支付订单扩展")
     public static class PayOrderExtension {
 
-        @Schema(description = "支付订单号")
+        @Schema(description = "支付订单号", required = true, example = "1024")
         private String no;
 
         @Schema(description = "支付异步通知的内容")
         private String channelNotifyData;
+
     }
 
 }

+ 25 - 46
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/vo/PayOrderExcelVO.java

@@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.pay.controller.admin.order.vo;
 
 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
+import cn.iocoder.yudao.framework.excel.core.convert.MoneyConvert;
 import cn.iocoder.yudao.module.pay.enums.DictTypeConstants;
 import com.alibaba.excel.annotation.ExcelProperty;
 import lombok.Data;
@@ -9,53 +10,44 @@ import lombok.Data;
 import java.time.LocalDateTime;
 
 /**
- * 支付订单Excel VO
+ * 支付订单 Excel VO
  *
  * @author aquan
  */
 @Data
 public class PayOrderExcelVO {
 
-    @ExcelProperty("支付订单编号")
+    @ExcelProperty("编号")
     private Long id;
 
-    @ExcelProperty(value = "应用名称")
-    private String appName;
-
-    @ExcelProperty("商品标题")
-    private String subject;
+    @ExcelProperty("创建时间")
+    private LocalDateTime createTime;
 
-    @ExcelProperty("商户订单编号")
-    private String merchantOrderId;
+    @ExcelProperty(value = "支付金额", converter = MoneyConvert.class)
+    private Integer price;
 
-    @ExcelProperty("渠道订单号")
-    private String channelOrderNo;
+    @ExcelProperty(value = "退款金额", converter = MoneyConvert.class)
+    private Integer refundPrice;
 
-    @ExcelProperty(value = "支付订单号")
-    private String no;
+    @ExcelProperty(value = "手续金额", converter = MoneyConvert.class)
+    private Integer channelFeePrice;
 
-    @ExcelProperty("支付金额,单位:元")
-    private String price;
+    @ExcelProperty("商户单号")
+    private String merchantOrderId;
 
-    @ExcelProperty("渠道手续金额,单位:元")
-    private String channelFeePrice;
+    @ExcelProperty(value = "支付单号")
+    private String no;
 
-    @ExcelProperty("渠道手续费,单位:百分比")
-    private String channelFeeRate;
+    @ExcelProperty("渠道单号")
+    private String channelOrderNo;
 
-    @DictFormat(DictTypeConstants.ORDER_STATUS)
     @ExcelProperty(value = "支付状态", converter = DictConvert.class)
+    @DictFormat(DictTypeConstants.ORDER_STATUS)
     private Integer status;
 
-    @DictFormat(DictTypeConstants.ORDER_NOTIFY_STATUS)
-    @ExcelProperty(value = "通知商户支付结果的回调状态", converter = DictConvert.class)
-    private Integer notifyStatus;
-
-    @ExcelProperty("异步通知地址")
-    private String notifyUrl;
-
-    @ExcelProperty("创建时间")
-    private LocalDateTime createTime;
+    @ExcelProperty(value = "渠道编号名称", converter = DictConvert.class)
+    @DictFormat(DictTypeConstants.CHANNEL_CODE)
+    private String channelCode;
 
     @ExcelProperty("订单支付成功时间")
     private LocalDateTime successTime;
@@ -63,24 +55,11 @@ public class PayOrderExcelVO {
     @ExcelProperty("订单失效时间")
     private LocalDateTime expireTime;
 
-    @ExcelProperty("订单支付通知时间")
-    private LocalDateTime notifyTime;
-
-    @ExcelProperty(value = "渠道编号名称")
-    private String channelCodeName;
-
-    @ExcelProperty("用户 IP")
-    private String userIp;
-
-    @DictFormat(DictTypeConstants.ORDER_REFUND_STATUS)
-    @ExcelProperty(value = "退款状态", converter = DictConvert.class)
-    private Integer refundStatus;
-
-    @ExcelProperty("退款次数")
-    private Integer refundTimes;
+    @ExcelProperty(value = "应用名称")
+    private String appName;
 
-    @ExcelProperty("退款总金额,单位:元")
-    private String refundPrice;
+    @ExcelProperty("商品标题")
+    private String subject;
 
     @ExcelProperty("商品描述")
     private String body;

+ 8 - 59
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/vo/PayOrderExportReqVO.java

@@ -12,75 +12,24 @@ import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_
 @Data
 public class PayOrderExportReqVO {
 
-    @Schema(description = "应用编号")
+    @Schema(description = "应用编号", example = "1024")
     private Long appId;
 
-    @Schema(description = "渠道编号")
-    private Long channelId;
-
-    @Schema(description = "渠道编码")
+    @Schema(description = "渠道编码", example = "wx_app")
     private String channelCode;
 
-    @Schema(description = "商户订单编号")
+    @Schema(description = "商户订单编号", example = "4096")
     private String merchantOrderId;
 
-    @Schema(description = "商品标题")
-    private String subject;
-
-    @Schema(description = "商品描述")
-    private String body;
-
-    @Schema(description = "异步通知地址")
-    private String notifyUrl;
-
-    @Schema(description = "通知商户支付结果的回调状态")
-    private Integer notifyStatus;
-
-    @Schema(description = "支付金额,单位:分")
-    private Long price;
-
-    @Schema(description = "渠道手续费,单位:百分比")
-    private Double channelFeeRate;
+    @Schema(description = "渠道编号", example = "1888")
+    private String channelOrderNo;
 
-    @Schema(description = "渠道手续金额,单位:分")
-    private Long channelFeePrice;
+    @Schema(description = "支付单号", example = "2014888")
+    private String no;
 
-    @Schema(description = "支付状态")
+    @Schema(description = "支付状态", example = "0")
     private Integer status;
 
-    @Schema(description = "用户 IP")
-    private String userIp;
-
-    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
-    @Schema(description = "订单失效时间")
-    private LocalDateTime[] expireTime;
-
-    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
-    @Schema(description = "开始订单支付成功时间")
-    private LocalDateTime[] successTime;
-
-    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
-    @Schema(description = "开始订单支付通知时间")
-    private LocalDateTime[] notifyTime;
-
-    @Schema(description = "支付成功的订单拓展单编号")
-    private Long successExtensionId;
-
-    @Schema(description = "退款状态")
-    private Integer refundStatus;
-
-    @Schema(description = "退款次数")
-    private Integer refundTimes;
-
-    @Schema(description = "退款总金额,单位:分")
-    private Long refundPrice;
-
-    @Schema(description = "渠道用户编号")
-    private String channelUserId;
-
-    @Schema(description = "渠道订单号")
-    private String channelOrderNo;
-
     @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
     @Schema(description = "创建时间")
     private LocalDateTime[] createTime;

+ 2 - 9
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/vo/PayOrderPageItemRespVO.java

@@ -13,20 +13,13 @@ import java.time.LocalDateTime;
 @ToString(callSuper = true)
 public class PayOrderPageItemRespVO extends PayOrderBaseVO {
 
-    @Schema(description = "支付订单编号", requiredMode = Schema.RequiredMode.REQUIRED)
+    @Schema(description = "支付订单编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
     private Long id;
 
     @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
     private LocalDateTime createTime;
 
-    @Schema(description = "应用名称")
+    @Schema(description = "应用名称", example = "wx_pay")
     private String  appName;
 
-    @Schema(description = "渠道名称")
-    private String channelCodeName;
-
-    @Schema(description = "支付订单号")
-    private String no;
-
-
 }

+ 8 - 59
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/vo/PayOrderPageReqVO.java

@@ -17,75 +17,24 @@ import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_
 @ToString(callSuper = true)
 public class PayOrderPageReqVO extends PageParam {
 
-    @Schema(description = "应用编号")
+    @Schema(description = "应用编号", example = "1024")
     private Long appId;
 
-    @Schema(description = "渠道编号")
-    private Long channelId;
-
-    @Schema(description = "渠道编码")
+    @Schema(description = "渠道编码", example = "wx_app")
     private String channelCode;
 
-    @Schema(description = "商户订单编号")
+    @Schema(description = "商户订单编号", example = "4096")
     private String merchantOrderId;
 
-    @Schema(description = "商品标题")
-    private String subject;
-
-    @Schema(description = "商品描述")
-    private String body;
-
-    @Schema(description = "异步通知地址")
-    private String notifyUrl;
-
-    @Schema(description = "通知商户支付结果的回调状态")
-    private Integer notifyStatus;
-
-    @Schema(description = "支付金额,单位:分")
-    private Long price;
-
-    @Schema(description = "渠道手续费,单位:百分比")
-    private Double channelFeeRate;
+    @Schema(description = "渠道编号", example = "1888")
+    private String channelOrderNo;
 
-    @Schema(description = "渠道手续金额,单位:分")
-    private Long channelFeePrice;
+    @Schema(description = "支付单号", example = "2014888")
+    private String no;
 
-    @Schema(description = "支付状态")
+    @Schema(description = "支付状态", example = "0")
     private Integer status;
 
-    @Schema(description = "用户 IP")
-    private String userIp;
-
-    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
-    @Schema(description = "订单失效时间")
-    private LocalDateTime[] expireTime;
-
-    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
-    @Schema(description = "订单支付成功时间")
-    private LocalDateTime[] successTime;
-
-    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
-    @Schema(description = "订单支付通知时间")
-    private LocalDateTime[] notifyTime;
-
-    @Schema(description = "支付成功的订单拓展单编号")
-    private Long successExtensionId;
-
-    @Schema(description = "退款状态")
-    private Integer refundStatus;
-
-    @Schema(description = "退款次数")
-    private Integer refundTimes;
-
-    @Schema(description = "退款总金额,单位:分")
-    private Long refundPrice;
-
-    @Schema(description = "渠道用户编号")
-    private String channelUserId;
-
-    @Schema(description = "渠道订单号")
-    private String channelOrderNo;
-
     @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
     @Schema(description = "创建时间")
     private LocalDateTime[] createTime;

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


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

@@ -1,24 +1,20 @@
 package cn.iocoder.yudao.module.pay.controller.admin.refund;
 
 import cn.hutool.core.collection.CollectionUtil;
-import cn.hutool.core.util.ObjectUtil;
+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 cn.iocoder.yudao.module.pay.controller.admin.refund.vo.*;
 import cn.iocoder.yudao.module.pay.convert.refund.PayRefundConvert;
 import cn.iocoder.yudao.module.pay.dal.dataobject.app.PayAppDO;
-import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderDO;
 import cn.iocoder.yudao.module.pay.dal.dataobject.refund.PayRefundDO;
 import cn.iocoder.yudao.module.pay.service.app.PayAppService;
 import cn.iocoder.yudao.module.pay.service.order.PayOrderService;
 import cn.iocoder.yudao.module.pay.service.refund.PayRefundService;
-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.channel.PayChannelEnum;
-import io.swagger.v3.oas.annotations.tags.Tag;
-import io.swagger.v3.oas.annotations.Parameter;
 import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.GetMapping;
@@ -35,6 +31,7 @@ import java.util.List;
 import java.util.Map;
 
 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
 import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT;
 
 @Tag(name = "管理后台 - 退款订单")
@@ -56,20 +53,13 @@ public class PayRefundController {
     @PreAuthorize("@ss.hasPermission('pay:refund:query')")
     public CommonResult<PayRefundDetailsRespVO> getRefund(@RequestParam("id") Long id) {
         PayRefundDO refund = refundService.getRefund(id);
-        if (ObjectUtil.isNull(refund)) {
+        if (refund == null) {
             return success(new PayRefundDetailsRespVO());
         }
 
-        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.setAppName(ObjectUtil.isNotNull(appDO) ? appDO.getName() : "未知应用");
-        refundDetail.setChannelCodeName(ObjectUtil.isNotNull(channelEnum) ? channelEnum.getName() : "未知渠道");
-        refundDetail.setSubject(orderDO.getSubject());
-
-        return success(refundDetail);
+        // 拼接数据
+        PayAppDO app = appService.getApp(refund.getAppId());
+        return success(PayRefundConvert.INSTANCE.convert(refund, app));
     }
 
     @GetMapping("/page")
@@ -82,21 +72,8 @@ public class PayRefundController {
         }
 
         // 处理应用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 -> {
-            PayAppDO appDO = appMap.get(c.getAppId());
-            PayChannelEnum channelEnum = PayChannelEnum.getByCode(c.getChannelCode());
-
-            PayRefundPageItemRespVO item = PayRefundConvert.INSTANCE.pageItemConvert(c);
-
-            item.setAppName(ObjectUtil.isNotNull(appDO) ? appDO.getName() : "未知应用");
-            item.setChannelCodeName(ObjectUtil.isNotNull(channelEnum) ? channelEnum.getName() : "未知渠道");
-            list.add(item);
-        });
-
-        return success(new PageResult<>(list, pageResult.getTotal()));
+        Map<Long, PayAppDO> appMap = appService.getAppMap(convertList(pageResult.getList(), PayRefundDO::getAppId));
+        return success(PayRefundConvert.INSTANCE.convertPage(pageResult, appMap));
     }
 
     @GetMapping("/export-excel")
@@ -105,36 +82,18 @@ public class PayRefundController {
     @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<>());
+            return;
         }
 
-        // 处理应用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 -> {
-            PayAppDO appDO = appMap.get(c.getAppId());
-            PayChannelEnum channelEnum = PayChannelEnum.getByCode(c.getChannelCode());
-
-            PayRefundExcelVO excelItem = PayRefundConvert.INSTANCE.excelConvert(c);
-            excelItem.setAppName(ObjectUtil.isNotNull(appDO) ? appDO.getName() : "未知应用");
-            excelItem.setChannelCodeName(ObjectUtil.isNotNull(channelEnum) ? channelEnum.getName() : "未知渠道");
-            excelItem.setSubject(orderMap.get(c.getOrderId()).getSubject());
-
-            excelDatum.add(excelItem);
-        });
-
+        // 拼接返回
+        Map<Long, PayAppDO> appMap = appService.getAppMap(convertList(list, PayRefundDO::getAppId));
+        List<PayRefundExcelVO> excelList = PayRefundConvert.INSTANCE.convertList(list, appMap);
         // 导出 Excel
-        ExcelUtils.write(response, "退款订单.xls", "数据", PayRefundExcelVO.class, excelDatum);
+        ExcelUtils.write(response, "退款订单.xls", "数据", PayRefundExcelVO.class, excelList);
     }
 
 }

+ 31 - 58
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/refund/vo/PayRefundBaseVO.java

@@ -1,13 +1,10 @@
 package cn.iocoder.yudao.module.pay.controller.admin.refund.vo;
+
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
-import org.springframework.format.annotation.DateTimeFormat;
 
-import javax.validation.constraints.NotNull;
 import java.time.LocalDateTime;
 
-import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
-
 /**
 * 退款订单 Base VO,提供给添加、修改、详细的子 VO 使用
 * 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
@@ -15,91 +12,67 @@ import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_
 @Data
 public class PayRefundBaseVO {
 
-    @Schema(description = "应用编号", requiredMode = Schema.RequiredMode.REQUIRED)
-    @NotNull(message = "应用编号不能为空")
+    @Schema(description = "外部退款号", requiredMode = Schema.RequiredMode.REQUIRED, example = "110")
+    private String no;
+
+    @Schema(description = "应用编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
     private Long appId;
 
-    @Schema(description = "渠道编号", requiredMode = Schema.RequiredMode.REQUIRED)
-    @NotNull(message = "渠道编号不能为空")
+    @Schema(description = "渠道编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048")
     private Long channelId;
 
-    @Schema(description = "渠道编码", requiredMode = Schema.RequiredMode.REQUIRED)
-    @NotNull(message = "渠道编码不能为空")
+    @Schema(description = "渠道编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "wx_app")
     private String channelCode;
 
-    @Schema(description = "支付订单编号 pay_order 表id", requiredMode = Schema.RequiredMode.REQUIRED)
-    @NotNull(message = "支付订单编号 pay_order 表id不能为空")
+    @Schema(description = "订单编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
     private Long orderId;
 
-    @Schema(description = "交易订单号 pay_extension 表no 字段", requiredMode = Schema.RequiredMode.REQUIRED)
-    @NotNull(message = "交易订单号 pay_extension 表no 字段不能为空")
-    private String tradeNo;
+    // ========== 商户相关字段 ==========
 
-    @Schema(description = "商户订单编号(商户系统生成)", requiredMode = Schema.RequiredMode.REQUIRED)
-    @NotNull(message = "商户订单编号(商户系统生成)不能为空")
+    @Schema(description = "商户订单编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "225")
     private String merchantOrderId;
 
-    @Schema(description = "商户退款订单号(商户系统生成)", requiredMode = Schema.RequiredMode.REQUIRED)
-    @NotNull(message = "商户退款订单号(商户系统生成)不能为空")
-    private String merchantRefundNo;
+    @Schema(description = "商户退款订单号", requiredMode = Schema.RequiredMode.REQUIRED, example = "512")
+    private String merchantRefundId;
 
-    @Schema(description = "异步通知商户地址", requiredMode = Schema.RequiredMode.REQUIRED)
-    @NotNull(message = "异步通知商户地址不能为空")
+    @Schema(description = "异步通知地址", requiredMode = Schema.RequiredMode.REQUIRED)
     private String notifyUrl;
 
-    @Schema(description = "通知商户退款结果的回调状态", requiredMode = Schema.RequiredMode.REQUIRED)
-    @NotNull(message = "通知商户退款结果的回调状态不能为空")
-    private Integer notifyStatus;
+    // ========== 退款相关字段 ==========
 
-    @Schema(description = "退款状态", requiredMode = Schema.RequiredMode.REQUIRED)
-    @NotNull(message = "退款状态不能为空")
+    @Schema(description = "退款状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
     private Integer status;
 
-    @Schema(description = "退款类型(部分退款,全部退款)", requiredMode = Schema.RequiredMode.REQUIRED)
-    @NotNull(message = "退款类型(部分退款,全部退款)不能为空")
-    private Integer type;
-
-    @Schema(description = "支付金额,单位分", requiredMode = Schema.RequiredMode.REQUIRED)
-    @NotNull(message = "支付金额,单位分不能为空")
-    private Long payAmount;
+    @Schema(description = "支付金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
+    private Long payPrice;
 
-    @Schema(description = "退款金额,单位分", requiredMode = Schema.RequiredMode.REQUIRED)
-    @NotNull(message = "退款金额,单位分不能为空")
-    private Long refundAmount;
+    @Schema(description = "退款金额,单位分", requiredMode = Schema.RequiredMode.REQUIRED, example = "200")
+    private Long refundPrice;
 
-    @Schema(description = "退款原因", requiredMode = Schema.RequiredMode.REQUIRED)
-    @NotNull(message = "退款原因不能为空")
+    @Schema(description = "退款原因", requiredMode = Schema.RequiredMode.REQUIRED, example = "我要退了")
     private String reason;
 
-    @Schema(description = "用户 IP")
+    @Schema(description = "用户 IP", requiredMode = Schema.RequiredMode.REQUIRED, example = "127.0.0.1")
     private String userIp;
 
-    @Schema(description = "渠道订单号,pay_order 中的channel_order_no 对应", requiredMode = Schema.RequiredMode.REQUIRED)
-    @NotNull(message = "渠道订单号,pay_order 中的channel_order_no 对应不能为空")
+    // ========== 渠道相关字段 ==========
+
+    @Schema(description = "渠道订单号", requiredMode = Schema.RequiredMode.REQUIRED, example = "233")
     private String channelOrderNo;
 
-    @Schema(description = "渠道退款单号,渠道返回")
+    @Schema(description = "渠道退款单号", example = "2022")
     private String channelRefundNo;
 
-    @Schema(description = "渠道调用报错时,错误码")
+    @Schema(description = "退款成功时间")
+    private LocalDateTime successTime;
+
+    @Schema(description = "调用渠道的错误码")
     private String channelErrorCode;
 
-    @Schema(description = "渠道调用报错时,错误信息")
+    @Schema(description = "调用渠道的错误提示")
     private String channelErrorMsg;
 
     @Schema(description = "支付渠道的额外参数")
-    private String channelExtras;
-
-    @Schema(description = "退款失效时间")
-    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
-    private LocalDateTime expireTime;
-
-    @Schema(description = "退款成功时间")
-    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
-    private LocalDateTime successTime;
-
-    @Schema(description = "退款通知时间")
-    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
-    private LocalDateTime notifyTime;
+    private String channelNotifyData;
 
 }

+ 0 - 11
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/refund/vo/PayRefundCreateReqVO.java

@@ -1,11 +0,0 @@
-package cn.iocoder.yudao.module.pay.controller.admin.refund.vo;
-import io.swagger.v3.oas.annotations.media.Schema;
-import lombok.*;
-
-@Schema(description = "管理后台 - 退款订单创建 Request VO")
-@Data
-@EqualsAndHashCode(callSuper = true)
-@ToString(callSuper = true)
-public class PayRefundCreateReqVO extends PayRefundBaseVO {
-
-}

+ 12 - 7
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/refund/vo/PayRefundDetailsRespVO.java

@@ -5,7 +5,6 @@ import lombok.Data;
 import lombok.EqualsAndHashCode;
 import lombok.ToString;
 
-import javax.validation.constraints.NotNull;
 import java.time.LocalDateTime;
 
 @Schema(description = "管理后台 - 退款订单详情 Response VO")
@@ -17,14 +16,11 @@ public class PayRefundDetailsRespVO extends PayRefundBaseVO {
     @Schema(description = "支付退款编号", requiredMode = Schema.RequiredMode.REQUIRED)
     private Long id;
 
-    @Schema(description = "应用名称")
+    @Schema(description = "应用名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "我是芋艿")
     private String appName;
 
-    @Schema(description = "渠道编号名称")
-    private String channelCodeName;
-
-    @NotNull(message = "商品标题不能为空")
-    private String subject;
+    @Schema(description = "支付订单", requiredMode = Schema.RequiredMode.REQUIRED)
+    private Order order;
 
     @Schema(description = "创建时间")
     private LocalDateTime createTime;
@@ -32,4 +28,13 @@ public class PayRefundDetailsRespVO extends PayRefundBaseVO {
     @Schema(description = "更新时间")
     private LocalDateTime updateTime;
 
+    @Schema(description = "管理后台 - 支付订单")
+    @Data
+    public static class Order {
+
+        @Schema(description = "商品标题", requiredMode = Schema.RequiredMode.REQUIRED, example = "土豆")
+        private String subject;
+
+    }
+
 }

+ 24 - 45
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/refund/vo/PayRefundExcelVO.java

@@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.pay.controller.admin.refund.vo;
 
 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
+import cn.iocoder.yudao.framework.excel.core.convert.MoneyConvert;
 import cn.iocoder.yudao.module.pay.enums.DictTypeConstants;
 import com.alibaba.excel.annotation.ExcelProperty;
 import lombok.Data;
@@ -19,64 +20,42 @@ public class PayRefundExcelVO {
     @ExcelProperty("支付退款编号")
     private Long id;
 
-    @ExcelProperty("商品名称")
-    private String subject;
+    @ExcelProperty("创建时间")
+    private LocalDateTime createTime;
 
-    @ExcelProperty(value = "应用名称")
-    private String appName;
+    @ExcelProperty(value = "支付金额", converter = MoneyConvert.class)
+    private Integer payPrice;
 
-    @ExcelProperty(value = "渠道编号名称")
-    private String channelCodeName;
+    @ExcelProperty(value = "退款金额", converter = MoneyConvert.class)
+    private Integer refundPrice;
 
-    @ExcelProperty("交易订单号")
-    private String tradeNo;
+    @ExcelProperty("商户退款单号")
+    private String merchantRefundId;
+    @ExcelProperty("退款单号")
+    private String no;
+    @ExcelProperty("渠道退款单号")
+    private String channelRefundNo;
 
-    @ExcelProperty("商户订单编号")
+    @ExcelProperty("商户支付单号")
     private String merchantOrderId;
+    @ExcelProperty("渠道支付单号")
+    private String channelOrderNo;
 
-    @ExcelProperty("商户退款订单号")
-    private String merchantRefundNo;
-
-    @ExcelProperty("异步通知商户地址")
-    private String notifyUrl;
-
-    @DictFormat(DictTypeConstants.ORDER_NOTIFY_STATUS)
-    @ExcelProperty(value = "商户退款结果回调状态", converter = DictConvert.class)
-    private Integer notifyStatus;
-
-    @DictFormat(DictTypeConstants.REFUND_ORDER_STATUS)
     @ExcelProperty(value = "退款状态", converter = DictConvert.class)
+    @DictFormat(DictTypeConstants.REFUND_STATUS)
     private Integer status;
 
-    @DictFormat(DictTypeConstants.REFUND_ORDER_TYPE)
-    @ExcelProperty(value = "退款类型", converter = DictConvert.class)
-    private Integer type;
+    @ExcelProperty(value = "退款渠道", converter = DictConvert.class)
+    @DictFormat(DictTypeConstants.CHANNEL_CODE)
+    private String channelCode;
 
-    @ExcelProperty("支付金额,单位:元")
-    private String payPrice;
+    @ExcelProperty("成功时间")
+    private LocalDateTime successTime;
 
-    @ExcelProperty("退款金额,单位:元")
-    private String refundPrice;
+    @ExcelProperty(value = "支付应用")
+    private String appName;
 
     @ExcelProperty("退款原因")
     private String reason;
 
-    @ExcelProperty("用户付款 IP")
-    private String userIp;
-
-    @ExcelProperty("渠道订单号")
-    private String channelOrderNo;
-
-    @ExcelProperty("渠道退款单号")
-    private String channelRefundNo;
-
-    @ExcelProperty("创建时间")
-    private LocalDateTime createTime;
-
-    @ExcelProperty("退款成功时间")
-    private LocalDateTime successTime;
-
-    @ExcelProperty("退款失效时间")
-    private LocalDateTime expireTime;
-
 }

+ 9 - 60
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/refund/vo/PayRefundExportReqVO.java

@@ -12,77 +12,26 @@ import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_
 @Data
 public class PayRefundExportReqVO {
 
-    @Schema(description = "应用编号")
+    @Schema(description = "应用编号", example = "1024")
     private Long appId;
 
-    @Schema(description = "渠道编号")
-    private Long channelId;
-
-    @Schema(description = "渠道编码")
+    @Schema(description = "渠道编码", example = "wx_app")
     private String channelCode;
 
-    @Schema(description = "支付订单编号 pay_order 表id")
-    private Long orderId;
-
-    @Schema(description = "交易订单号 pay_extension 表no 字段")
-    private String tradeNo;
-
-    @Schema(description = "商户订单编号(商户系统生成)")
+    @Schema(description = "商户支付单号", example = "10")
     private String merchantOrderId;
 
-    @Schema(description = "商户退款订单号(商户系统生成)")
-    private String merchantRefundNo;
-
-    @Schema(description = "异步通知商户地址")
-    private String notifyUrl;
-
-    @Schema(description = "通知商户退款结果的回调状态")
-    private Integer notifyStatus;
-
-    @Schema(description = "退款状态")
-    private Integer status;
-
-    @Schema(description = "退款类型(部分退款,全部退款)")
-    private Integer type;
-
-    @Schema(description = "支付金额,单位分")
-    private Long payPrice;
-
-    @Schema(description = "退款金额,单位分")
-    private Long refundPrice;
-
-    @Schema(description = "退款原因")
-    private String reason;
+    @Schema(description = "商户退款单号", example = "20")
+    private String merchantRefundId;
 
-    @Schema(description = "用户 IP")
-    private String userIp;
-
-    @Schema(description = "渠道订单号,pay_order 中的channel_order_no 对应")
+    @Schema(description = "渠道支付单号", example = "30")
     private String channelOrderNo;
 
-    @Schema(description = "渠道退款单号,渠道返回")
+    @Schema(description = "渠道退款单号", example = "40")
     private String channelRefundNo;
 
-    @Schema(description = "渠道调用报错时,错误码")
-    private String channelErrorCode;
-
-    @Schema(description = "渠道调用报错时,错误信息")
-    private String channelErrorMsg;
-
-    @Schema(description = "支付渠道的额外参数")
-    private String channelExtras;
-
-    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
-    @Schema(description = "退款失效时间")
-    private LocalDateTime[] expireTime;
-
-    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
-    @Schema(description = "退款成功时间")
-    private LocalDateTime[] successTime;
-
-    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
-    @Schema(description = "退款通知时间")
-    private LocalDateTime[] notifyTime;
+    @Schema(description = "退款状态", example = "0")
+    private Integer status;
 
     @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
     @Schema(description = "创建时间")

+ 2 - 5
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/refund/vo/PayRefundPageItemRespVO.java

@@ -13,15 +13,12 @@ import java.time.LocalDateTime;
 @ToString(callSuper = true)
 public class PayRefundPageItemRespVO extends PayRefundBaseVO {
 
-    @Schema(description = "支付订单编号", requiredMode = Schema.RequiredMode.REQUIRED)
+    @Schema(description = "支付订单编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
     private Long id;
 
-    @Schema(description = "应用名称")
+    @Schema(description = "应用名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "我是芋艿")
     private String  appName;
 
-    @Schema(description = "渠道名称")
-    private String channelCodeName;
-
     @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
     private LocalDateTime createTime;
 

+ 9 - 60
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/refund/vo/PayRefundPageReqVO.java

@@ -17,77 +17,26 @@ import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_
 @ToString(callSuper = true)
 public class PayRefundPageReqVO extends PageParam {
 
-    @Schema(description = "应用编号")
+    @Schema(description = "应用编号", example = "1024")
     private Long appId;
 
-    @Schema(description = "渠道编号")
-    private Long channelId;
-
-    @Schema(description = "渠道编码")
+    @Schema(description = "渠道编码", example = "wx_app")
     private String channelCode;
 
-    @Schema(description = "支付订单编号 pay_order 表id")
-    private Long orderId;
-
-    @Schema(description = "交易订单号 pay_extension 表no 字段")
-    private String tradeNo;
-
-    @Schema(description = "商户订单编号(商户系统生成)")
+    @Schema(description = "商户支付单号", example = "10")
     private String merchantOrderId;
 
-    @Schema(description = "商户退款订单号(商户系统生成)")
-    private String merchantRefundNo;
-
-    @Schema(description = "异步通知商户地址")
-    private String notifyUrl;
-
-    @Schema(description = "通知商户退款结果的回调状态")
-    private Integer notifyStatus;
-
-    @Schema(description = "退款状态")
-    private Integer status;
-
-    @Schema(description = "退款类型(部分退款,全部退款)")
-    private Integer type;
-
-    @Schema(description = "支付金额,单位分")
-    private Long payPrice;
-
-    @Schema(description = "退款金额,单位分")
-    private Long refundPrice;
-
-    @Schema(description = "退款原因")
-    private String reason;
+    @Schema(description = "商户退款单号", example = "20")
+    private String merchantRefundId;
 
-    @Schema(description = "用户 IP")
-    private String userIp;
-
-    @Schema(description = "渠道订单号,pay_order 中的channel_order_no 对应")
+    @Schema(description = "渠道支付单号", example = "30")
     private String channelOrderNo;
 
-    @Schema(description = "渠道退款单号,渠道返回")
+    @Schema(description = "渠道退款单号", example = "40")
     private String channelRefundNo;
 
-    @Schema(description = "渠道调用报错时,错误码")
-    private String channelErrorCode;
-
-    @Schema(description = "渠道调用报错时,错误信息")
-    private String channelErrorMsg;
-
-    @Schema(description = "支付渠道的额外参数")
-    private String channelExtras;
-
-    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
-    @Schema(description = "退款失效时间")
-    private LocalDateTime[] expireTime;
-
-    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
-    @Schema(description = "退款成功时间")
-    private LocalDateTime[] successTime;
-
-    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
-    @Schema(description = "退款通知时间")
-    private LocalDateTime[] notifyTime;
+    @Schema(description = "退款状态", example = "0")
+    private Integer status;
 
     @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
     @Schema(description = "创建时间")

+ 0 - 22
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/refund/vo/PayRefundRespVO.java

@@ -1,22 +0,0 @@
-package cn.iocoder.yudao.module.pay.controller.admin.refund.vo;
-
-import io.swagger.v3.oas.annotations.media.Schema;
-import lombok.Data;
-import lombok.EqualsAndHashCode;
-import lombok.ToString;
-
-import java.time.LocalDateTime;
-
-@Schema(description = "管理后台 - 退款订单 Response VO")
-@Data
-@EqualsAndHashCode(callSuper = true)
-@ToString(callSuper = true)
-public class PayRefundRespVO extends PayRefundBaseVO {
-
-    @Schema(description = "支付退款编号", requiredMode = Schema.RequiredMode.REQUIRED)
-    private Long id;
-
-    @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
-    private LocalDateTime createTime;
-
-}

+ 0 - 16
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/refund/vo/PayRefundUpdateReqVO.java

@@ -1,16 +0,0 @@
-package cn.iocoder.yudao.module.pay.controller.admin.refund.vo;
-import io.swagger.v3.oas.annotations.media.Schema;
-import lombok.*;
-import javax.validation.constraints.*;
-
-@Schema(description = "管理后台 - 退款订单更新 Request VO")
-@Data
-@EqualsAndHashCode(callSuper = true)
-@ToString(callSuper = true)
-public class PayRefundUpdateReqVO extends PayRefundBaseVO {
-
-    @Schema(description = "支付退款编号", requiredMode = Schema.RequiredMode.REQUIRED)
-    @NotNull(message = "支付退款编号不能为空")
-    private Long id;
-
-}

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

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

+ 27 - 53
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/convert/order/PayOrderConvert.java

@@ -1,20 +1,22 @@
 package cn.iocoder.yudao.module.pay.convert.order;
 
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
+import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
 import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO;
 import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderCreateReqDTO;
 import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderRespDTO;
 import cn.iocoder.yudao.module.pay.controller.admin.order.vo.*;
 import cn.iocoder.yudao.module.pay.controller.app.order.vo.AppPayOrderSubmitRespVO;
+import cn.iocoder.yudao.module.pay.dal.dataobject.app.PayAppDO;
 import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderDO;
 import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderExtensionDO;
 import org.mapstruct.Mapper;
 import org.mapstruct.Mapping;
 import org.mapstruct.factory.Mappers;
 
-import java.math.BigDecimal;
-import java.math.RoundingMode;
 import java.util.List;
+import java.util.Map;
 
 /**
  * 支付订单 Convert
@@ -30,60 +32,32 @@ public interface PayOrderConvert {
 
     PayOrderRespDTO convert2(PayOrderDO order);
 
-    PayOrderDetailsRespVO orderDetailConvert(PayOrderDO bean);
-
-    PayOrderDetailsRespVO.PayOrderExtension orderDetailExtensionConvert(PayOrderExtensionDO bean);
-
-    List<PayOrderRespVO> convertList(List<PayOrderDO> list);
-
-    PageResult<PayOrderRespVO> convertPage(PageResult<PayOrderDO> page);
-
-    /**
-     * 订单 DO 转自定义分页对象
-     *
-     * @param bean 订单DO
-     * @return 分页对象
-     */
-    PayOrderPageItemRespVO pageConvertItemPage(PayOrderDO bean);
-
-    // TODO 芋艿:优化下 convert 逻辑
-    default PayOrderExcelVO excelConvert(PayOrderDO bean) {
-        if (bean == null) {
-            return null;
+    default PayOrderDetailsRespVO convert(PayOrderDO order, PayOrderExtensionDO orderExtension, PayAppDO app) {
+        PayOrderDetailsRespVO respVO = convertDetail(order);
+        respVO.setExtension(convert(orderExtension));
+        if (app != null) {
+            respVO.setAppName(app.getName());
         }
+        return respVO;
+    }
+    PayOrderDetailsRespVO convertDetail(PayOrderDO bean);
+    PayOrderDetailsRespVO.PayOrderExtension convert(PayOrderExtensionDO bean);
 
-        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.setPrice(BigDecimal.valueOf(bean.getPrice())
-                .divide(multiple, 2, RoundingMode.HALF_UP).toString());
-
-        payOrderExcelVO.setChannelFeePrice(BigDecimal.valueOf(bean.getChannelFeePrice())
-                .divide(multiple, 2, RoundingMode.HALF_UP).toString());
-        payOrderExcelVO.setChannelFeeRate(java.math.BigDecimal.valueOf(bean.getChannelFeeRate())
-                .multiply(multiple).toString());
-        payOrderExcelVO.setRefundPrice(BigDecimal.valueOf(bean.getRefundPrice())
-                .divide(multiple, 2, RoundingMode.HALF_UP).toString());
-
-        return payOrderExcelVO;
+    default PageResult<PayOrderPageItemRespVO> convertPage(PageResult<PayOrderDO> page, Map<Long, PayAppDO> appMap) {
+        PageResult<PayOrderPageItemRespVO> result = convertPage(page);
+        result.getList().forEach(order -> MapUtils.findAndThen(appMap, order.getAppId(), app -> order.setAppName(app.getName())));
+        return result;
+    }
+    PageResult<PayOrderPageItemRespVO> convertPage(PageResult<PayOrderDO> page);
+
+    default List<PayOrderExcelVO> convertList(List<PayOrderDO> list, Map<Long, PayAppDO> appMap) {
+        return CollectionUtils.convertList(list, order -> {
+            PayOrderExcelVO excelVO = convertExcel(order);
+            MapUtils.findAndThen(appMap, order.getAppId(), app -> excelVO.setAppName(app.getName()));
+            return excelVO;
+        });
     }
+    PayOrderExcelVO convertExcel(PayOrderDO bean);
 
     PayOrderDO convert(PayOrderCreateReqDTO bean);
 

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

@@ -1,87 +1,56 @@
 package cn.iocoder.yudao.module.pay.convert.refund;
 
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
+import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
 import cn.iocoder.yudao.module.pay.api.refund.dto.PayRefundCreateReqDTO;
 import cn.iocoder.yudao.module.pay.api.refund.dto.PayRefundRespDTO;
-import cn.iocoder.yudao.module.pay.controller.admin.refund.vo.*;
+import cn.iocoder.yudao.module.pay.controller.admin.refund.vo.PayRefundDetailsRespVO;
+import cn.iocoder.yudao.module.pay.controller.admin.refund.vo.PayRefundExcelVO;
+import cn.iocoder.yudao.module.pay.controller.admin.refund.vo.PayRefundPageItemRespVO;
+import cn.iocoder.yudao.module.pay.dal.dataobject.app.PayAppDO;
+import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderDO;
 import cn.iocoder.yudao.module.pay.dal.dataobject.refund.PayRefundDO;
 import org.mapstruct.Mapper;
 import org.mapstruct.factory.Mappers;
 
-import java.math.BigDecimal;
-import java.math.RoundingMode;
 import java.util.List;
+import java.util.Map;
 
 @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);
-
-    /**
-     * 退款订单DO 转 导出excel VO
-     *
-     * @param bean 退款订单DO
-     * @return 导出 excel VO
-     */
-    default PayRefundExcelVO excelConvert(PayRefundDO bean) {
-        if (bean == null) {
-            return null;
+    default PayRefundDetailsRespVO convert(PayRefundDO refund, PayAppDO app) {
+        PayRefundDetailsRespVO respVO = convert(refund);
+        if (app != null) {
+            respVO.setAppName(app.getName());
         }
+        return respVO;
+    }
+    PayRefundDetailsRespVO convert(PayRefundDO bean);
+    PayRefundDetailsRespVO.Order convert(PayOrderDO bean);
 
-        PayRefundExcelVO payRefundExcelVO = new PayRefundExcelVO();
-
-        payRefundExcelVO.setId(bean.getId());
-        payRefundExcelVO.setTradeNo(bean.getNo());
-        payRefundExcelVO.setMerchantOrderId(bean.getMerchantOrderId());
-        // TODO 芋艿:晚点在改
-//        payRefundExcelVO.setMerchantRefundNo(bean.getMerchantRefundNo());
-        payRefundExcelVO.setNotifyUrl(bean.getNotifyUrl());
-        payRefundExcelVO.setNotifyStatus(bean.getNotifyStatus());
-        payRefundExcelVO.setStatus(bean.getStatus());
-        payRefundExcelVO.setReason(bean.getReason());
-        payRefundExcelVO.setUserIp(bean.getUserIp());
-        payRefundExcelVO.setChannelOrderNo(bean.getChannelOrderNo());
-        payRefundExcelVO.setChannelRefundNo(bean.getChannelRefundNo());
-        payRefundExcelVO.setSuccessTime(bean.getSuccessTime());
-        payRefundExcelVO.setCreateTime(bean.getCreateTime());
-
-        BigDecimal multiple = new BigDecimal(100);
-        payRefundExcelVO.setPayPrice(BigDecimal.valueOf(bean.getPayPrice())
-                .divide(multiple, 2, RoundingMode.HALF_UP).toString());
-        payRefundExcelVO.setRefundPrice(BigDecimal.valueOf(bean.getRefundPrice())
-                .divide(multiple, 2, RoundingMode.HALF_UP).toString());
-
-        return payRefundExcelVO;
+    default PageResult<PayRefundPageItemRespVO> convertPage(PageResult<PayRefundDO> page, Map<Long, PayAppDO> appMap) {
+        PageResult<PayRefundPageItemRespVO> result = convertPage(page);
+        result.getList().forEach(order -> MapUtils.findAndThen(appMap, order.getAppId(), app -> order.setAppName(app.getName())));
+        return result;
     }
+    PageResult<PayRefundPageItemRespVO> convertPage(PageResult<PayRefundDO> page);
 
     PayRefundDO convert(PayRefundCreateReqDTO bean);
 
     PayRefundRespDTO convert02(PayRefundDO bean);
 
+    default List<PayRefundExcelVO> convertList(List<PayRefundDO> list, Map<Long, PayAppDO> appMap) {
+        return CollectionUtils.convertList(list, order -> {
+            PayRefundExcelVO excelVO = convertExcel(order);
+            MapUtils.findAndThen(appMap, order.getAppId(), app -> excelVO.setAppName(app.getName()));
+            return excelVO;
+        });
+    }
+    PayRefundExcelVO convertExcel(PayRefundDO bean);
+
 }

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

@@ -15,7 +15,7 @@ import lombok.experimental.Accessors;
 import java.time.LocalDateTime;
 
 /**
- * 商户支付、退款等的通知
+ * 支付通知
  * 在支付系统收到支付渠道的支付、退款的结果后,需要不断的通知到业务系统,直到成功。
  *
  * @author 芋道源码

+ 8 - 24
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/order/PayOrderDO.java

@@ -4,9 +4,7 @@ import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
 import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum;
 import cn.iocoder.yudao.module.pay.dal.dataobject.app.PayAppDO;
 import cn.iocoder.yudao.module.pay.dal.dataobject.channel.PayChannelDO;
-import cn.iocoder.yudao.module.pay.enums.order.PayOrderNotifyStatusEnum;
 import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum;
-import cn.iocoder.yudao.module.pay.enums.order.PayOrderRefundStatusEnum;
 import com.baomidou.mybatisplus.annotation.KeySequence;
 import com.baomidou.mybatisplus.annotation.TableName;
 import lombok.*;
@@ -71,12 +69,6 @@ public class PayOrderDO extends BaseDO {
      * 异步通知地址
      */
     private String notifyUrl;
-    /**
-     * 通知商户支付结果的回调状态
-     *
-     * 枚举 {@link PayOrderNotifyStatusEnum}
-     */
-    private Integer notifyStatus;
 
     // ========== 订单相关字段 ==========
 
@@ -93,7 +85,7 @@ public class PayOrderDO extends BaseDO {
     /**
      * 渠道手续金额,单位:分
      */
-    private Long channelFeePrice;
+    private Integer channelFeePrice;
     /**
      * 支付状态
      *
@@ -112,28 +104,20 @@ public class PayOrderDO extends BaseDO {
      * 订单支付成功时间
      */
     private LocalDateTime successTime;
-    /**
-     * 订单支付通知时间,即支付渠道的通知时间
-     */
-    private LocalDateTime notifyTime;
     /**
      * 支付成功的订单拓展单编号
      *
-     * 关联 {@link PayOrderDO#getId()}
+     * 关联 {@link PayOrderExtensionDO#getId()}
      */
-    private Long successExtensionId;
-
-    // ========== 退款相关字段 ==========
+    private Long extensionId;
     /**
-     * 退款状态
+     * 支付成功的外部订单号
      *
-     * 枚举 {@link PayOrderRefundStatusEnum}
-     */
-    private Integer refundStatus;
-    /**
-     * 退款次数
+     * 关联 {@link PayOrderExtensionDO#getNo()}
      */
-    private Integer refundTimes;
+    private String no;
+
+    // ========== 退款相关字段 ==========
     /**
      * 退款总金额,单位:分
      */

+ 1 - 0
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/order/PayOrderExtensionDO.java

@@ -15,6 +15,7 @@ import java.util.Map;
 /**
  * 支付订单拓展 DO
  *
+ * 每次调用支付渠道,都会生成一条对应记录
  *
  * @author 芋道源码
  */

+ 1 - 8
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/refund/PayRefundDO.java

@@ -6,7 +6,6 @@ import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum;
 import cn.iocoder.yudao.module.pay.dal.dataobject.app.PayAppDO;
 import cn.iocoder.yudao.module.pay.dal.dataobject.channel.PayChannelDO;
 import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderDO;
-import cn.iocoder.yudao.module.pay.enums.notify.PayNotifyStatusEnum;
 import cn.iocoder.yudao.module.pay.enums.refund.PayRefundStatusEnum;
 import com.baomidou.mybatisplus.annotation.KeySequence;
 import com.baomidou.mybatisplus.annotation.TableId;
@@ -89,12 +88,6 @@ public class PayRefundDO extends BaseDO {
      * 异步通知地址
      */
     private String notifyUrl;
-    /**
-     * 通知商户退款结果的回调状态
-     *
-     * 枚举 {@link PayNotifyStatusEnum}
-     */
-    private Integer notifyStatus;
 
     // ========== 退款相关字段 ==========
     /**
@@ -147,7 +140,7 @@ public class PayRefundDO extends BaseDO {
      */
     private String channelErrorCode;
     /**
-     * 调用渠道报错时,错误信息
+     * 调用渠道的错误提示
      */
     private String channelErrorMsg;
 

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

@@ -4,6 +4,13 @@ import cn.iocoder.yudao.module.pay.dal.dataobject.notify.PayNotifyLogDO;
 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
 import org.apache.ibatis.annotations.Mapper;
 
+import java.util.List;
+
 @Mapper
 public interface PayNotifyLogMapper extends BaseMapperX<PayNotifyLogDO> {
+
+    default List<PayNotifyLogDO> selectListByTaskId(Long taskId) {
+        return selectList(PayNotifyLogDO::getTaskId, taskId);
+    }
+
 }

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

@@ -1,9 +1,12 @@
 package cn.iocoder.yudao.module.pay.dal.mysql.notify;
 
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.module.pay.controller.admin.notify.vo.PayNotifyTaskPageReqVO;
 import cn.iocoder.yudao.module.pay.dal.dataobject.notify.PayNotifyTaskDO;
 import cn.iocoder.yudao.module.pay.enums.notify.PayNotifyStatusEnum;
-import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
-import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import org.apache.ibatis.annotations.Mapper;
 
 import java.time.LocalDateTime;
@@ -21,10 +24,21 @@ public interface PayNotifyTaskMapper extends BaseMapperX<PayNotifyTaskDO> {
      * @return PayTransactionNotifyTaskDO 数组
      */
     default List<PayNotifyTaskDO> selectListByNotify() {
-        return selectList(new QueryWrapper<PayNotifyTaskDO>()
-                .in("status", PayNotifyStatusEnum.WAITING.getStatus(), PayNotifyStatusEnum.REQUEST_SUCCESS.getStatus(),
-                        PayNotifyStatusEnum.REQUEST_FAILURE.getStatus())
-                .le("next_notify_time", LocalDateTime.now()));
+        return selectList(new LambdaQueryWrapper<PayNotifyTaskDO>()
+                .in(PayNotifyTaskDO::getStatus, PayNotifyStatusEnum.WAITING.getStatus(),
+                        PayNotifyStatusEnum.REQUEST_SUCCESS.getStatus(), PayNotifyStatusEnum.REQUEST_FAILURE.getStatus())
+                .le(PayNotifyTaskDO::getNextNotifyTime, LocalDateTime.now()));
+    }
+
+    default PageResult<PayNotifyTaskDO> selectPage(PayNotifyTaskPageReqVO reqVO) {
+        return selectPage(reqVO, new LambdaQueryWrapperX<PayNotifyTaskDO>()
+                .eqIfPresent(PayNotifyTaskDO::getAppId, reqVO.getAppId())
+                .eqIfPresent(PayNotifyTaskDO::getType, reqVO.getType())
+                .eqIfPresent(PayNotifyTaskDO::getDataId, reqVO.getDataId())
+                .eqIfPresent(PayNotifyTaskDO::getStatus, reqVO.getStatus())
+                .eqIfPresent(PayNotifyTaskDO::getMerchantOrderId, reqVO.getMerchantOrderId())
+                .betweenIfPresent(PayNotifyTaskDO::getCreateTime, reqVO.getCreateTime())
+                .orderByDesc(PayNotifyTaskDO::getId));
     }
 
 }

+ 25 - 37
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/order/PayOrderMapper.java

@@ -1,53 +1,41 @@
 package cn.iocoder.yudao.module.pay.dal.mysql.order;
 
-import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderExportReqVO;
-import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderPageReqVO;
 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 cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderExportReqVO;
+import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderPageReqVO;
 import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderDO;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
-import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import org.apache.ibatis.annotations.Mapper;
 
-import java.util.Collection;
 import java.util.List;
 
 @Mapper
 public interface PayOrderMapper extends BaseMapperX<PayOrderDO> {
 
     default PageResult<PayOrderDO> selectPage(PayOrderPageReqVO reqVO) {
-        return selectPage(reqVO, new QueryWrapperX<PayOrderDO>()
-                .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.getCreateTime())
-                .orderByDesc("id"));
+        return selectPage(reqVO, new LambdaQueryWrapperX<PayOrderDO>()
+                .eqIfPresent(PayOrderDO::getAppId, reqVO.getAppId())
+                .eqIfPresent(PayOrderDO::getChannelCode, reqVO.getChannelCode())
+                .likeIfPresent(PayOrderDO::getMerchantOrderId, reqVO.getMerchantOrderId())
+                .likeIfPresent(PayOrderDO::getChannelOrderNo, reqVO.getChannelOrderNo())
+                .likeIfPresent(PayOrderDO::getNo, reqVO.getNo())
+                .eqIfPresent(PayOrderDO::getStatus, reqVO.getStatus())
+                .betweenIfPresent(PayOrderDO::getCreateTime, reqVO.getCreateTime())
+                .orderByDesc(PayOrderDO::getId));
     }
 
     default List<PayOrderDO> selectList(PayOrderExportReqVO reqVO) {
-        return selectList(new QueryWrapperX<PayOrderDO>()
-                .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.getCreateTime())
-                .orderByDesc("id"));
-    }
-
-    default List<PayOrderDO> findByIdListQueryOrderSubject(Collection<Long> idList) {
-        return selectList(new LambdaQueryWrapper<PayOrderDO>()
-                .select(PayOrderDO::getId, PayOrderDO::getSubject)
-                .in(PayOrderDO::getId, idList));
+        return selectList(new LambdaQueryWrapperX<PayOrderDO>()
+                .eqIfPresent(PayOrderDO::getAppId, reqVO.getAppId())
+                .eqIfPresent(PayOrderDO::getChannelCode, reqVO.getChannelCode())
+                .likeIfPresent(PayOrderDO::getMerchantOrderId, reqVO.getMerchantOrderId())
+                .likeIfPresent(PayOrderDO::getChannelOrderNo, reqVO.getChannelOrderNo())
+                .likeIfPresent(PayOrderDO::getNo, reqVO.getNo())
+                .eqIfPresent(PayOrderDO::getStatus, reqVO.getStatus())
+                .betweenIfPresent(PayOrderDO::getCreateTime, reqVO.getCreateTime())
+                .orderByDesc(PayOrderDO::getId));
     }
 
     default Long selectCountByAppId(Long appId) {
@@ -55,13 +43,13 @@ public interface PayOrderMapper extends BaseMapperX<PayOrderDO> {
     }
 
     default PayOrderDO selectByAppIdAndMerchantOrderId(Long appId, String merchantOrderId) {
-        return selectOne(new QueryWrapper<PayOrderDO>().eq("app_id", appId)
-                .eq("merchant_order_id", merchantOrderId));
+        return selectOne(PayOrderDO::getAppId, appId,
+                PayOrderDO::getMerchantOrderId, merchantOrderId);
     }
 
     default int updateByIdAndStatus(Long id, Integer status, PayOrderDO update) {
-        return update(update, new QueryWrapper<PayOrderDO>()
-                .eq("id", id).eq("status", status));
+        return update(update, new LambdaQueryWrapper<PayOrderDO>()
+                .eq(PayOrderDO::getId, id).eq(PayOrderDO::getStatus, status));
     }
 
 }

+ 20 - 19
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/refund/PayRefundMapper.java

@@ -3,7 +3,6 @@ package cn.iocoder.yudao.module.pay.dal.mysql.refund;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
-import cn.iocoder.yudao.framework.mybatis.core.query.QueryWrapperX;
 import cn.iocoder.yudao.module.pay.controller.admin.refund.vo.PayRefundExportReqVO;
 import cn.iocoder.yudao.module.pay.controller.admin.refund.vo.PayRefundPageReqVO;
 import cn.iocoder.yudao.module.pay.dal.dataobject.refund.PayRefundDO;
@@ -44,27 +43,29 @@ public interface PayRefundMapper extends BaseMapperX<PayRefundDO> {
     }
 
     default PageResult<PayRefundDO> selectPage(PayRefundPageReqVO reqVO) {
-        return selectPage(reqVO, new QueryWrapperX<PayRefundDO>()
-                .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.getCreateTime())
-                .orderByDesc("id"));
+        return selectPage(reqVO, new LambdaQueryWrapperX<PayRefundDO>()
+                .eqIfPresent(PayRefundDO::getAppId, reqVO.getAppId())
+                .eqIfPresent(PayRefundDO::getChannelCode, reqVO.getChannelCode())
+                .likeIfPresent(PayRefundDO::getMerchantOrderId, reqVO.getMerchantOrderId())
+                .likeIfPresent(PayRefundDO::getMerchantRefundId, reqVO.getMerchantRefundId())
+                .likeIfPresent(PayRefundDO::getChannelOrderNo, reqVO.getChannelOrderNo())
+                .likeIfPresent(PayRefundDO::getChannelRefundNo, reqVO.getChannelRefundNo())
+                .eqIfPresent(PayRefundDO::getStatus, reqVO.getStatus())
+                .betweenIfPresent(PayRefundDO::getCreateTime, reqVO.getCreateTime())
+                .orderByDesc(PayRefundDO::getId));
     }
 
     default List<PayRefundDO> selectList(PayRefundExportReqVO reqVO) {
-        return selectList(new QueryWrapperX<PayRefundDO>()
-                .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.getCreateTime())
-                .orderByDesc("id"));
+        return selectList(new LambdaQueryWrapperX<PayRefundDO>()
+                .eqIfPresent(PayRefundDO::getAppId, reqVO.getAppId())
+                .eqIfPresent(PayRefundDO::getChannelCode, reqVO.getChannelCode())
+                .likeIfPresent(PayRefundDO::getMerchantOrderId, reqVO.getMerchantOrderId())
+                .likeIfPresent(PayRefundDO::getMerchantRefundId, reqVO.getMerchantRefundId())
+                .likeIfPresent(PayRefundDO::getChannelOrderNo, reqVO.getChannelOrderNo())
+                .likeIfPresent(PayRefundDO::getChannelRefundNo, reqVO.getChannelRefundNo())
+                .eqIfPresent(PayRefundDO::getStatus, reqVO.getStatus())
+                .betweenIfPresent(PayRefundDO::getCreateTime, reqVO.getCreateTime())
+                .orderByDesc(PayRefundDO::getId));
     }
 
 }

+ 8 - 0
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/redis/RedisKeyConstants.java

@@ -14,4 +14,12 @@ public interface RedisKeyConstants {
             "pay_notify:lock:", // 参数来自 DefaultLockKeyBuilder 类
             RedisKeyDefine.KeyTypeEnum.HASH, RLock.class, RedisKeyDefine.TimeoutTypeEnum.DYNAMIC); // Redisson 的 Lock 锁,使用 Hash 数据结构
 
+    /**
+     * 支付序号的缓存
+     *
+     * KEY 格式:pay_no:{prefix}
+     * VALUE 数据格式:编号自增
+     */
+    String PAY_NO = "pay_no";
+
 }

+ 31 - 0
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/redis/no/PayNoRedisDAO.java

@@ -0,0 +1,31 @@
+package cn.iocoder.yudao.module.pay.dal.redis.no;
+
+import cn.hutool.core.date.DatePattern;import cn.hutool.core.date.DateUtil;import org.springframework.data.redis.core.StringRedisTemplate;
+import org.springframework.stereotype.Repository;
+
+import javax.annotation.Resource;import java.time.LocalDateTime;
+
+/**
+ * 支付序号的 Redis DAO
+ *
+ * @author 芋道源码
+ */
+@Repository
+public class PayNoRedisDAO {
+
+    @Resource
+    private StringRedisTemplate stringRedisTemplate;
+
+    /**
+     * 生成序号
+     *
+     * @param prefix 前缀
+     * @return 序号
+     */
+    public String generate(String prefix) {
+        String noPrefix = prefix + DateUtil.format(LocalDateTime.now(), DatePattern.PURE_DATETIME_PATTERN);
+        Long no = stringRedisTemplate.opsForValue().increment(noPrefix);
+        return noPrefix + no;
+    }
+
+}

+ 0 - 29
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/enums/order/PayOrderNotifyStatusEnum.java

@@ -1,29 +0,0 @@
-package cn.iocoder.yudao.module.pay.enums.order;
-
-import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
-import lombok.AllArgsConstructor;
-import lombok.Getter;
-
-/**
- * 支付订单的通知状态枚举
- *
- * @author 芋道源码
- */
-@Getter
-@AllArgsConstructor
-public enum PayOrderNotifyStatusEnum implements IntArrayValuable {
-
-    NO(0, "未通知"),
-    SUCCESS(10, "通知成功"),
-    FAILURE(20, "通知失败")
-    ;
-
-    private final Integer status;
-    private final String name;
-
-    @Override
-    public int[] array() {
-        return new int[0];
-    }
-
-}

+ 0 - 29
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/enums/order/PayOrderRefundStatusEnum.java

@@ -1,29 +0,0 @@
-package cn.iocoder.yudao.module.pay.enums.order;
-
-import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
-import lombok.AllArgsConstructor;
-import lombok.Getter;
-
-/**
- * 支付订单的退款状态枚举
- *
- * @author 芋道源码
- */
-@Getter
-@AllArgsConstructor
-public enum PayOrderRefundStatusEnum implements IntArrayValuable {
-
-    NO(0, "未退款"),
-    PART(10, "部分退款"),
-    ALL(20, "全部退款")
-    ;
-
-    private final Integer status;
-    private final String name;
-
-    @Override
-    public int[] array() {
-        return new int[0];
-    }
-
-}

+ 9 - 0
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/config/PayConfiguration.java

@@ -0,0 +1,9 @@
+package cn.iocoder.yudao.module.pay.framework.pay.config;
+
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration(proxyBeanMethods = false)
+@EnableConfigurationProperties(PayProperties.class)
+public class PayConfiguration {
+}

+ 16 - 1
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/config/PayProperties.java → yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/config/PayProperties.java

@@ -1,4 +1,4 @@
-package cn.iocoder.yudao.framework.pay.config;
+package cn.iocoder.yudao.module.pay.framework.pay.config;
 
 import lombok.Data;
 import org.hibernate.validator.constraints.URL;
@@ -12,6 +12,9 @@ import javax.validation.constraints.NotEmpty;
 @Data
 public class PayProperties {
 
+    private static final String ORDER_NO_PREFIX = "P";
+    private static final String REFUND_NO_PREFIX = "R";
+
     /**
      * 支付回调地址
      *
@@ -34,4 +37,16 @@ public class PayProperties {
     @URL(message = "支付回调地址的格式必须是 URL")
     private String refundNotifyUrl;
 
+    /**
+     * 支付订单 no 的前缀
+     */
+    @NotEmpty(message = "支付订单 no 的前缀不能为空")
+    private String orderNoPrefix = ORDER_NO_PREFIX;
+
+    /**
+     * 退款订单 no 的前缀
+     */
+    @NotEmpty(message = "退款订单 no 的前缀不能为空")
+    private String refundNoPrefix = REFUND_NO_PREFIX;
+
 }

+ 4 - 0
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/package-info.java

@@ -0,0 +1,4 @@
+/**
+ * 占位,无实际作用
+ */
+package cn.iocoder.yudao.module.pay.framework.pay.core;

+ 21 - 14
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/app/PayAppService.java

@@ -14,29 +14,29 @@ import java.util.List;
 import java.util.Map;
 
 /**
- * 支付应用信息 Service 接口
+ * 支付应用 Service 接口
  *
  * @author 芋艿
  */
 public interface PayAppService {
 
     /**
-     * 创建支付应用信息
+     * 创建支付应用
      *
-     * @param createReqVO 创建信息
+     * @param createReqVO 创建
      * @return 编号
      */
     Long createApp(@Valid PayAppCreateReqVO createReqVO);
 
     /**
-     * 更新支付应用信息
+     * 更新支付应用
      *
-     * @param updateReqVO 更新信息
+     * @param updateReqVO 更新
      */
     void updateApp(@Valid PayAppUpdateReqVO updateReqVO);
 
     /**
-     * 修改应用信息状态
+     * 修改应用状态
      *
      * @param id     应用编号
      * @param status 状态
@@ -44,33 +44,40 @@ public interface PayAppService {
     void updateAppStatus(Long id, Integer status);
 
     /**
-     * 删除支付应用信息
+     * 删除支付应用
      *
      * @param id 编号
      */
     void deleteApp(Long id);
 
     /**
-     * 获得支付应用信息
+     * 获得支付应用
      *
      * @param id 编号
-     * @return 支付应用信息
+     * @return 支付应用
      */
     PayAppDO getApp(Long id);
 
     /**
-     * 获得支付应用信息列表
+     * 获得支付应用列表
      *
      * @param ids 编号
-     * @return 支付应用信息列表
+     * @return 支付应用列表
      */
     List<PayAppDO> getAppList(Collection<Long> ids);
 
     /**
-     * 获得支付应用信息分页
+     * 获得支付应用列表
+     *
+     * @return 支付应用列表
+     */
+    List<PayAppDO> getAppList();
+
+    /**
+     * 获得支付应用分页
      *
      * @param pageReqVO 分页查询
-     * @return 支付应用信息分页
+     * @return 支付应用分页
      */
     PageResult<PayAppDO> getAppPage(PayAppPageReqVO pageReqVO);
 
@@ -91,7 +98,7 @@ public interface PayAppService {
      * 如果不合法,抛出 {@link ServiceException} 业务异常
      *
      * @param id 应用编号
-     * @return 应用信息
+     * @return 应用
      */
     PayAppDO validPayApp(Long id);
 

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

@@ -1,7 +1,6 @@
 package cn.iocoder.yudao.module.pay.service.app;
 
 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
-import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.module.pay.controller.admin.app.vo.PayAppCreateReqVO;
 import cn.iocoder.yudao.module.pay.controller.admin.app.vo.PayAppPageReqVO;
@@ -9,10 +8,7 @@ import cn.iocoder.yudao.module.pay.controller.admin.app.vo.PayAppUpdateReqVO;
 import cn.iocoder.yudao.module.pay.convert.app.PayAppConvert;
 import cn.iocoder.yudao.module.pay.dal.dataobject.app.PayAppDO;
 import cn.iocoder.yudao.module.pay.dal.mysql.app.PayAppMapper;
-import cn.iocoder.yudao.module.pay.dal.mysql.refund.PayRefundMapper;
 import cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants;
-import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum;
-import cn.iocoder.yudao.module.pay.enums.refund.PayRefundStatusEnum;
 import cn.iocoder.yudao.module.pay.service.order.PayOrderService;
 import cn.iocoder.yudao.module.pay.service.refund.PayRefundService;
 import org.springframework.context.annotation.Lazy;
@@ -77,10 +73,10 @@ public class PayAppServiceImpl implements PayAppService {
         validateAppExists(id);
         // 校验关联数据是否存在
         if (orderService.getOrderCountByAppId(id) > 0) {
-            throw exception(PAY_APP_EXIST_ORDER_CANT_DELETE);
+            throw exception(APP_EXIST_ORDER_CANT_DELETE);
         }
         if (refundService.getRefundCountByAppId(id) > 0) {
-            throw exception(PAY_APP_EXIST_REFUND_CANT_DELETE);
+            throw exception(APP_EXIST_REFUND_CANT_DELETE);
         }
 
         // 删除
@@ -89,7 +85,7 @@ public class PayAppServiceImpl implements PayAppService {
 
     private void validateAppExists(Long id) {
         if (appMapper.selectById(id) == null) {
-            throw exception(PAY_APP_NOT_FOUND);
+            throw exception(APP_NOT_FOUND);
         }
     }
 
@@ -103,6 +99,11 @@ public class PayAppServiceImpl implements PayAppService {
         return appMapper.selectBatchIds(ids);
     }
 
+    @Override
+    public List<PayAppDO> getAppList() {
+         return appMapper.selectList();
+    }
+
     @Override
     public PageResult<PayAppDO> getAppPage(PayAppPageReqVO pageReqVO) {
         return appMapper.selectPage(pageReqVO);
@@ -113,11 +114,11 @@ public class PayAppServiceImpl implements PayAppService {
         PayAppDO app = appMapper.selectById(id);
         // 校验是否存在
         if (app == null) {
-            throw exception(ErrorCodeConstants.PAY_APP_NOT_FOUND);
+            throw exception(ErrorCodeConstants.APP_NOT_FOUND);
         }
         // 校验是否禁用
         if (CommonStatusEnum.DISABLE.getStatus().equals(app.getStatus())) {
-            throw exception(ErrorCodeConstants.PAY_APP_IS_DISABLE);
+            throw exception(ErrorCodeConstants.APP_IS_DISABLE);
         }
         return app;
     }

+ 5 - 6
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/channel/PayChannelServiceImpl.java

@@ -32,8 +32,7 @@ import java.util.List;
 import java.util.concurrent.TimeUnit;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
-import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.CHANNEL_EXIST_SAME_CHANNEL_ERROR;
-import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.CHANNEL_NOT_EXISTS;
+import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.*;
 
 /**
  * 支付渠道 Service 实现类
@@ -142,7 +141,7 @@ public class PayChannelServiceImpl implements PayChannelService {
         // 解析配置
         Class<? extends PayClientConfig> payClass = PayChannelEnum.getByCode(code).getConfigClass();
         if (ObjectUtil.isNull(payClass)) {
-            throw exception(CHANNEL_NOT_EXISTS);
+            throw exception(CHANNEL_NOT_FOUND);
         }
         PayClientConfig config = JsonUtils.parseObject2(configStr, payClass);
         Assert.notNull(config);
@@ -167,7 +166,7 @@ public class PayChannelServiceImpl implements PayChannelService {
     private PayChannelDO validateChannelExists(Long id) {
         PayChannelDO channel = channelMapper.selectById(id);
         if (channel == null) {
-            throw exception(CHANNEL_NOT_EXISTS);
+            throw exception(CHANNEL_NOT_FOUND);
         }
         return channel;
     }
@@ -203,10 +202,10 @@ public class PayChannelServiceImpl implements PayChannelService {
 
     private void validPayChannel(PayChannelDO channel) {
         if (channel == null) {
-            throw exception(ErrorCodeConstants.PAY_CHANNEL_NOT_FOUND);
+            throw exception(CHANNEL_NOT_FOUND);
         }
         if (CommonStatusEnum.DISABLE.getStatus().equals(channel.getStatus())) {
-            throw exception(ErrorCodeConstants.PAY_CHANNEL_IS_DISABLE);
+            throw exception(CHANNEL_IS_DISABLE);
         }
     }
 

+ 17 - 17
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/demo/PayDemoOrderServiceImpl.java

@@ -119,7 +119,7 @@ public class PayDemoOrderServiceImpl implements PayDemoOrderService {
                 new PayDemoOrderDO().setPayStatus(true).setPayTime(LocalDateTime.now())
                         .setPayChannelCode(payOrder.getChannelCode()));
         if (updateCount == 0) {
-            throw exception(PAY_DEMO_ORDER_UPDATE_PAID_STATUS_NOT_UNPAID);
+            throw exception(DEMO_ORDER_UPDATE_PAID_STATUS_NOT_UNPAID);
         }
     }
 
@@ -137,44 +137,44 @@ public class PayDemoOrderServiceImpl implements PayDemoOrderService {
         // 1.1 校验订单是否存在
         PayDemoOrderDO order = payDemoOrderMapper.selectById(id);
         if (order == null) {
-            throw exception(PAY_DEMO_ORDER_NOT_FOUND);
+            throw exception(DEMO_ORDER_NOT_FOUND);
         }
         // 1.2 校验订单未支付
         if (order.getPayStatus()) {
             log.error("[validateDemoOrderCanPaid][order({}) 不处于待支付状态,请进行处理!order 数据是:{}]",
                     id, toJsonString(order));
-            throw exception(PAY_DEMO_ORDER_UPDATE_PAID_STATUS_NOT_UNPAID);
+            throw exception(DEMO_ORDER_UPDATE_PAID_STATUS_NOT_UNPAID);
         }
         // 1.3 校验支付订单匹配
         if (notEqual(order.getPayOrderId(), payOrderId)) { // 支付单号
             log.error("[validateDemoOrderCanPaid][order({}) 支付单不匹配({}),请进行处理!order 数据是:{}]",
                     id, payOrderId, toJsonString(order));
-            throw exception(PAY_DEMO_ORDER_UPDATE_PAID_FAIL_PAY_ORDER_ID_ERROR);
+            throw exception(DEMO_ORDER_UPDATE_PAID_FAIL_PAY_ORDER_ID_ERROR);
         }
 
         // 2.1 校验支付单是否存在
         PayOrderRespDTO payOrder = payOrderApi.getOrder(payOrderId);
         if (payOrder == null) {
             log.error("[validateDemoOrderCanPaid][order({}) payOrder({}) 不存在,请进行处理!]", id, payOrderId);
-            throw exception(PAY_ORDER_NOT_FOUND);
+            throw exception(ORDER_NOT_FOUND);
         }
         // 2.2 校验支付单已支付
         if (!PayOrderStatusEnum.isSuccess(payOrder.getStatus())) {
             log.error("[validateDemoOrderCanPaid][order({}) payOrder({}) 未支付,请进行处理!payOrder 数据是:{}]",
                     id, payOrderId, toJsonString(payOrder));
-            throw exception(PAY_DEMO_ORDER_UPDATE_PAID_FAIL_PAY_ORDER_STATUS_NOT_SUCCESS);
+            throw exception(DEMO_ORDER_UPDATE_PAID_FAIL_PAY_ORDER_STATUS_NOT_SUCCESS);
         }
         // 2.3 校验支付金额一致
         if (notEqual(payOrder.getPrice(), order.getPrice())) {
             log.error("[validateDemoOrderCanPaid][order({}) payOrder({}) 支付金额不匹配,请进行处理!order 数据是:{},payOrder 数据是:{}]",
                     id, payOrderId, toJsonString(order), toJsonString(payOrder));
-            throw exception(PAY_DEMO_ORDER_UPDATE_PAID_FAIL_PAY_PRICE_NOT_MATCH);
+            throw exception(DEMO_ORDER_UPDATE_PAID_FAIL_PAY_PRICE_NOT_MATCH);
         }
         // 2.4 校验支付订单匹配(二次)
         if (notEqual(payOrder.getMerchantOrderId(), id.toString())) {
             log.error("[validateDemoOrderCanPaid][order({}) 支付单不匹配({}),请进行处理!payOrder 数据是:{}]",
                     id, payOrderId, toJsonString(payOrder));
-            throw exception(PAY_DEMO_ORDER_UPDATE_PAID_FAIL_PAY_ORDER_ID_ERROR);
+            throw exception(DEMO_ORDER_UPDATE_PAID_FAIL_PAY_ORDER_ID_ERROR);
         }
         return payOrder;
     }
@@ -203,15 +203,15 @@ public class PayDemoOrderServiceImpl implements PayDemoOrderService {
         // 校验订单是否存在
         PayDemoOrderDO order = payDemoOrderMapper.selectById(id);
         if (order == null) {
-            throw exception(PAY_DEMO_ORDER_NOT_FOUND);
+            throw exception(DEMO_ORDER_NOT_FOUND);
         }
         // 校验订单是否支付
         if (!order.getPayStatus()) {
-            throw exception(PAY_DEMO_ORDER_REFUND_FAIL_NOT_PAID);
+            throw exception(DEMO_ORDER_REFUND_FAIL_NOT_PAID);
         }
         // 校验订单是否已退款
         if (order.getPayRefundId() != null) {
-            throw exception(PAY_DEMO_ORDER_REFUND_FAIL_REFUNDED);
+            throw exception(DEMO_ORDER_REFUND_FAIL_REFUNDED);
         }
         return order;
     }
@@ -229,35 +229,35 @@ public class PayDemoOrderServiceImpl implements PayDemoOrderService {
         // 1.1 校验示例订单
         PayDemoOrderDO order = payDemoOrderMapper.selectById(id);
         if (order == null) {
-            throw exception(PAY_DEMO_ORDER_NOT_FOUND);
+            throw exception(DEMO_ORDER_NOT_FOUND);
         }
         // 1.2 校验退款订单匹配
         if (Objects.equals(order.getPayOrderId(), payRefundId)) {
             log.error("[validateDemoOrderCanRefunded][order({}) 退款单不匹配({}),请进行处理!order 数据是:{}]",
                     id, payRefundId, toJsonString(order));
-            throw exception(PAY_DEMO_ORDER_REFUND_FAIL_REFUND_ORDER_ID_ERROR);
+            throw exception(DEMO_ORDER_REFUND_FAIL_REFUND_ORDER_ID_ERROR);
         }
 
         // 2.1 校验退款订单
         PayRefundRespDTO payRefund = payRefundApi.getRefund(payRefundId);
         if (payRefund == null) {
-            throw exception(PAY_DEMO_ORDER_REFUND_FAIL_REFUND_NOT_FOUND);
+            throw exception(DEMO_ORDER_REFUND_FAIL_REFUND_NOT_FOUND);
         }
         // 2.2
         if (!PayRefundStatusEnum.isSuccess(payRefund.getStatus())) {
-            throw exception(PAY_DEMO_ORDER_REFUND_FAIL_REFUND_NOT_SUCCESS);
+            throw exception(DEMO_ORDER_REFUND_FAIL_REFUND_NOT_SUCCESS);
         }
         // 2.3 校验退款金额一致
         if (notEqual(payRefund.getRefundPrice(), order.getPrice())) {
             log.error("[validateDemoOrderCanRefunded][order({}) payRefund({}) 退款金额不匹配,请进行处理!order 数据是:{},payRefund 数据是:{}]",
                     id, payRefundId, toJsonString(order), toJsonString(payRefund));
-            throw exception(PAY_DEMO_ORDER_REFUND_FAIL_REFUND_PRICE_NOT_MATCH);
+            throw exception(DEMO_ORDER_REFUND_FAIL_REFUND_PRICE_NOT_MATCH);
         }
         // 2.4 校验退款订单匹配(二次)
         if (notEqual(payRefund.getMerchantOrderId(), id.toString())) {
             log.error("[validateDemoOrderCanRefunded][order({}) 退款单不匹配({}),请进行处理!payRefund 数据是:{}]",
                     id, payRefundId, toJsonString(payRefund));
-            throw exception(PAY_DEMO_ORDER_REFUND_FAIL_REFUND_ORDER_ID_ERROR);
+            throw exception(DEMO_ORDER_REFUND_FAIL_REFUND_ORDER_ID_ERROR);
         }
         return payRefund;
     }

+ 32 - 3
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/notify/PayNotifyService.java

@@ -1,29 +1,58 @@
 package cn.iocoder.yudao.module.pay.service.notify;
 
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.pay.controller.admin.notify.vo.PayNotifyTaskPageReqVO;
+import cn.iocoder.yudao.module.pay.dal.dataobject.notify.PayNotifyLogDO;
+import cn.iocoder.yudao.module.pay.dal.dataobject.notify.PayNotifyTaskDO;
 import cn.iocoder.yudao.module.pay.service.notify.dto.PayNotifyTaskCreateReqDTO;
 
 import javax.validation.Valid;
+import java.util.List;
 
 /**
- * 支付通知 Service 接口
+ * 回调通知 Service 接口
  *
  * @author 芋道源码
  */
 public interface PayNotifyService {
 
     /**
-     * 创建支付通知任务
+     * 创建回调通知任务
      *
      * @param reqDTO 任务信息
      */
     void createPayNotifyTask(@Valid PayNotifyTaskCreateReqDTO reqDTO);
 
     /**
-     * 执行支付通知
+     * 执行回调通知
      *
      * 注意,该方法提供给定时任务调用。目前是 yudao-server 进行调用
      * @return 通知数量
      */
     int executeNotify() throws InterruptedException;
 
+    /**
+     * 获得回调通知
+     *
+     * @param id 编号
+     * @return 回调通知
+     */
+    PayNotifyTaskDO getNotifyTask(Long id);
+
+    /**
+     * 获得回调通知分页
+     *
+     * @param pageReqVO 分页查询
+     * @return 回调通知分页
+     */
+    PageResult<PayNotifyTaskDO> getNotifyTaskPage(PayNotifyTaskPageReqVO pageReqVO);
+
+    /**
+     * 获得回调日志列表
+     *
+     * @param taskId 任务编号
+     * @return 日志列表
+     */
+    List<PayNotifyLogDO> getNotifyLogList(Long taskId);
+
 }

+ 28 - 11
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/notify/PayNotifyServiceImpl.java

@@ -6,11 +6,13 @@ import cn.hutool.core.util.ObjectUtil;
 import cn.hutool.http.HttpResponse;
 import cn.hutool.http.HttpUtil;
 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.common.util.date.DateUtils;
 import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
 import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
 import cn.iocoder.yudao.module.pay.api.notify.dto.PayOrderNotifyReqDTO;
 import cn.iocoder.yudao.module.pay.api.notify.dto.PayRefundNotifyReqDTO;
+import cn.iocoder.yudao.module.pay.controller.admin.notify.vo.PayNotifyTaskPageReqVO;
 import cn.iocoder.yudao.module.pay.dal.dataobject.notify.PayNotifyLogDO;
 import cn.iocoder.yudao.module.pay.dal.dataobject.notify.PayNotifyTaskDO;
 import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderDO;
@@ -72,15 +74,15 @@ public class PayNotifyServiceImpl implements PayNotifyService {
     private PayRefundService refundService;
 
     @Resource
-    private PayNotifyTaskMapper payNotifyTaskMapper;
+    private PayNotifyTaskMapper notifyTaskMapper;
     @Resource
-    private PayNotifyLogMapper payNotifyLogMapper;
+    private PayNotifyLogMapper notifyLogMapper;
 
     @Resource(name = NOTIFY_THREAD_POOL_TASK_EXECUTOR)
     private ThreadPoolTaskExecutor threadPoolTaskExecutor;
 
     @Resource
-    private PayNotifyLockRedisDAO payNotifyLockCoreRedisDAO;
+    private PayNotifyLockRedisDAO notifyLockCoreRedisDAO;
 
     @Resource
     @Lazy // 循环依赖(自己依赖自己),避免报错
@@ -105,7 +107,7 @@ public class PayNotifyServiceImpl implements PayNotifyService {
         }
 
         // 执行插入
-        payNotifyTaskMapper.insert(task);
+        notifyTaskMapper.insert(task);
 
         // 必须在事务提交后,在发起任务,否则 PayNotifyTaskDO 还没入库,就提前回调接入的业务
         TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
@@ -119,7 +121,7 @@ public class PayNotifyServiceImpl implements PayNotifyService {
     @Override
     public int executeNotify() throws InterruptedException {
         // 获得需要通知的任务
-        List<PayNotifyTaskDO> tasks = payNotifyTaskMapper.selectListByNotify();
+        List<PayNotifyTaskDO> tasks = notifyTaskMapper.selectListByNotify();
         if (CollUtil.isEmpty(tasks)) {
             return 0;
         }
@@ -164,11 +166,11 @@ public class PayNotifyServiceImpl implements PayNotifyService {
      */
     public void executeNotify(PayNotifyTaskDO task) {
         // 分布式锁,避免并发问题
-        payNotifyLockCoreRedisDAO.lock(task.getId(), NOTIFY_TIMEOUT_MILLIS, () -> {
+        notifyLockCoreRedisDAO.lock(task.getId(), NOTIFY_TIMEOUT_MILLIS, () -> {
             // 校验,当前任务是否已经被通知过
             // 虽然已经通过分布式加锁,但是可能同时满足通知的条件,然后都去获得锁。此时,第一个执行完后,第二个还是能拿到锁,然后会再执行一次。
             // 因此,此处我们通过第 notifyTimes 通知次数是否匹配来判断
-            PayNotifyTaskDO dbTask = payNotifyTaskMapper.selectById(task.getId());
+            PayNotifyTaskDO dbTask = notifyTaskMapper.selectById(task.getId());
             if (ObjectUtil.notEqual(task.getNotifyTimes(), dbTask.getNotifyTimes())) {
                 log.warn("[executeNotifySync][task({}) 任务被忽略,原因是它的通知不是第 ({}) 次,可能是因为并发执行了]",
                         JsonUtils.toJsonString(task), dbTask.getNotifyTimes());
@@ -197,7 +199,7 @@ public class PayNotifyServiceImpl implements PayNotifyService {
         // 记录 PayNotifyLog 日志
         String response = invokeException != null ? ExceptionUtil.getRootCauseMessage(invokeException) :
                 JsonUtils.toJsonString(invokeResult);
-        payNotifyLogMapper.insert(PayNotifyLogDO.builder().taskId(task.getId())
+        notifyLogMapper.insert(PayNotifyLogDO.builder().taskId(task.getId())
                 .notifyTimes(task.getNotifyTimes() + 1).status(newStatus).response(response).build());
     }
 
@@ -250,22 +252,37 @@ public class PayNotifyServiceImpl implements PayNotifyService {
         // 情况一:调用成功
         if (invokeResult != null && invokeResult.isSuccess()) {
             updateTask.setStatus(PayNotifyStatusEnum.SUCCESS.getStatus());
-            payNotifyTaskMapper.updateById(updateTask);
+            notifyTaskMapper.updateById(updateTask);
             return updateTask.getStatus();
         }
         // 情况二:调用失败、调用异常
         // 2.1 超过最大回调次数
         if (updateTask.getNotifyTimes() >= PayNotifyTaskDO.NOTIFY_FREQUENCY.length) {
             updateTask.setStatus(PayNotifyStatusEnum.FAILURE.getStatus());
-            payNotifyTaskMapper.updateById(updateTask);
+            notifyTaskMapper.updateById(updateTask);
             return updateTask.getStatus();
         }
         // 2.2 未超过最大回调次数
         updateTask.setNextNotifyTime(addTime(Duration.ofSeconds(PayNotifyTaskDO.NOTIFY_FREQUENCY[updateTask.getNotifyTimes()])));
         updateTask.setStatus(invokeException != null ? PayNotifyStatusEnum.REQUEST_FAILURE.getStatus()
                 : PayNotifyStatusEnum.REQUEST_SUCCESS.getStatus());
-        payNotifyTaskMapper.updateById(updateTask);
+        notifyTaskMapper.updateById(updateTask);
         return updateTask.getStatus();
     }
 
+    @Override
+    public PayNotifyTaskDO getNotifyTask(Long id) {
+        return notifyTaskMapper.selectById(id);
+    }
+
+    @Override
+    public PageResult<PayNotifyTaskDO> getNotifyTaskPage(PayNotifyTaskPageReqVO pageReqVO) {
+        return notifyTaskMapper.selectPage(pageReqVO);
+    }
+
+    @Override
+    public List<PayNotifyLogDO> getNotifyLogList(Long taskId) {
+        return notifyLogMapper.selectListByTaskId(taskId);
+    }
+
 }

+ 0 - 47
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/order/PayOrderExtensionService.java

@@ -1,47 +0,0 @@
-package cn.iocoder.yudao.module.pay.service.order;
-
-import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
-import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderExtensionDO;
-
-import java.util.Collection;
-import java.util.List;
-import java.util.Map;
-
-// TODO 芋艿:合并到 PayOrder 里;
-/**
- * 支付订单 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 = getOrderExtensionList(successExtensionIdList);
-        return CollectionUtils.convertMap(list, PayOrderExtensionDO::getId);
-    }
-
-}

+ 0 - 34
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/order/PayOrderExtensionServiceImpl.java

@@ -1,34 +0,0 @@
-package cn.iocoder.yudao.module.pay.service.order;
-
-import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderExtensionDO;
-import cn.iocoder.yudao.module.pay.dal.mysql.order.PayOrderExtensionMapper;
-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);
-    }
-
-}

+ 13 - 30
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/order/PayOrderService.java

@@ -1,7 +1,6 @@
 package cn.iocoder.yudao.module.pay.service.order;
 
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
 import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderRespDTO;
 import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderCreateReqDTO;
 import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderExportReqVO;
@@ -9,12 +8,11 @@ import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderPageReqVO;
 import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderSubmitReqVO;
 import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderSubmitRespVO;
 import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderDO;
+import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderExtensionDO;
 
 import javax.validation.Valid;
 import javax.validation.constraints.NotEmpty;
-import java.util.Collection;
 import java.util.List;
-import java.util.Map;
 
 /**
  * 支付订单 Service 接口
@@ -49,44 +47,21 @@ public interface PayOrderService {
     Long getOrderCountByAppId(Long appId);
 
     /**
-     * 获得支付订单
-     * 分页
+     * 获得支付订单分页
      *
      * @param pageReqVO 分页查询
-     * @return 支付订单
-     * 分页
+     * @return 支付订单分页
      */
     PageResult<PayOrderDO> getOrderPage(PayOrderPageReqVO pageReqVO);
 
     /**
-     * 获得支付订单
-     * 列表, 用于 Excel 导出
+     * 获得支付订单列表, 用于 Excel 导出
      *
      * @param exportReqVO 查询条件
-     * @return 支付订单
-     * 列表
+     * @return 支付订单列表
      */
     List<PayOrderDO> getOrderList(PayOrderExportReqVO exportReqVO);
 
-    /**
-     * 根据 ID 集合获取只包含商品名称的订单集合
-     *
-     * @param idList 订单 ID 集合
-     * @return 只包含商品名称的订单集合
-     */
-    List<PayOrderDO> getOrderSubjectList(Collection<Long> idList);
-
-    /**
-     * 根据订单 ID 集合获取订单商品名称Map集合
-     *
-     * @param ids 订单 ID 集合
-     * @return 订单商品 map 集合
-     */
-    default Map<Long, PayOrderDO> getOrderSubjectMap(Collection<Long> ids) {
-        List<PayOrderDO> list = getOrderSubjectList(ids);
-        return CollectionUtils.convertMap(list, PayOrderDO::getId);
-    }
-
     /**
      * 创建支付单
      *
@@ -122,4 +97,12 @@ public interface PayOrderService {
      */
     void updateOrderRefundPrice(Long id, Integer incrRefundPrice);
 
+    /**
+     * 获得支付订单
+     *
+     * @param id 编号
+     * @return 支付订单
+     */
+    PayOrderExtensionDO getOrderExtension(Long id);
+
 }

+ 87 - 93
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/order/PayOrderServiceImpl.java

@@ -1,13 +1,11 @@
 package cn.iocoder.yudao.module.pay.service.order;
 
-import cn.hutool.core.date.DateUtil;
 import cn.hutool.core.lang.Pair;
 import cn.hutool.core.util.ObjectUtil;
-import cn.hutool.core.util.RandomUtil;
 import cn.hutool.core.util.StrUtil;
+import cn.hutool.extra.spring.SpringUtil;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils;
-import cn.iocoder.yudao.framework.pay.config.PayProperties;
 import cn.iocoder.yudao.framework.pay.core.client.PayClient;
 import cn.iocoder.yudao.framework.pay.core.client.PayClientFactory;
 import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderRespDTO;
@@ -26,22 +24,21 @@ import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderDO;
 import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderExtensionDO;
 import cn.iocoder.yudao.module.pay.dal.mysql.order.PayOrderExtensionMapper;
 import cn.iocoder.yudao.module.pay.dal.mysql.order.PayOrderMapper;
+import cn.iocoder.yudao.module.pay.dal.redis.no.PayNoRedisDAO;
 import cn.iocoder.yudao.module.pay.enums.notify.PayNotifyTypeEnum;
-import cn.iocoder.yudao.module.pay.enums.order.PayOrderNotifyStatusEnum;
-import cn.iocoder.yudao.module.pay.enums.order.PayOrderRefundStatusEnum;
 import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum;
+import cn.iocoder.yudao.module.pay.framework.pay.config.PayProperties;
 import cn.iocoder.yudao.module.pay.service.app.PayAppService;
 import cn.iocoder.yudao.module.pay.service.channel.PayChannelService;
 import cn.iocoder.yudao.module.pay.service.notify.PayNotifyService;
 import cn.iocoder.yudao.module.pay.service.notify.dto.PayNotifyTaskCreateReqDTO;
+import cn.iocoder.yudao.module.pay.util.MoneyUtils;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.validation.annotation.Validated;
 
 import javax.annotation.Resource;
-import java.time.LocalDateTime;
-import java.util.Collection;
 import java.util.List;
 import java.util.Objects;
 
@@ -69,6 +66,8 @@ public class PayOrderServiceImpl implements PayOrderService {
     private PayOrderMapper orderMapper;
     @Resource
     private PayOrderExtensionMapper orderExtensionMapper;
+    @Resource
+    private PayNoRedisDAO noRedisDAO;
 
     @Resource
     private PayAppService appService;
@@ -102,12 +101,6 @@ public class PayOrderServiceImpl implements PayOrderService {
         return orderMapper.selectList(exportReqVO);
     }
 
-    // TODO @艿艿:需要优化。不确定这个方法的作用
-    @Override
-    public List<PayOrderDO> getOrderSubjectList(Collection<Long> idList) {
-        return orderMapper.findByIdListQueryOrderSubject(idList);
-    }
-
     @Override
     public Long createOrder(PayOrderCreateReqDTO reqDTO) {
         // 校验 App
@@ -125,11 +118,11 @@ public class PayOrderServiceImpl implements PayOrderService {
         // 创建支付交易单
         order = PayOrderConvert.INSTANCE.convert(reqDTO).setAppId(app.getId())
                 // 商户相关字段
-                .setNotifyUrl(app.getOrderNotifyUrl()).setNotifyStatus(PayOrderNotifyStatusEnum.NO.getStatus())
+                .setNotifyUrl(app.getOrderNotifyUrl())
                 // 订单相关字段
                 .setStatus(PayOrderStatusEnum.WAITING.getStatus())
                 // 退款相关字段
-                .setRefundStatus(PayOrderRefundStatusEnum.NO.getStatus()).setRefundTimes(0).setRefundPrice(0);
+                .setRefundPrice(0);
         orderMapper.insert(order);
         return order.getId();
     }
@@ -143,8 +136,9 @@ public class PayOrderServiceImpl implements PayOrderService {
         PayClient client = payClientFactory.getPayClient(channel.getId());
 
         // 2. 插入 PayOrderExtensionDO
+        String no = noRedisDAO.generate(payProperties.getOrderNoPrefix());
         PayOrderExtensionDO orderExtension = PayOrderConvert.INSTANCE.convert(reqVO, userIp)
-                .setOrderId(order.getId()).setNo(generateOrderExtensionNo())
+                .setOrderId(order.getId()).setNo(no)
                 .setChannelId(channel.getId()).setChannelCode(channel.getCode())
                 .setStatus(PayOrderStatusEnum.WAITING.getStatus());
         orderExtensionMapper.insert(orderExtension);
@@ -162,10 +156,10 @@ public class PayOrderServiceImpl implements PayOrderService {
 
         // 4. 如果调用直接支付成功,则直接更新支付单状态为成功。例如说:付款码支付,免密支付时,就直接验证支付成功
         if (unifiedOrderResp != null) {
-            notifyPayOrder(channel, unifiedOrderResp);
+            getSelf().notifyOrder(channel, unifiedOrderResp);
             // 如有渠道错误码,则抛出业务异常,提示用户
             if (StrUtil.isNotEmpty(unifiedOrderResp.getChannelErrorCode())) {
-                throw exception(PAY_ORDER_SUBMIT_CHANNEL_ERROR, unifiedOrderResp.getChannelErrorCode(),
+                throw exception(ORDER_SUBMIT_CHANNEL_ERROR, unifiedOrderResp.getChannelErrorCode(),
                         unifiedOrderResp.getChannelErrorMsg());
             }
             // 此处需要读取最新的状态
@@ -177,13 +171,13 @@ public class PayOrderServiceImpl implements PayOrderService {
     private PayOrderDO validateOrderCanSubmit(Long id) {
         PayOrderDO order = orderMapper.selectById(id);
         if (order == null) { // 是否存在
-            throw exception(PAY_ORDER_NOT_FOUND);
+            throw exception(ORDER_NOT_FOUND);
         }
         if (!PayOrderStatusEnum.WAITING.getStatus().equals(order.getStatus())) { // 校验状态,必须是待支付
-            throw exception(PAY_ORDER_STATUS_IS_NOT_WAITING);
+            throw exception(ORDER_STATUS_IS_NOT_WAITING);
         }
         if (LocalDateTimeUtils.beforeNow(order.getExpireTime())) { // 校验是否过期
-            throw exception(PAY_ORDER_IS_EXPIRED);
+            throw exception(ORDER_IS_EXPIRED);
         }
         return order;
     }
@@ -196,7 +190,7 @@ public class PayOrderServiceImpl implements PayOrderService {
         PayClient client = payClientFactory.getPayClient(channel.getId());
         if (client == null) {
             log.error("[validatePayChannelCanSubmit][渠道编号({}) 找不到对应的支付客户端]", channel.getId());
-            throw exception(PAY_CHANNEL_CLIENT_NOT_FOUND);
+            throw exception(CHANNEL_NOT_FOUND);
         }
         return channel;
     }
@@ -211,61 +205,22 @@ public class PayOrderServiceImpl implements PayOrderService {
         return payProperties.getOrderNotifyUrl() + "/" + channel.getId();
     }
 
-    private String generateOrderExtensionNo() {
-//    wx
-//    2014
-//    10
-//    27
-//    20
-//    09
-//    39
-//    5522657
-//    a690389285100
-        // 目前的算法
-        // 时间序列,年月日时分秒 14 位
-        // 纯随机,6 位 TODO 芋艿:此处估计是会有问题的,后续在调整
-        return DateUtil.format(LocalDateTime.now(), "yyyyMMddHHmmss") + // 时间序列
-                RandomUtil.randomInt(100000, 999999) // 随机。为什么是这个范围,因为偷懒
-                ;
-    }
-
     @Override
-    @Transactional(rollbackFor = Exception.class)
     public void notifyOrder(Long channelId, PayOrderRespDTO notify) {
         // 校验支付渠道是否有效
         PayChannelDO channel = channelService.validPayChannel(channelId);
         // 更新支付订单为已支付
-        TenantUtils.execute(channel.getTenantId(), () -> notifyPayOrder(channel, notify));
+        TenantUtils.execute(channel.getTenantId(), () -> getSelf().notifyOrder(channel, notify));
     }
 
-    @Override
-    public void updateOrderRefundPrice(Long id, Integer incrRefundPrice) {
-        PayOrderDO order = orderMapper.selectById(id);
-        if (order == null) {
-            throw exception(PAY_ORDER_NOT_FOUND);
-        }
-        if (!PayOrderStatusEnum.isSuccess(order.getStatus())) {
-            throw exception(PAY_REFUND_PRICE_EXCEED);
-        }
-        if (order.getRefundPrice() + incrRefundPrice > order.getPrice()) {
-            throw exception(PAY_REFUND_PRICE_EXCEED);
-        }
-
-        // 更新订单
-        PayOrderDO updateObj = new PayOrderDO()
-                .setRefundPrice(order.getRefundPrice() + incrRefundPrice)
-                .setRefundTimes(order.getRefundTimes() + 1);
-        if (Objects.equals(updateObj.getRefundPrice(), order.getPrice())) {
-            updateObj.setStatus(PayOrderStatusEnum.CLOSED.getStatus())
-                    .setRefundStatus(PayOrderRefundStatusEnum.ALL.getStatus());
-        } else {
-            updateObj.setStatus(PayOrderStatusEnum.CLOSED.getStatus())
-                    .setRefundStatus(PayOrderRefundStatusEnum.PART.getStatus());
-        }
-        orderMapper.updateByIdAndStatus(id, PayOrderStatusEnum.SUCCESS.getStatus(), updateObj);
-    }
-
-    private void notifyPayOrder(PayChannelDO channel, PayOrderRespDTO notify) {
+    /**
+     * 通知并更新订单的支付结果
+     *
+     * @param channel 支付渠道
+     * @param notify 通知
+     */
+    @Transactional(rollbackFor = Exception.class) // 注意,如果是方法内调用该方法,需要通过 getSelf().notifyPayOrder(channel, notify) 调用,否则事务不生效
+    public void notifyOrder(PayChannelDO channel, PayOrderRespDTO notify) {
         // 情况一:支付成功的回调
         if (PayOrderStatusRespEnum.isSuccess(notify.getStatus())) {
             notifyOrderSuccess(channel, notify);
@@ -275,20 +230,22 @@ public class PayOrderServiceImpl implements PayOrderService {
         if (PayOrderStatusRespEnum.isClosed(notify.getStatus())) {
             notifyOrderClosed(channel, notify);
         }
+        // 情况三:WAITING:无需处理
+        // 情况四:REFUND:通过退款回调处理
     }
 
     private void notifyOrderSuccess(PayChannelDO channel, PayOrderRespDTO notify) {
         // 1. 更新 PayOrderExtensionDO 支付成功
-        PayOrderExtensionDO orderExtension = updateOrderExtensionSuccess(notify);
+        PayOrderExtensionDO orderExtension = updateOrderSuccess(notify);
         // 2. 更新 PayOrderDO 支付成功
-        Pair<Boolean, PayOrderDO> order = updateOrderExtensionSuccess(channel, orderExtension, notify);
-        if (order.getKey()) { // 如果之前已经成功回调,则直接返回,不用重复记录支付通知记录;例如说:支付平台重复回调
+        Boolean paid = updateOrderSuccess(channel, orderExtension, notify);
+        if (paid) { // 如果之前已经成功回调,则直接返回,不用重复记录支付通知记录;例如说:支付平台重复回调
             return;
         }
 
         // 3. 插入支付通知记录
         notifyService.createPayNotifyTask(PayNotifyTaskCreateReqDTO.builder()
-                .type(PayNotifyTypeEnum.ORDER.getType()).dataId(order.getValue().getId()).build());
+                .type(PayNotifyTypeEnum.ORDER.getType()).dataId(orderExtension.getOrderId()).build());
     }
 
     /**
@@ -297,25 +254,25 @@ public class PayOrderServiceImpl implements PayOrderService {
      * @param notify 通知
      * @return PayOrderExtensionDO 对象
      */
-    private PayOrderExtensionDO updateOrderExtensionSuccess(PayOrderRespDTO notify) {
+    private PayOrderExtensionDO updateOrderSuccess(PayOrderRespDTO notify) {
         // 1. 查询 PayOrderExtensionDO
         PayOrderExtensionDO orderExtension = orderExtensionMapper.selectByNo(notify.getOutTradeNo());
         if (orderExtension == null) {
-            throw exception(PAY_ORDER_EXTENSION_NOT_FOUND);
+            throw exception(ORDER_EXTENSION_NOT_FOUND);
         }
         if (PayOrderStatusEnum.isSuccess(orderExtension.getStatus())) { // 如果已经是成功,直接返回,不用重复更新
             log.info("[updateOrderExtensionSuccess][支付拓展单({}) 已经是已支付,无需更新]", orderExtension.getId());
             return orderExtension;
         }
         if (ObjectUtil.notEqual(orderExtension.getStatus(), PayOrderStatusEnum.WAITING.getStatus())) { // 校验状态,必须是待支付
-            throw exception(PAY_ORDER_EXTENSION_STATUS_IS_NOT_WAITING);
+            throw exception(ORDER_EXTENSION_STATUS_IS_NOT_WAITING);
         }
 
         // 2. 更新 PayOrderExtensionDO
         int updateCounts = orderExtensionMapper.updateByIdAndStatus(orderExtension.getId(), orderExtension.getStatus(),
                 PayOrderExtensionDO.builder().status(PayOrderStatusEnum.SUCCESS.getStatus()).channelNotifyData(toJsonString(notify)).build());
         if (updateCounts == 0) { // 校验状态,必须是待支付
-            throw exception(PAY_ORDER_EXTENSION_STATUS_IS_NOT_WAITING);
+            throw exception(ORDER_EXTENSION_STATUS_IS_NOT_WAITING);
         }
         log.info("[updateOrderExtensionSuccess][支付拓展单({}) 更新为已支付]", orderExtension.getId());
         return orderExtension;
@@ -327,37 +284,37 @@ public class PayOrderServiceImpl implements PayOrderService {
      * @param channel 支付渠道
      * @param orderExtension 支付拓展单
      * @param notify 通知回调
-     * @return key:是否之前已经成功回调
-     *         value:PayOrderDO 对象
+     * @return 是否之前已经成功回调
      */
-    private Pair<Boolean, PayOrderDO> updateOrderExtensionSuccess(PayChannelDO channel, PayOrderExtensionDO orderExtension,
-                                                                  PayOrderRespDTO notify) {
+    private Boolean updateOrderSuccess(PayChannelDO channel, PayOrderExtensionDO orderExtension,
+                                                         PayOrderRespDTO notify) {
         // 1. 判断 PayOrderDO 是否处于待支付
         PayOrderDO order = orderMapper.selectById(orderExtension.getOrderId());
         if (order == null) {
-            throw exception(PAY_ORDER_NOT_FOUND);
+            throw exception(ORDER_NOT_FOUND);
         }
         if (PayOrderStatusEnum.isSuccess(order.getStatus()) // 如果已经是成功,直接返回,不用重复更新
-                && Objects.equals(order.getSuccessExtensionId(), orderExtension.getId())) {
+                && Objects.equals(order.getExtensionId(), orderExtension.getId())) {
             log.info("[updateOrderExtensionSuccess][支付订单({}) 已经是已支付,无需更新]", order.getId());
-            return Pair.of(true, order);
+            return true;
         }
         if (!PayOrderStatusEnum.WAITING.getStatus().equals(order.getStatus())) { // 校验状态,必须是待支付
-            throw exception(PAY_ORDER_STATUS_IS_NOT_WAITING);
+            throw exception(ORDER_STATUS_IS_NOT_WAITING);
         }
 
         // 2. 更新 PayOrderDO
         int updateCounts = orderMapper.updateByIdAndStatus(order.getId(), PayOrderStatusEnum.WAITING.getStatus(),
                 PayOrderDO.builder().status(PayOrderStatusEnum.SUCCESS.getStatus())
                         .channelId(channel.getId()).channelCode(channel.getCode())
-                        .successTime(notify.getSuccessTime()).successExtensionId(orderExtension.getId())
+                        .successTime(notify.getSuccessTime()).extensionId(orderExtension.getId()).no(orderExtension.getNo())
                         .channelOrderNo(notify.getChannelOrderNo()).channelUserId(notify.getChannelUserId())
-                        .notifyTime(LocalDateTime.now()).build());
+                        .channelFeeRate(channel.getFeeRate()).channelFeePrice(MoneyUtils.calculateRatePrice(order.getPrice(), channel.getFeeRate()))
+                        .build());
         if (updateCounts == 0) { // 校验状态,必须是待支付
-            throw exception(PAY_ORDER_STATUS_IS_NOT_WAITING);
+            throw exception(ORDER_STATUS_IS_NOT_WAITING);
         }
         log.info("[updateOrderExtensionSuccess][支付订单({}) 更新为已支付]", order.getId());
-        return Pair.of(false, order);
+        return false;
     }
 
     private void notifyOrderClosed(PayChannelDO channel, PayOrderRespDTO notify) {
@@ -368,7 +325,7 @@ public class PayOrderServiceImpl implements PayOrderService {
         // 1. 查询 PayOrderExtensionDO
         PayOrderExtensionDO orderExtension = orderExtensionMapper.selectByNo(notify.getOutTradeNo());
         if (orderExtension == null) {
-            throw exception(PAY_ORDER_EXTENSION_NOT_FOUND);
+            throw exception(ORDER_EXTENSION_NOT_FOUND);
         }
         if (PayOrderStatusEnum.isClosed(orderExtension.getStatus())) { // 如果已经是关闭,直接返回,不用重复更新
             log.info("[updateOrderExtensionClosed][支付拓展单({}) 已经是支付关闭,无需更新]", orderExtension.getId());
@@ -380,7 +337,7 @@ public class PayOrderServiceImpl implements PayOrderService {
             return;
         }
         if (ObjectUtil.notEqual(orderExtension.getStatus(), PayOrderStatusEnum.WAITING.getStatus())) { // 校验状态,必须是待支付
-            throw exception(PAY_ORDER_EXTENSION_STATUS_IS_NOT_WAITING);
+            throw exception(ORDER_EXTENSION_STATUS_IS_NOT_WAITING);
         }
 
         // 2. 更新 PayOrderExtensionDO
@@ -388,9 +345,46 @@ public class PayOrderServiceImpl implements PayOrderService {
                 PayOrderExtensionDO.builder().status(PayOrderStatusEnum.CLOSED.getStatus()).channelNotifyData(toJsonString(notify))
                         .channelErrorCode(notify.getChannelErrorCode()).channelErrorMsg(notify.getChannelErrorMsg()).build());
         if (updateCounts == 0) { // 校验状态,必须是待支付
-            throw exception(PAY_ORDER_EXTENSION_STATUS_IS_NOT_WAITING);
+            throw exception(ORDER_EXTENSION_STATUS_IS_NOT_WAITING);
         }
         log.info("[updateOrderExtensionClosed][支付拓展单({}) 更新为支付关闭]", orderExtension.getId());
     }
 
+    @Override
+    public void updateOrderRefundPrice(Long id, Integer incrRefundPrice) {
+        PayOrderDO order = orderMapper.selectById(id);
+        if (order == null) {
+            throw exception(ORDER_NOT_FOUND);
+        }
+        if (!PayOrderStatusEnum.isSuccessOrRefund(order.getStatus())) {
+            throw exception(ORDER_REFUND_FAIL_STATUS_ERROR);
+        }
+        if (order.getRefundPrice() + incrRefundPrice > order.getPrice()) {
+            throw exception(REFUND_PRICE_EXCEED);
+        }
+
+        // 更新订单
+        PayOrderDO updateObj = new PayOrderDO()
+                .setRefundPrice(order.getRefundPrice() + incrRefundPrice)
+                .setStatus(PayOrderStatusEnum.REFUND.getStatus());
+        int updateCount = orderMapper.updateByIdAndStatus(id, order.getStatus(), updateObj);
+        if (updateCount == 0) {
+            throw exception(ORDER_REFUND_FAIL_STATUS_ERROR);
+        }
+    }
+
+    @Override
+    public PayOrderExtensionDO getOrderExtension(Long id) {
+        return orderExtensionMapper.selectById(id);
+    }
+
+    /**
+     * 获得自身的代理对象,解决 AOP 生效问题
+     *
+     * @return 自己
+     */
+    private PayOrderServiceImpl getSelf() {
+        return SpringUtil.getBean(getClass());
+    }
+
 }

+ 54 - 29
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/refund/PayRefundServiceImpl.java

@@ -3,7 +3,6 @@ package cn.iocoder.yudao.module.pay.service.refund;
 import cn.hutool.core.date.DateUtil;
 import cn.hutool.core.util.RandomUtil;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.framework.pay.config.PayProperties;
 import cn.iocoder.yudao.framework.pay.core.client.PayClient;
 import cn.iocoder.yudao.framework.pay.core.client.PayClientFactory;
 import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundRespDTO;
@@ -21,16 +20,14 @@ import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderExtensionDO;
 import cn.iocoder.yudao.module.pay.dal.dataobject.refund.PayRefundDO;
 import cn.iocoder.yudao.module.pay.dal.mysql.refund.PayRefundMapper;
 import cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants;
-import cn.iocoder.yudao.module.pay.enums.notify.PayNotifyStatusEnum;
 import cn.iocoder.yudao.module.pay.enums.notify.PayNotifyTypeEnum;
-import cn.iocoder.yudao.module.pay.enums.order.PayOrderRefundStatusEnum;
 import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum;
 import cn.iocoder.yudao.module.pay.enums.refund.PayRefundStatusEnum;
+import cn.iocoder.yudao.module.pay.framework.pay.config.PayProperties;
 import cn.iocoder.yudao.module.pay.service.app.PayAppService;
 import cn.iocoder.yudao.module.pay.service.channel.PayChannelService;
 import cn.iocoder.yudao.module.pay.service.notify.PayNotifyService;
 import cn.iocoder.yudao.module.pay.service.notify.dto.PayNotifyTaskCreateReqDTO;
-import cn.iocoder.yudao.module.pay.service.order.PayOrderExtensionService;
 import cn.iocoder.yudao.module.pay.service.order.PayOrderService;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
@@ -43,6 +40,7 @@ import java.util.List;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
 import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
+import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.CHANNEL_NOT_FOUND;
 
 /**
  * 退款订单 Service 实现类
@@ -66,8 +64,6 @@ public class PayRefundServiceImpl implements PayRefundService {
     @Resource
     private PayOrderService orderService;
     @Resource
-    private PayOrderExtensionService orderExtensionService;
-    @Resource
     private PayAppService appService;
     @Resource
     private PayChannelService channelService;
@@ -106,13 +102,13 @@ public class PayRefundServiceImpl implements PayRefundService {
         PayClient client = payClientFactory.getPayClient(channel.getId());
         if (client == null) {
             log.error("[refund][渠道编号({}) 找不到对应的支付客户端]", channel.getId());
-            throw exception(ErrorCodeConstants.PAY_CHANNEL_CLIENT_NOT_FOUND);
+            throw exception(CHANNEL_NOT_FOUND);
         }
         // 1.4 校验退款订单是否已经存在
         PayRefundDO refund = refundMapper.selectByAppIdAndMerchantRefundId(
                 app.getId(), reqDTO.getMerchantRefundId());
         if (refund != null) {
-            throw exception(ErrorCodeConstants.PAY_REFUND_EXISTS);
+            throw exception(ErrorCodeConstants.REFUND_EXISTS);
         }
 
         // 2.1 插入退款单
@@ -120,7 +116,7 @@ public class PayRefundServiceImpl implements PayRefundService {
                 .setNo(generateRefundNo()).setOrderId(order.getId())
                 .setChannelId(order.getChannelId()).setChannelCode(order.getChannelCode())
                 // 商户相关的字段
-                .setNotifyUrl(app.getRefundNotifyUrl()).setNotifyStatus(PayNotifyStatusEnum.WAITING.getStatus())
+                .setNotifyUrl(app.getRefundNotifyUrl())
                 // 渠道相关字段
                 .setChannelOrderNo(order.getChannelOrderNo())
                 // 退款相关字段
@@ -128,7 +124,7 @@ public class PayRefundServiceImpl implements PayRefundService {
                 .setPayPrice(order.getPrice()).setRefundPrice(reqDTO.getPrice());
         refundMapper.insert(refund);
         // 2.2 向渠道发起退款申请
-        PayOrderExtensionDO orderExtension = orderExtensionService.getOrderExtension(order.getSuccessExtensionId());
+        PayOrderExtensionDO orderExtension = orderService.getOrderExtension(order.getExtensionId());
         PayRefundUnifiedReqDTO unifiedReqDTO = new PayRefundUnifiedReqDTO()
                 .setPayPrice(order.getPrice())
                 .setRefundPrice(reqDTO.getPrice())
@@ -136,7 +132,7 @@ public class PayRefundServiceImpl implements PayRefundService {
                 .setOutRefundNo(refund.getNo())
                 .setNotifyUrl(genChannelRefundNotifyUrl(channel))
                 .setReason(reqDTO.getReason());
-        PayRefundRespDTO refundRespDTO = client.unifiedRefund(unifiedReqDTO); // TODO 增加一个 channelErrorCode、channelErrorMsg 字段
+        PayRefundRespDTO refundRespDTO = client.unifiedRefund(unifiedReqDTO);
         // 2.3 处理退款返回
         notifyRefund(channel, refundRespDTO);
 
@@ -153,25 +149,21 @@ public class PayRefundServiceImpl implements PayRefundService {
     private PayOrderDO validatePayOrderCanRefund(PayRefundCreateReqDTO reqDTO) {
         PayOrderDO order = orderService.getOrder(reqDTO.getAppId(), reqDTO.getMerchantOrderId());
         if (order == null) {
-            throw exception(ErrorCodeConstants.PAY_ORDER_NOT_FOUND);
+            throw exception(ErrorCodeConstants.ORDER_NOT_FOUND);
         }
         // 校验状态,必须是支付状态
         if (!PayOrderStatusEnum.SUCCESS.getStatus().equals(order.getStatus())) {
-            throw exception(ErrorCodeConstants.PAY_ORDER_STATUS_IS_NOT_SUCCESS);
+            throw exception(ErrorCodeConstants.ORDER_STATUS_IS_NOT_SUCCESS);
         }
 
-        // 是否已经全额退款
-        if (PayOrderRefundStatusEnum.ALL.getStatus().equals(order.getRefundStatus())) {
-            throw exception(ErrorCodeConstants.PAY_REFUND_ALL_REFUNDED);
-        }
         // 校验金额 退款金额不能大于原定的金额
         if (reqDTO.getPrice() + order.getRefundPrice() > order.getPrice()){
-            throw exception(ErrorCodeConstants.PAY_REFUND_PRICE_EXCEED);
+            throw exception(ErrorCodeConstants.REFUND_PRICE_EXCEED);
         }
         // 是否有退款中的订单
         if (refundMapper.selectCountByAppIdAndOrderId(reqDTO.getAppId(), order.getId(),
                 PayRefundStatusEnum.WAITING.getStatus()) > 0) {
-            throw exception(ErrorCodeConstants.PAY_REFUND_HAS_REFUNDING);
+            throw exception(ErrorCodeConstants.REFUND_HAS_REFUNDING);
         }
         return order;
     }
@@ -205,7 +197,6 @@ public class PayRefundServiceImpl implements PayRefundService {
     }
 
     @Override
-    @Transactional(rollbackFor = Exception.class)
     public void notifyRefund(Long channelId, PayRefundRespDTO notify) {
         // 校验支付渠道是否有效
         channelService.validPayChannel(channelId);
@@ -217,28 +208,33 @@ public class PayRefundServiceImpl implements PayRefundService {
         TenantUtils.execute(channel.getTenantId(), () -> notifyRefund(channel, notify));
     }
 
+    // TODO 芋艿:事务问题
     private void notifyRefund(PayChannelDO channel, PayRefundRespDTO notify) {
+        // 情况一:退款成功
         if (PayRefundStatusRespEnum.isSuccess(notify.getStatus())) {
             notifyRefundSuccess(channel, notify);
-        } else {
+            return;
+        }
+        // 情况二:退款失败
+        if (PayRefundStatusRespEnum.isFailure(notify.getStatus())) {
             notifyRefundFailure(channel, notify);
         }
     }
 
-    private void notifyRefundSuccess(PayChannelDO channel, PayRefundRespDTO notify) {
+    public void notifyRefundSuccess(PayChannelDO channel, PayRefundRespDTO notify) {
         // 1.1 查询 PayRefundDO
         PayRefundDO refund = refundMapper.selectByAppIdAndNo(
                 channel.getAppId(), notify.getOutRefundNo());
         if (refund == null) {
-            throw exception(ErrorCodeConstants.PAY_REFUND_NOT_FOUND);
+            throw exception(ErrorCodeConstants.REFUND_NOT_FOUND);
         }
         if (PayRefundStatusEnum.isSuccess(refund.getStatus())) { // 如果已经是成功,直接返回,不用重复更新
+            log.info("[notifyRefundSuccess][退款订单({}) 已经是退款成功,无需更新]", refund.getId());
             return;
         }
         if (!PayRefundStatusEnum.WAITING.getStatus().equals(refund.getStatus())) {
-            throw exception(ErrorCodeConstants.PAY_REFUND_STATUS_IS_NOT_WAITING);
+            throw exception(ErrorCodeConstants.REFUND_STATUS_IS_NOT_WAITING);
         }
-
         // 1.2 更新 PayRefundDO
         PayRefundDO updateRefundObj = new PayRefundDO()
                 .setSuccessTime(notify.getSuccessTime())
@@ -247,19 +243,48 @@ public class PayRefundServiceImpl implements PayRefundService {
                 .setChannelNotifyData(toJsonString(notify));
         int updateCounts = refundMapper.updateByIdAndStatus(refund.getId(), refund.getStatus(), updateRefundObj);
         if (updateCounts == 0) { // 校验状态,必须是等待状态
-            throw exception(ErrorCodeConstants.PAY_REFUND_STATUS_IS_NOT_WAITING);
+            throw exception(ErrorCodeConstants.REFUND_STATUS_IS_NOT_WAITING);
         }
+        log.info("[notifyRefundSuccess][退款订单({}) 更新为退款成功]", refund.getId());
 
         // 2. 更新订单
         orderService.updateOrderRefundPrice(refund.getOrderId(), refund.getRefundPrice());
 
-        // 3. 插入退款通知记录
+        // 3. 插入退款通知记录 TODO 芋艿:退款成功
         notifyService.createPayNotifyTask(PayNotifyTaskCreateReqDTO.builder()
                 .type(PayNotifyTypeEnum.REFUND.getType()).dataId(refund.getId()).build());
     }
 
-    private void notifyRefundFailure(PayChannelDO channel, PayRefundRespDTO notify) {
-        // TODO 芋艿:未实现
+    @Transactional(rollbackFor = Exception.class)
+    public void notifyRefundFailure(PayChannelDO channel, PayRefundRespDTO notify) {
+        // 1.1 查询 PayRefundDO
+        PayRefundDO refund = refundMapper.selectByAppIdAndNo(
+                channel.getAppId(), notify.getOutRefundNo());
+        if (refund == null) {
+            throw exception(ErrorCodeConstants.REFUND_NOT_FOUND);
+        }
+        if (PayRefundStatusEnum.isFailure(refund.getStatus())) { // 如果已经是成功,直接返回,不用重复更新
+            log.info("[notifyRefundSuccess][退款订单({}) 已经是退款关闭,无需更新]", refund.getId());
+            return;
+        }
+        if (!PayRefundStatusEnum.WAITING.getStatus().equals(refund.getStatus())) {
+            throw exception(ErrorCodeConstants.REFUND_STATUS_IS_NOT_WAITING);
+        }
+        // 1.2 更新 PayRefundDO
+        PayRefundDO updateRefundObj = new PayRefundDO()
+                .setChannelRefundNo(notify.getChannelRefundNo())
+                .setStatus(PayRefundStatusEnum.FAILURE.getStatus())
+                .setChannelNotifyData(toJsonString(notify))
+                .setChannelErrorCode(notify.getChannelErrorCode()).setChannelErrorMsg(notify.getChannelErrorMsg());
+        int updateCounts = refundMapper.updateByIdAndStatus(refund.getId(), refund.getStatus(), updateRefundObj);
+        if (updateCounts == 0) { // 校验状态,必须是等待状态
+            throw exception(ErrorCodeConstants.REFUND_STATUS_IS_NOT_WAITING);
+        }
+        log.info("[notifyRefundFailure][退款订单({}) 更新为退款失败]", refund.getId());
+
+        // 2. 插入退款通知记录 TODO 芋艿:退款失败
+        notifyService.createPayNotifyTask(PayNotifyTaskCreateReqDTO.builder()
+                .type(PayNotifyTypeEnum.REFUND.getType()).dataId(refund.getId()).build());
     }
 
 }

+ 27 - 0
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/util/MoneyUtils.java

@@ -0,0 +1,27 @@
+package cn.iocoder.yudao.module.pay.util;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+
+/**
+ * 金额工具类
+ *
+ * @author 芋道源码
+ */
+public class MoneyUtils {
+
+    /**
+     * 计算百分比金额,四舍五入
+     *
+     * @param price 金额
+     * @param rate 百分比,例如说 56.77% 则传入 56.77
+     * @return 百分比金额
+     */
+    public static Integer calculateRatePrice(Integer price, Double rate) {
+        return new BigDecimal(price)
+                .multiply(BigDecimal.valueOf(rate)) // 乘以
+                .setScale(0, RoundingMode.HALF_UP) // 四舍五入
+                .intValue();
+    }
+
+}

+ 6 - 6
yudao-module-pay/yudao-module-pay-biz/src/test/java/cn/iocoder/yudao/module/pay/service/app/PayAppServiceTest.java

@@ -90,7 +90,7 @@ public class PayAppServiceTest extends BaseDbUnitTest {
         PayAppUpdateReqVO reqVO = randomPojo(PayAppUpdateReqVO.class, o ->
                 o.setStatus((RandomUtil.randomEle(CommonStatusEnum.values()).getStatus())));
         // 调用, 并断言异常
-        assertServiceException(() -> appService.updateApp(reqVO), PAY_APP_NOT_FOUND);
+        assertServiceException(() -> appService.updateApp(reqVO), APP_NOT_FOUND);
     }
 
     @Test
@@ -130,7 +130,7 @@ public class PayAppServiceTest extends BaseDbUnitTest {
         Long id = randomLongId();
 
         // 调用, 并断言异常
-        assertServiceException(() -> appService.deleteApp(id), PAY_APP_NOT_FOUND);
+        assertServiceException(() -> appService.deleteApp(id), APP_NOT_FOUND);
     }
 
     @Test
@@ -144,7 +144,7 @@ public class PayAppServiceTest extends BaseDbUnitTest {
         when(orderService.getOrderCountByAppId(eq(id))).thenReturn(10L);
 
         // 调用, 并断言异常
-        assertServiceException(() -> appService.deleteApp(id), PAY_APP_EXIST_ORDER_CANT_DELETE);
+        assertServiceException(() -> appService.deleteApp(id), APP_EXIST_ORDER_CANT_DELETE);
     }
 
     @Test
@@ -158,7 +158,7 @@ public class PayAppServiceTest extends BaseDbUnitTest {
         when(refundService.getRefundCountByAppId(eq(id))).thenReturn(10L);
 
         // 调用, 并断言异常
-        assertServiceException(() -> appService.deleteApp(id), PAY_APP_EXIST_REFUND_CANT_DELETE);
+        assertServiceException(() -> appService.deleteApp(id), APP_EXIST_REFUND_CANT_DELETE);
     }
 
     @Test
@@ -239,7 +239,7 @@ public class PayAppServiceTest extends BaseDbUnitTest {
 
     @Test
     public void testValidPayApp_notFound() {
-        assertServiceException(() -> appService.validPayApp(randomLongId()), PAY_APP_NOT_FOUND);
+        assertServiceException(() -> appService.validPayApp(randomLongId()), APP_NOT_FOUND);
     }
 
     @Test
@@ -252,7 +252,7 @@ public class PayAppServiceTest extends BaseDbUnitTest {
         Long id = dbApp.getId();
 
         // 调用,并断言异常
-        assertServiceException(() -> appService.validPayApp(id), PAY_APP_IS_DISABLE);
+        assertServiceException(() -> appService.validPayApp(id), APP_IS_DISABLE);
     }
 
 }

+ 132 - 4
yudao-module-pay/yudao-module-pay-biz/src/test/java/cn/iocoder/yudao/module/pay/service/channel/PayChannelServiceTest.java

@@ -26,8 +26,7 @@ import java.util.List;
 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.*;
-import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.CHANNEL_EXIST_SAME_CHANNEL_ERROR;
-import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.CHANNEL_NOT_EXISTS;
+import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.*;
 import static org.junit.jupiter.api.Assertions.*;
 
 @Import({PayChannelServiceImpl.class})
@@ -51,6 +50,40 @@ public class PayChannelServiceTest extends BaseDbUnitTest {
         channelService.setChannelCache(null);
     }
 
+    @Test
+    public void testInitLocalCache() {
+        // mock 数据
+        PayChannelDO dbChannel = randomPojo(PayChannelDO.class,
+                o -> o.setConfig(randomWxPayClientConfig()));
+        channelMapper.insert(dbChannel);// @Sql: 先插入出一条存在的数据
+
+        // 调用
+        channelService.initLocalCache();
+        // 校验缓存
+        assertEquals(1, channelService.getChannelCache().size());
+        assertEquals(dbChannel, channelService.getChannelCache().get(0));
+    }
+
+    @Test
+    public void testRefreshLocalCache() {
+        // mock 数据 01
+        PayChannelDO dbChannel = randomPojo(PayChannelDO.class,
+                o -> o.setConfig(randomWxPayClientConfig()));
+        channelMapper.insert(dbChannel);// @Sql: 先插入出一条存在的数据
+        channelService.initLocalCache();
+        // mock 数据 02
+        PayChannelDO dbChannel02 = randomPojo(PayChannelDO.class,
+                o -> o.setConfig(randomWxPayClientConfig()));
+        channelMapper.insert(dbChannel02);// @Sql: 先插入出一条存在的数据
+
+        // 调用
+        channelService.refreshLocalCache();
+        // 校验缓存
+        assertEquals(2, channelService.getChannelCache().size());
+        assertEquals(dbChannel, channelService.getChannelCache().get(0));
+        assertEquals(dbChannel02, channelService.getChannelCache().get(1));
+    }
+
     @Test
     public void testCreateChannel_success() {
         // 准备参数
@@ -125,7 +158,7 @@ public class PayChannelServiceTest extends BaseDbUnitTest {
         });
 
         // 调用, 并断言异常
-        assertServiceException(() -> channelService.updateChannel(reqVO), CHANNEL_NOT_EXISTS);
+        assertServiceException(() -> channelService.updateChannel(reqVO), CHANNEL_NOT_FOUND);
     }
 
     @Test
@@ -153,7 +186,7 @@ public class PayChannelServiceTest extends BaseDbUnitTest {
         Long id = randomLongId();
 
         // 调用, 并断言异常
-        assertServiceException(() -> channelService.deleteChannel(id), CHANNEL_NOT_EXISTS);
+        assertServiceException(() -> channelService.deleteChannel(id), CHANNEL_NOT_FOUND);
     }
 
     @Test
@@ -214,6 +247,101 @@ public class PayChannelServiceTest extends BaseDbUnitTest {
         assertPojoEquals(channel, dbChannel);
     }
 
+    @Test
+    public void testValidPayChannel_notExists() {
+        // 准备参数
+        Long id = randomLongId();
+
+        // 调用, 并断言异常
+        assertServiceException(() -> channelService.validPayChannel(id), CHANNEL_NOT_FOUND);
+    }
+
+    @Test
+    public void testValidPayChannel_isDisable() {
+        // mock 数据
+        PayChannelDO dbChannel = randomPojo(PayChannelDO.class, o -> {
+            o.setCode(PayChannelEnum.ALIPAY_APP.getCode());
+            o.setConfig(randomAlipayPayClientConfig());
+            o.setStatus(CommonStatusEnum.DISABLE.getStatus());
+        });
+        channelMapper.insert(dbChannel);// @Sql: 先插入出一条存在的数据
+        // 准备参数
+        Long id = dbChannel.getId();
+
+        // 调用, 并断言异常
+        assertServiceException(() -> channelService.validPayChannel(id), CHANNEL_IS_DISABLE);
+    }
+
+    @Test
+    public void testValidPayChannel_success() {
+        // mock 数据
+        PayChannelDO dbChannel = randomPojo(PayChannelDO.class, o -> {
+            o.setCode(PayChannelEnum.ALIPAY_APP.getCode());
+            o.setConfig(randomAlipayPayClientConfig());
+            o.setStatus(CommonStatusEnum.ENABLE.getStatus());
+        });
+        channelMapper.insert(dbChannel);// @Sql: 先插入出一条存在的数据
+        // 准备参数
+        Long id = dbChannel.getId();
+
+        // 调用
+        PayChannelDO channel = channelService.validPayChannel(id);
+        // 断言异常
+        assertPojoEquals(channel, dbChannel);
+    }
+
+    @Test
+    public void testValidPayChannel_appIdAndCode() {
+        // mock 数据
+        PayChannelDO dbChannel = randomPojo(PayChannelDO.class, o -> {
+            o.setCode(PayChannelEnum.ALIPAY_APP.getCode());
+            o.setConfig(randomAlipayPayClientConfig());
+            o.setStatus(CommonStatusEnum.ENABLE.getStatus());
+        });
+        channelMapper.insert(dbChannel);// @Sql: 先插入出一条存在的数据
+        // 准备参数
+        Long appId = dbChannel.getAppId();
+        String code = dbChannel.getCode();
+
+        // 调用
+        PayChannelDO channel = channelService.validPayChannel(appId, code);
+        // 断言异常
+        assertPojoEquals(channel, dbChannel);
+    }
+
+    @Test
+    public void testGetEnableChannelList() {
+        // 准备参数
+        Long appId = randomLongId();
+        // mock 数据 01(enable 不匹配)
+        PayChannelDO dbChannel01 = randomPojo(PayChannelDO.class, o -> {
+            o.setCode(PayChannelEnum.ALIPAY_APP.getCode());
+            o.setConfig(randomAlipayPayClientConfig());
+            o.setStatus(CommonStatusEnum.DISABLE.getStatus());
+        });
+        channelMapper.insert(dbChannel01);// @Sql: 先插入出一条存在的数据
+        // mock 数据 02(appId 不匹配)
+        PayChannelDO dbChannel02 = randomPojo(PayChannelDO.class, o -> {
+            o.setCode(PayChannelEnum.ALIPAY_APP.getCode());
+            o.setConfig(randomAlipayPayClientConfig());
+            o.setStatus(CommonStatusEnum.ENABLE.getStatus());
+        });
+        channelMapper.insert(dbChannel02);// @Sql: 先插入出一条存在的数据
+        // mock 数据 03
+        PayChannelDO dbChannel03 = randomPojo(PayChannelDO.class, o -> {
+            o.setCode(PayChannelEnum.ALIPAY_APP.getCode());
+            o.setConfig(randomAlipayPayClientConfig());
+            o.setAppId(appId);
+            o.setStatus(CommonStatusEnum.ENABLE.getStatus());
+        });
+        channelMapper.insert(dbChannel03);// @Sql: 先插入出一条存在的数据
+
+        // 调用
+        List<PayChannelDO> channel = channelService.getEnableChannelList(appId);
+        // 断言异常
+        assertPojoEquals(channel, dbChannel03);
+    }
+
     public WxPayClientConfig randomWxPayClientConfig() {
         return new WxPayClientConfig()
                 .setAppId(randomString())

+ 455 - 89
yudao-module-pay/yudao-module-pay-biz/src/test/java/cn/iocoder/yudao/module/pay/service/order/PayOrderServiceTest.java

@@ -1,48 +1,73 @@
 package cn.iocoder.yudao.module.pay.service.order;
 
-import cn.hutool.core.date.DateUtil;
-import cn.hutool.core.util.RandomUtil;
+import cn.hutool.extra.spring.SpringUtil;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.framework.pay.config.PayProperties;
+import cn.iocoder.yudao.framework.pay.core.client.PayClient;
 import cn.iocoder.yudao.framework.pay.core.client.PayClientFactory;
+import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderRespDTO;
 import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum;
-import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
+import cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderDisplayModeEnum;
+import cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderStatusRespEnum;
+import cn.iocoder.yudao.framework.test.core.ut.BaseDbAndRedisUnitTest;
+import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderCreateReqDTO;
 import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderExportReqVO;
 import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderPageReqVO;
+import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderSubmitReqVO;
+import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderSubmitRespVO;
+import cn.iocoder.yudao.module.pay.dal.dataobject.app.PayAppDO;
+import cn.iocoder.yudao.module.pay.dal.dataobject.channel.PayChannelDO;
 import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderDO;
+import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderExtensionDO;
+import cn.iocoder.yudao.module.pay.dal.mysql.order.PayOrderExtensionMapper;
 import cn.iocoder.yudao.module.pay.dal.mysql.order.PayOrderMapper;
-import cn.iocoder.yudao.module.pay.enums.order.PayOrderNotifyStatusEnum;
+import cn.iocoder.yudao.module.pay.dal.redis.no.PayNoRedisDAO;
+import cn.iocoder.yudao.module.pay.enums.notify.PayNotifyTypeEnum;
 import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum;
-import cn.iocoder.yudao.module.pay.enums.order.PayOrderRefundStatusEnum;
+import cn.iocoder.yudao.module.pay.framework.pay.config.PayProperties;
 import cn.iocoder.yudao.module.pay.service.app.PayAppService;
 import cn.iocoder.yudao.module.pay.service.channel.PayChannelService;
 import cn.iocoder.yudao.module.pay.service.notify.PayNotifyService;
+import cn.iocoder.yudao.module.pay.service.notify.dto.PayNotifyTaskCreateReqDTO;
+import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
+import org.mockito.ArgumentMatcher;
+import org.mockito.MockedStatic;
 import org.springframework.boot.test.mock.mockito.MockBean;
 import org.springframework.context.annotation.Import;
 
 import javax.annotation.Resource;
-import java.time.LocalDateTime;
+import java.time.Duration;
 import java.util.List;
 
+import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.*;
+import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
 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.randomPojo;
+import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomString;
+import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.*;
+import static org.assertj.core.api.Assertions.assertThat;
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.mockito.ArgumentMatchers.*;
+import static org.mockito.Mockito.*;
 
 /**
  * {@link PayOrderServiceImpl} 的单元测试类
  *
  * @author 芋艿
  */
-@Import({PayOrderServiceImpl.class})
-public class PayOrderServiceTest extends BaseDbUnitTest {
+@Import({PayOrderServiceImpl.class, PayNoRedisDAO.class})
+public class PayOrderServiceTest extends BaseDbAndRedisUnitTest {
 
     @Resource
     private PayOrderServiceImpl orderService;
 
     @Resource
     private PayOrderMapper orderMapper;
+    @Resource
+    private PayOrderExtensionMapper orderExtensionMapper;
 
     @MockBean
     private PayClientFactory payClientFactory;
@@ -55,140 +80,92 @@ public class PayOrderServiceTest extends BaseDbUnitTest {
     @MockBean
     private PayNotifyService notifyService;
 
-    public String generateNo() {
-        return DateUtil.format(LocalDateTime.now(), "yyyyMMddHHmmss") + RandomUtil.randomInt(100000, 999999);
+    @BeforeEach
+    public void setUp() {
+        when(properties.getOrderNotifyUrl()).thenReturn("http://127.0.0.1");
     }
 
     @Test
     public void testGetOrderPage() {
-
-        String merchantOrderId = generateNo();
-        String channelOrderId = generateNo();
-
         // mock 数据
         PayOrderDO dbOrder = randomPojo(PayOrderDO.class, o -> { // 等会查询到
             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.setPrice(10000);
-            o.setChannelFeeRate(0.01);
-            o.setChannelFeePrice(1L);
+            o.setMerchantOrderId("110");
+            o.setChannelOrderNo("220");
+            o.setNo("330");
             o.setStatus(PayOrderStatusEnum.SUCCESS.getStatus());
-            o.setUserIp("127.0.0.1");
-            o.setCreateTime(LocalDateTime.of(2018, 1, 1, 10, 1, 0));
-            o.setExpireTime(LocalDateTime.of(2018, 1, 1, 10, 30, 0));
-            o.setSuccessTime(LocalDateTime.of(2018, 1, 1, 10, 10, 2));
-            o.setNotifyTime(LocalDateTime.of(2018, 1, 1, 10, 10, 15));
-            o.setSuccessExtensionId(1L);
-            o.setRefundStatus(PayOrderRefundStatusEnum.NO.getStatus());
-            o.setRefundTimes(0);
-            o.setRefundPrice(0);
-            o.setChannelUserId("1008611");
-            o.setChannelOrderNo(channelOrderId);
-            o.setUpdateTime(LocalDateTime.of(2018, 1, 1, 10, 10, 15));
+            o.setCreateTime(buildTime(2018, 1, 15));
         });
         orderMapper.insert(dbOrder);
         // 测试 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())));
+        orderMapper.insert(cloneIgnoreId(dbOrder, o -> o.setMerchantOrderId(randomString())));
+        // 测试 channelOrderNo 不匹配
+        orderMapper.insert(cloneIgnoreId(dbOrder, o -> o.setChannelOrderNo(randomString())));
+        // 测试 no 不匹配
+        orderMapper.insert(cloneIgnoreId(dbOrder, o -> o.setNo(randomString())));
         // 测试 status 不匹配
         orderMapper.insert(cloneIgnoreId(dbOrder, o -> o.setStatus(PayOrderStatusEnum.CLOSED.getStatus())));
-        // 测试 refundStatus 不匹配
-        orderMapper.insert(cloneIgnoreId(dbOrder, o -> o.setRefundStatus(PayOrderRefundStatusEnum.ALL.getStatus())));
         // 测试 createTime 不匹配
-        orderMapper.insert(cloneIgnoreId(dbOrder, o -> o.setCreateTime(LocalDateTime.of(2019, 1, 1, 10, 10,
-                1))));
+        orderMapper.insert(cloneIgnoreId(dbOrder, o -> o.setCreateTime(buildTime(2019, 1, 1))));
         // 准备参数
         PayOrderPageReqVO reqVO = new PayOrderPageReqVO();
         reqVO.setAppId(1L);
-        reqVO.setChannelId(1L);
         reqVO.setChannelCode(PayChannelEnum.WX_PUB.getCode());
-        reqVO.setMerchantOrderId(merchantOrderId);
-        reqVO.setNotifyStatus(PayOrderNotifyStatusEnum.SUCCESS.getStatus());
+        reqVO.setMerchantOrderId("11");
+        reqVO.setChannelOrderNo("22");
+        reqVO.setNo("33");
         reqVO.setStatus(PayOrderStatusEnum.SUCCESS.getStatus());
-        reqVO.setRefundStatus(PayOrderRefundStatusEnum.NO.getStatus());
-        reqVO.setCreateTime((new LocalDateTime[]{LocalDateTime.of(2018, 1, 1, 10, 1, 0), LocalDateTime.of(2018, 1, 1, 10, 1, 0)}));
+        reqVO.setCreateTime(buildBetweenTime(2018, 1, 10, 2018, 1, 30));
+
         // 调用
         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.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.setPrice(10000);
-            o.setChannelFeeRate(0.01);
-            o.setChannelFeePrice(1L);
+            o.setMerchantOrderId("110");
+            o.setChannelOrderNo("220");
+            o.setNo("330");
             o.setStatus(PayOrderStatusEnum.SUCCESS.getStatus());
-            o.setUserIp("127.0.0.1");
-            o.setCreateTime(LocalDateTime.of(2018, 1, 1, 10, 1, 0));
-            o.setExpireTime(LocalDateTime.of(2018, 1, 1, 10, 30, 0));
-            o.setSuccessTime(LocalDateTime.of(2018, 1, 1, 10, 10, 2));
-            o.setNotifyTime(LocalDateTime.of(2018, 1, 1, 10, 10, 15));
-            o.setSuccessExtensionId(1L);
-            o.setRefundStatus(PayOrderRefundStatusEnum.NO.getStatus());
-            o.setRefundTimes(0);
-            o.setRefundPrice(0);
-            o.setChannelUserId("1008611");
-            o.setChannelOrderNo(channelOrderId);
-            o.setUpdateTime(LocalDateTime.of(2018, 1, 1, 10, 10, 15));
-
+            o.setCreateTime(buildTime(2018, 1, 15));
         });
         orderMapper.insert(dbOrder);
         // 测试 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())));
+        orderMapper.insert(cloneIgnoreId(dbOrder, o -> o.setMerchantOrderId(randomString())));
+        // 测试 channelOrderNo 不匹配
+        orderMapper.insert(cloneIgnoreId(dbOrder, o -> o.setChannelOrderNo(randomString())));
+        // 测试 no 不匹配
+        orderMapper.insert(cloneIgnoreId(dbOrder, o -> o.setNo(randomString())));
         // 测试 status 不匹配
         orderMapper.insert(cloneIgnoreId(dbOrder, o -> o.setStatus(PayOrderStatusEnum.CLOSED.getStatus())));
-        // 测试 refundStatus 不匹配
-        orderMapper.insert(cloneIgnoreId(dbOrder, o -> o.setRefundStatus(PayOrderRefundStatusEnum.ALL.getStatus())));
         // 测试 createTime 不匹配
-        orderMapper.insert(cloneIgnoreId(dbOrder, o -> o.setCreateTime(LocalDateTime.of(2019, 1, 1, 10, 10,
-                1))));
+        orderMapper.insert(cloneIgnoreId(dbOrder, o -> o.setCreateTime(buildTime(2019, 1, 1))));
         // 准备参数
         PayOrderExportReqVO reqVO = new PayOrderExportReqVO();
         reqVO.setAppId(1L);
-        reqVO.setChannelId(1L);
         reqVO.setChannelCode(PayChannelEnum.WX_PUB.getCode());
-        reqVO.setMerchantOrderId(merchantOrderId);
-        reqVO.setNotifyStatus(PayOrderNotifyStatusEnum.SUCCESS.getStatus());
+        reqVO.setMerchantOrderId("11");
+        reqVO.setChannelOrderNo("22");
+        reqVO.setNo("33");
         reqVO.setStatus(PayOrderStatusEnum.SUCCESS.getStatus());
-        reqVO.setRefundStatus(PayOrderRefundStatusEnum.NO.getStatus());
-        reqVO.setCreateTime((new LocalDateTime[]{LocalDateTime.of(2018, 1, 1, 10, 1, 0), LocalDateTime.of(2018, 1, 1, 10, 1, 0)}));
+        reqVO.setCreateTime(buildBetweenTime(2018, 1, 10, 2018, 1, 30));
 
         // 调用
         List<PayOrderDO> list = orderService.getOrderList(reqVO);
@@ -197,4 +174,393 @@ public class PayOrderServiceTest extends BaseDbUnitTest {
         assertPojoEquals(dbOrder, list.get(0));
     }
 
+    @Test
+    public void testCreateOrder_success() {
+        // mock 参数
+        PayOrderCreateReqDTO reqDTO = randomPojo(PayOrderCreateReqDTO.class,
+                o -> o.setAppId(1L).setMerchantOrderId("10")
+                        .setSubject(randomString()).setBody(randomString()));
+        // mock 方法
+        PayAppDO app = randomPojo(PayAppDO.class, o -> o.setId(1L).setOrderNotifyUrl("http://127.0.0.1"));
+        when(appService.validPayApp(eq(reqDTO.getAppId()))).thenReturn(app);
+
+        // 调用
+        Long orderId = orderService.createOrder(reqDTO);
+        // 断言
+        PayOrderDO order = orderMapper.selectById(orderId);
+        assertPojoEquals(order, reqDTO);
+        assertEquals(order.getAppId(), 1L);
+        assertEquals(order.getNotifyUrl(), "http://127.0.0.1");
+        assertEquals(order.getStatus(), PayOrderStatusEnum.WAITING.getStatus());
+        assertEquals(order.getRefundPrice(), 0);
+    }
+
+    @Test
+    public void testCreateOrder_exists() {
+        // mock 参数
+        PayOrderCreateReqDTO reqDTO = randomPojo(PayOrderCreateReqDTO.class,
+                o -> o.setAppId(1L).setMerchantOrderId("10"));
+        // mock 数据
+        PayOrderDO dbOrder = randomPojo(PayOrderDO.class,  o -> o.setAppId(1L).setMerchantOrderId("10"));
+        orderMapper.insert(dbOrder);
+
+        // 调用
+        Long orderId = orderService.createOrder(reqDTO);
+        // 断言
+        PayOrderDO order = orderMapper.selectById(orderId);
+        assertPojoEquals(dbOrder, order);
+    }
+
+    @Test
+    public void testSubmitOrder_notFound() {
+        // 准备参数
+        PayOrderSubmitReqVO reqVO = randomPojo(PayOrderSubmitReqVO.class);
+        String userIp = randomString();
+
+        // 调用, 并断言异常
+        assertServiceException(() -> orderService.submitOrder(reqVO, userIp), ORDER_NOT_FOUND);
+    }
+
+    @Test
+    public void testSubmitOrder_notWaiting() {
+        // mock 数据(order)
+        PayOrderDO order = randomPojo(PayOrderDO.class, o -> o.setStatus(PayOrderStatusEnum.SUCCESS.getStatus()));
+        orderMapper.insert(order);
+        // 准备参数
+        PayOrderSubmitReqVO reqVO = randomPojo(PayOrderSubmitReqVO.class, o -> o.setId(order.getId()));
+        String userIp = randomString();
+
+        // 调用, 并断言异常
+        assertServiceException(() -> orderService.submitOrder(reqVO, userIp), ORDER_STATUS_IS_NOT_WAITING);
+    }
+
+    @Test
+    public void testSubmitOrder_expired() {
+        // mock 数据(order)
+        PayOrderDO order = randomPojo(PayOrderDO.class, o -> o.setStatus(PayOrderStatusEnum.WAITING.getStatus())
+                .setExpireTime(addTime(Duration.ofDays(-1))));
+        orderMapper.insert(order);
+        // 准备参数
+        PayOrderSubmitReqVO reqVO = randomPojo(PayOrderSubmitReqVO.class, o -> o.setId(order.getId()));
+        String userIp = randomString();
+
+        // 调用, 并断言异常
+        assertServiceException(() -> orderService.submitOrder(reqVO, userIp), ORDER_IS_EXPIRED);
+    }
+
+    @Test
+    public void testSubmitOrder_channelNotFound() {
+        // mock 数据(order)
+        PayOrderDO order = randomPojo(PayOrderDO.class, o -> o.setStatus(PayOrderStatusEnum.WAITING.getStatus())
+                .setAppId(1L).setExpireTime(addTime(Duration.ofDays(1))));
+        orderMapper.insert(order);
+        // 准备参数
+        PayOrderSubmitReqVO reqVO = randomPojo(PayOrderSubmitReqVO.class, o -> o.setId(order.getId())
+                .setChannelCode(PayChannelEnum.ALIPAY_APP.getCode()));
+        String userIp = randomString();
+        // mock 方法(app)
+        PayAppDO app = randomPojo(PayAppDO.class, o -> o.setId(1L));
+        when(appService.validPayApp(eq(1L))).thenReturn(app);
+        // mock 方法(channel)
+        PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setCode(PayChannelEnum.ALIPAY_APP.getCode()));
+        when(channelService.validPayChannel(eq(1L), eq(PayChannelEnum.ALIPAY_APP.getCode())))
+                .thenReturn(channel);
+
+        // 调用, 并断言异常
+        assertServiceException(() -> orderService.submitOrder(reqVO, userIp), CHANNEL_NOT_FOUND);
+    }
+
+    @Test // 调用 unifiedOrder 接口,返回存在渠道错误
+    public void testSubmitOrder_channelError() {
+        PayOrderServiceImpl payOrderServiceImpl = mock(PayOrderServiceImpl.class);
+        try (MockedStatic<SpringUtil> springUtilMockedStatic = mockStatic(SpringUtil.class)) {
+            springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(PayOrderServiceImpl.class)))
+                    .thenReturn(payOrderServiceImpl);
+
+            // mock 数据(order)
+            PayOrderDO order = randomPojo(PayOrderDO.class, o -> o.setStatus(PayOrderStatusEnum.WAITING.getStatus())
+                    .setAppId(1L).setExpireTime(addTime(Duration.ofDays(1))));
+            orderMapper.insert(order);
+            // 准备参数
+            PayOrderSubmitReqVO reqVO = randomPojo(PayOrderSubmitReqVO.class, o -> o.setId(order.getId())
+                    .setChannelCode(PayChannelEnum.ALIPAY_APP.getCode()));
+            String userIp = randomString();
+            // mock 方法(app)
+            PayAppDO app = randomPojo(PayAppDO.class, o -> o.setId(1L));
+            when(appService.validPayApp(eq(1L))).thenReturn(app);
+            // mock 方法(channel)
+            PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L)
+                    .setCode(PayChannelEnum.ALIPAY_APP.getCode()));
+            when(channelService.validPayChannel(eq(1L), eq(PayChannelEnum.ALIPAY_APP.getCode())))
+                    .thenReturn(channel);
+            // mock 方法(client)
+            PayClient client = mock(PayClient.class);
+            when(payClientFactory.getPayClient(eq(10L))).thenReturn(client);
+            // mock 方法()
+            PayOrderRespDTO unifiedOrderResp = randomPojo(PayOrderRespDTO.class, o ->
+                    o.setChannelErrorCode("001").setChannelErrorMsg("模拟异常"));
+            when(client.unifiedOrder(argThat(payOrderUnifiedReqDTO -> {
+                assertNotNull(payOrderUnifiedReqDTO.getOutTradeNo());
+                assertThat(payOrderUnifiedReqDTO)
+                        .extracting("subject", "body", "notifyUrl", "returnUrl", "price", "expireTime")
+                        .containsExactly(order.getSubject(), order.getBody(), "http://127.0.0.1/10",
+                                reqVO.getReturnUrl(), order.getPrice(), order.getExpireTime());
+                return true;
+            }))).thenReturn(unifiedOrderResp);
+
+            // 调用,并断言异常
+            assertServiceException(() -> orderService.submitOrder(reqVO, userIp),
+                    ORDER_SUBMIT_CHANNEL_ERROR, "001", "模拟异常");
+            // 断言,数据记录(PayOrderExtensionDO)
+            PayOrderExtensionDO orderExtension = orderExtensionMapper.selectOne(null);
+            assertNotNull(orderExtension);
+            assertThat(orderExtension).extracting("no", "orderId").isNotNull();
+            assertThat(orderExtension)
+                    .extracting("channelId", "channelCode","userIp" ,"status", "channelExtras",
+                            "channelErrorCode", "channelErrorMsg", "channelNotifyData")
+                    .containsExactly(10L, PayChannelEnum.ALIPAY_APP.getCode(), userIp,
+                            PayOrderStatusEnum.WAITING.getStatus(), reqVO.getChannelExtras(),
+                            null, null, null);
+        }
+    }
+
+    @Test
+    public void testSubmitOrder_success() {
+        PayOrderServiceImpl payOrderServiceImpl = mock(PayOrderServiceImpl.class);
+        try (MockedStatic<SpringUtil> springUtilMockedStatic = mockStatic(SpringUtil.class)) {
+            springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(PayOrderServiceImpl.class)))
+                    .thenReturn(payOrderServiceImpl);
+
+            // mock 数据(order)
+            PayOrderDO order = randomPojo(PayOrderDO.class, o -> o.setStatus(PayOrderStatusEnum.WAITING.getStatus())
+                    .setAppId(1L).setExpireTime(addTime(Duration.ofDays(1))));
+            orderMapper.insert(order);
+            // 准备参数
+            PayOrderSubmitReqVO reqVO = randomPojo(PayOrderSubmitReqVO.class, o -> o.setId(order.getId())
+                    .setChannelCode(PayChannelEnum.ALIPAY_APP.getCode()));
+            String userIp = randomString();
+            // mock 方法(app)
+            PayAppDO app = randomPojo(PayAppDO.class, o -> o.setId(1L));
+            when(appService.validPayApp(eq(1L))).thenReturn(app);
+            // mock 方法(channel)
+            PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L)
+                    .setCode(PayChannelEnum.ALIPAY_APP.getCode()));
+            when(channelService.validPayChannel(eq(1L), eq(PayChannelEnum.ALIPAY_APP.getCode())))
+                    .thenReturn(channel);
+            // mock 方法(client)
+            PayClient client = mock(PayClient.class);
+            when(payClientFactory.getPayClient(eq(10L))).thenReturn(client);
+            // mock 方法()
+            PayOrderRespDTO unifiedOrderResp = randomPojo(PayOrderRespDTO.class, o -> o.setChannelErrorCode(null).setChannelErrorMsg(null)
+                    .setDisplayMode(PayOrderDisplayModeEnum.URL.getMode()).setDisplayContent("tudou"));
+            when(client.unifiedOrder(argThat(payOrderUnifiedReqDTO -> {
+                assertNotNull(payOrderUnifiedReqDTO.getOutTradeNo());
+                assertThat(payOrderUnifiedReqDTO)
+                        .extracting("subject", "body", "notifyUrl", "returnUrl", "price", "expireTime")
+                        .containsExactly(order.getSubject(), order.getBody(), "http://127.0.0.1/10",
+                                reqVO.getReturnUrl(), order.getPrice(), order.getExpireTime());
+                return true;
+            }))).thenReturn(unifiedOrderResp);
+
+            // 调用
+            PayOrderSubmitRespVO result = orderService.submitOrder(reqVO, userIp);
+            // 断言,数据记录(PayOrderExtensionDO)
+            PayOrderExtensionDO orderExtension = orderExtensionMapper.selectOne(null);
+            assertNotNull(orderExtension);
+            assertThat(orderExtension).extracting("no", "orderId").isNotNull();
+            assertThat(orderExtension)
+                    .extracting("channelId", "channelCode","userIp" ,"status", "channelExtras",
+                            "channelErrorCode", "channelErrorMsg", "channelNotifyData")
+                    .containsExactly(10L, PayChannelEnum.ALIPAY_APP.getCode(), userIp,
+                            PayOrderStatusEnum.WAITING.getStatus(), reqVO.getChannelExtras(),
+                            null, null, null);
+            // 断言,返回(PayOrderSubmitRespVO)
+            assertThat(result)
+                    .extracting("status", "displayMode", "displayContent")
+                    .containsExactly(PayOrderStatusEnum.WAITING.getStatus(), PayOrderDisplayModeEnum.URL.getMode(), "tudou");
+            // 断言,调用
+            verify(payOrderServiceImpl).notifyOrder(same(channel), same(unifiedOrderResp));
+        }
+    }
+
+    @Test
+    public void testNotifyOrder_channelId() {
+        PayOrderServiceImpl payOrderServiceImpl = mock(PayOrderServiceImpl.class);
+        try (MockedStatic<SpringUtil> springUtilMockedStatic = mockStatic(SpringUtil.class)) {
+            springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(PayOrderServiceImpl.class)))
+                    .thenReturn(payOrderServiceImpl);
+            // 准备参数
+            Long channelId = 10L;
+            PayOrderRespDTO notify = randomPojo(PayOrderRespDTO.class);
+            // mock 方法(channel)
+            PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L));
+            when(channelService.validPayChannel(eq(10L))).thenReturn(channel);
+
+            // 调用
+            orderService.notifyOrder(channelId, notify);
+            // 断言
+            verify(payOrderServiceImpl).notifyOrder(same(channel), same(notify));
+        }
+    }
+
+    @Test
+    public void testNotifyOrderSuccess_orderExtension_notFound() {
+        // 准备参数
+        PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L));
+        PayOrderRespDTO notify = randomPojo(PayOrderRespDTO.class,
+                o -> o.setStatus(PayOrderStatusRespEnum.SUCCESS.getStatus()));
+
+        // 调用,并断言异常
+        assertServiceException(() -> orderService.notifyOrder(channel, notify),
+                ORDER_EXTENSION_NOT_FOUND);
+    }
+
+    @Test
+    public void testNotifyOrderSuccess_orderExtension_closed() {
+        // mock 数据(PayOrderExtensionDO)
+        PayOrderExtensionDO orderExtension = randomPojo(PayOrderExtensionDO.class,
+                o -> o.setStatus(PayOrderStatusEnum.CLOSED.getStatus())
+                        .setNo("P110"));
+        orderExtensionMapper.insert(orderExtension);
+        // 准备参数
+        PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L));
+        PayOrderRespDTO notify = randomPojo(PayOrderRespDTO.class,
+                o -> o.setStatus(PayOrderStatusRespEnum.SUCCESS.getStatus())
+                        .setOutTradeNo("P110"));
+
+        // 调用,并断言异常
+        assertServiceException(() -> orderService.notifyOrder(channel, notify),
+                ORDER_EXTENSION_STATUS_IS_NOT_WAITING);
+    }
+
+    @Test
+    public void testNotifyOrderSuccess_order_notFound() {
+        // mock 数据(PayOrderExtensionDO)
+        PayOrderExtensionDO orderExtension = randomPojo(PayOrderExtensionDO.class,
+                o -> o.setStatus(PayOrderStatusEnum.SUCCESS.getStatus())
+                        .setNo("P110"));
+        orderExtensionMapper.insert(orderExtension);
+        // 准备参数
+        PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L));
+        PayOrderRespDTO notify = randomPojo(PayOrderRespDTO.class,
+                o -> o.setStatus(PayOrderStatusRespEnum.SUCCESS.getStatus())
+                        .setOutTradeNo("P110"));
+
+        // 调用,并断言异常
+        assertServiceException(() -> orderService.notifyOrder(channel, notify),
+                ORDER_NOT_FOUND);
+        // 断言 PayOrderExtensionDO :数据更新被回滚
+        assertPojoEquals(orderExtension, orderExtensionMapper.selectOne(null));
+    }
+
+    @Test
+    public void testNotifyOrderSuccess_order_closed() {
+        testNotifyOrderSuccess_order_closedOrRefund(PayOrderStatusEnum.CLOSED.getStatus());
+    }
+
+    @Test
+    public void testNotifyOrderSuccess_order_refund() {
+        testNotifyOrderSuccess_order_closedOrRefund(PayOrderStatusEnum.REFUND.getStatus());
+    }
+
+    private void testNotifyOrderSuccess_order_closedOrRefund(Integer status) {
+        // mock 数据(PayOrderDO)
+        PayOrderDO order = randomPojo(PayOrderDO.class, o -> o.setStatus(status));
+        orderMapper.insert(order);
+        // mock 数据(PayOrderExtensionDO)
+        PayOrderExtensionDO orderExtension = randomPojo(PayOrderExtensionDO.class,
+                o -> o.setStatus(PayOrderStatusEnum.SUCCESS.getStatus())
+                        .setNo("P110")
+                        .setOrderId(order.getId()));
+        orderExtensionMapper.insert(orderExtension);
+        // 准备参数
+        PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L));
+        PayOrderRespDTO notify = randomPojo(PayOrderRespDTO.class,
+                o -> o.setStatus(PayOrderStatusRespEnum.SUCCESS.getStatus())
+                        .setOutTradeNo("P110"));
+
+        // 调用,并断言异常
+        assertServiceException(() -> orderService.notifyOrder(channel, notify),
+                ORDER_STATUS_IS_NOT_WAITING);
+        // 断言 PayOrderExtensionDO :数据未更新,因为它是 SUCCESS
+        assertPojoEquals(orderExtension, orderExtensionMapper.selectOne(null));
+    }
+
+    @Test
+    public void testNotifyOrderSuccess_order_paid() {
+        // mock 数据(PayOrderDO)
+        PayOrderDO order = randomPojo(PayOrderDO.class,
+                o -> o.setStatus(PayOrderStatusEnum.SUCCESS.getStatus()));
+        orderMapper.insert(order);
+        // mock 数据(PayOrderExtensionDO)
+        PayOrderExtensionDO orderExtension = randomPojo(PayOrderExtensionDO.class,
+                o -> o.setStatus(PayOrderStatusEnum.SUCCESS.getStatus())
+                        .setNo("P110")
+                        .setOrderId(order.getId()));
+        orderExtensionMapper.insert(orderExtension);
+        // 重要:需要将 order 的 extensionId 更新下
+        order.setExtensionId(orderExtension.getId());
+        orderMapper.updateById(order);
+        // 准备参数
+        PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L));
+        PayOrderRespDTO notify = randomPojo(PayOrderRespDTO.class,
+                o -> o.setStatus(PayOrderStatusRespEnum.SUCCESS.getStatus())
+                        .setOutTradeNo("P110"));
+
+        // 调用,并断言异常
+        orderService.notifyOrder(channel, notify);
+        // 断言 PayOrderExtensionDO :数据未更新,因为它是 SUCCESS
+        assertPojoEquals(orderExtension, orderExtensionMapper.selectOne(null));
+        // 断言 PayOrderDO :数据未更新,因为它是 SUCCESS
+        assertPojoEquals(order, orderMapper.selectOne(null));
+        // 断言,调用
+        verify(notifyService, never()).createPayNotifyTask(any(PayNotifyTaskCreateReqDTO.class));
+    }
+
+    @Test
+    public void testNotifyOrderSuccess_order_waiting() {
+        // mock 数据(PayOrderDO)
+        PayOrderDO order = randomPojo(PayOrderDO.class,
+                o -> o.setStatus(PayOrderStatusEnum.WAITING.getStatus())
+                        .setPrice(10));
+        orderMapper.insert(order);
+        // mock 数据(PayOrderExtensionDO)
+        PayOrderExtensionDO orderExtension = randomPojo(PayOrderExtensionDO.class,
+                o -> o.setStatus(PayOrderStatusEnum.WAITING.getStatus())
+                        .setNo("P110")
+                        .setOrderId(order.getId()));
+        orderExtensionMapper.insert(orderExtension);
+        // 准备参数
+        PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L)
+                .setFeeRate(0.1D));
+        PayOrderRespDTO notify = randomPojo(PayOrderRespDTO.class,
+                o -> o.setStatus(PayOrderStatusRespEnum.SUCCESS.getStatus())
+                        .setOutTradeNo("P110"));
+
+        // 调用,并断言异常
+        orderService.notifyOrder(channel, notify);
+        // 断言 PayOrderExtensionDO :数据未更新,因为它是 SUCCESS
+        orderExtension.setStatus(PayOrderStatusEnum.SUCCESS.getStatus())
+                .setChannelNotifyData(toJsonString(notify));
+        assertPojoEquals(orderExtension, orderExtensionMapper.selectOne(null),
+                "updateTime", "updater");
+        // 断言 PayOrderDO :数据未更新,因为它是 SUCCESS
+        order.setStatus(PayOrderStatusEnum.SUCCESS.getStatus())
+                .setChannelId(10L).setChannelCode(channel.getCode())
+                .setSuccessTime(notify.getSuccessTime()).setExtensionId(orderExtension.getId()).setNo(orderExtension.getNo())
+                .setChannelOrderNo(notify.getChannelOrderNo()).setChannelUserId(notify.getChannelUserId())
+                .setChannelFeeRate(0.1D).setChannelFeePrice(1);
+        assertPojoEquals(order, orderMapper.selectOne(null),
+                "updateTime", "updater");
+        // 断言,调用
+        verify(notifyService).createPayNotifyTask(argThat(new ArgumentMatcher<PayNotifyTaskCreateReqDTO>() {
+            @Override
+            public boolean matches(PayNotifyTaskCreateReqDTO reqDTO) {
+                assertEquals(reqDTO.getType(), PayNotifyTypeEnum.ORDER.getType());
+                assertEquals(reqDTO.getDataId(), orderExtension.getOrderId());
+                return true;
+            }
+        }));
+    }
+
 }

+ 9 - 24
yudao-module-pay/yudao-module-pay-biz/src/test/java/cn/iocoder/yudao/module/pay/service/refund/PayRefundServiceTest.java

@@ -1,21 +1,20 @@
 package cn.iocoder.yudao.module.pay.service.refund;
 
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.framework.pay.config.PayProperties;
 import cn.iocoder.yudao.framework.pay.core.client.PayClientFactory;
 import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum;
+import cn.iocoder.yudao.framework.test.core.ut.BaseDbAndRedisUnitTest;
 import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
 import cn.iocoder.yudao.module.pay.controller.admin.refund.vo.PayRefundExportReqVO;
 import cn.iocoder.yudao.module.pay.controller.admin.refund.vo.PayRefundPageReqVO;
 import cn.iocoder.yudao.module.pay.dal.dataobject.refund.PayRefundDO;
 import cn.iocoder.yudao.module.pay.dal.mysql.refund.PayRefundMapper;
-import cn.iocoder.yudao.module.pay.enums.order.PayOrderNotifyStatusEnum;
+import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum;
 import cn.iocoder.yudao.module.pay.enums.refund.PayRefundStatusEnum;
-import cn.iocoder.yudao.module.pay.enums.order.PayOrderRefundStatusEnum;
+import cn.iocoder.yudao.module.pay.framework.pay.config.PayProperties;
 import cn.iocoder.yudao.module.pay.service.app.PayAppService;
 import cn.iocoder.yudao.module.pay.service.channel.PayChannelService;
 import cn.iocoder.yudao.module.pay.service.notify.PayNotifyService;
-import cn.iocoder.yudao.module.pay.service.order.PayOrderExtensionService;
 import cn.iocoder.yudao.module.pay.service.order.PayOrderService;
 import org.junit.jupiter.api.Test;
 import org.springframework.boot.test.mock.mockito.MockBean;
@@ -31,7 +30,7 @@ import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 
 @Import(PayRefundServiceImpl.class)
-public class PayRefundServiceTest extends BaseDbUnitTest {
+public class PayRefundServiceTest extends BaseDbAndRedisUnitTest {
 
     @Resource
     private PayRefundServiceImpl refundService;
@@ -46,8 +45,6 @@ public class PayRefundServiceTest extends BaseDbUnitTest {
     @MockBean
     private PayOrderService orderService;
     @MockBean
-    private PayOrderExtensionService orderExtensionService;
-    @MockBean
     private PayAppService appService;
     @MockBean
     private PayChannelService channelService;
@@ -66,8 +63,7 @@ public class PayRefundServiceTest extends BaseDbUnitTest {
             o.setMerchantOrderId("MOT0000001");
             o.setMerchantRefundId("MRF0000001");
             o.setNotifyUrl("https://www.cancanzi.com");
-            o.setNotifyStatus(PayOrderNotifyStatusEnum.SUCCESS.getStatus());
-            o.setStatus(PayRefundStatusEnum.SUCCESS.getStatus());
+            o.setStatus(PayOrderStatusEnum.SUCCESS.getStatus());
             o.setPayPrice(100);
             o.setRefundPrice(500);
             o.setReason("就是想退款了,你有意见吗");
@@ -87,11 +83,8 @@ public class PayRefundServiceTest extends BaseDbUnitTest {
         refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setChannelCode(PayChannelEnum.ALIPAY_APP.getCode())));
         // 测试 merchantRefundNo 不匹配
         refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setMerchantRefundId("MRF1111112")));
-        // 测试 notifyStatus 不匹配
-        refundMapper.insert(
-                cloneIgnoreId(dbRefund, o -> o.setNotifyStatus(PayOrderNotifyStatusEnum.FAILURE.getStatus())));
         // 测试 status 不匹配
-        refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setStatus(PayRefundStatusEnum.FAILURE.getStatus())));
+        refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setStatus(PayOrderStatusEnum.WAITING.getStatus())));
         // 测试 createTime 不匹配
         refundMapper.insert(cloneIgnoreId(dbRefund, o ->
                 o.setCreateTime(LocalDateTime.of(2022, 1, 1, 10, 10, 10))));
@@ -99,8 +92,7 @@ public class PayRefundServiceTest extends BaseDbUnitTest {
         PayRefundPageReqVO reqVO = new PayRefundPageReqVO();
         reqVO.setAppId(1L);
         reqVO.setChannelCode(PayChannelEnum.WX_PUB.getCode());
-        reqVO.setMerchantRefundNo("MRF0000001");
-        reqVO.setNotifyStatus(PayOrderNotifyStatusEnum.SUCCESS.getStatus());
+        reqVO.setMerchantRefundId("MRF0000001");
         reqVO.setStatus(PayRefundStatusEnum.SUCCESS.getStatus());
         reqVO.setCreateTime((new LocalDateTime[]{LocalDateTime.of(2021, 1, 1, 10, 10, 10), LocalDateTime.of(2021, 1, 1, 10, 10, 12)}));
 
@@ -124,8 +116,7 @@ public class PayRefundServiceTest extends BaseDbUnitTest {
             o.setMerchantOrderId("MOT0000001");
             o.setMerchantRefundId("MRF0000001");
             o.setNotifyUrl("https://www.cancanzi.com");
-            o.setNotifyStatus(PayOrderNotifyStatusEnum.SUCCESS.getStatus());
-            o.setStatus(PayRefundStatusEnum.SUCCESS.getStatus());
+            o.setStatus(PayOrderStatusEnum.SUCCESS.getStatus());
             o.setPayPrice(100);
             o.setRefundPrice(500);
             o.setReason("就是想退款了,你有意见吗");
@@ -145,11 +136,8 @@ public class PayRefundServiceTest extends BaseDbUnitTest {
         refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setChannelCode(PayChannelEnum.ALIPAY_APP.getCode())));
         // 测试 merchantRefundNo 不匹配
         refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setMerchantRefundId("MRF1111112")));
-        // 测试 notifyStatus 不匹配
-        refundMapper.insert(
-                cloneIgnoreId(dbRefund, o -> o.setNotifyStatus(PayOrderNotifyStatusEnum.FAILURE.getStatus())));
         // 测试 status 不匹配
-        refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setStatus(PayRefundStatusEnum.FAILURE.getStatus())));
+        refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setStatus(PayOrderStatusEnum.WAITING.getStatus())));
         // 测试 createTime 不匹配
         refundMapper.insert(cloneIgnoreId(dbRefund, o ->
                 o.setCreateTime(LocalDateTime.of(2022, 1, 1, 10, 10, 10))));
@@ -158,10 +146,7 @@ public class PayRefundServiceTest extends BaseDbUnitTest {
         PayRefundExportReqVO reqVO = new PayRefundExportReqVO();
         reqVO.setAppId(1L);
         reqVO.setChannelCode(PayChannelEnum.WX_PUB.getCode());
-        reqVO.setMerchantRefundNo("MRF0000001");
-        reqVO.setNotifyStatus(PayOrderNotifyStatusEnum.SUCCESS.getStatus());
         reqVO.setStatus(PayRefundStatusEnum.SUCCESS.getStatus());
-        reqVO.setType(PayOrderRefundStatusEnum.PART.getStatus());
         reqVO.setCreateTime((new LocalDateTime[]{LocalDateTime.of(2021, 1, 1, 10, 10, 10), LocalDateTime.of(2021, 1, 1, 10, 10, 12)}));
 
         // 调用

+ 1 - 0
yudao-module-pay/yudao-module-pay-biz/src/test/resources/sql/clean.sql

@@ -1,4 +1,5 @@
 DELETE FROM pay_app;
 DELETE FROM pay_channel;
 DELETE FROM pay_order;
+DELETE FROM pay_order_extension;
 DELETE FROM pay_refund;

+ 26 - 8
yudao-module-pay/yudao-module-pay-biz/src/test/resources/sql/create_tables.sql

@@ -39,19 +39,17 @@ CREATE TABLE IF NOT EXISTS `pay_order` (
     `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,
+    `price`                bigint(20)    NOT NULL,
     `channel_fee_rate`     double                 DEFAULT 0,
-    `channel_fee_amount`   bigint(20)             DEFAULT 0,
+    `channel_fee_price`    bigint(20)             DEFAULT 0,
     `status`               tinyint(4)    NOT NULL,
     `user_ip`              varchar(50)   NOT NULL,
-    `expire_time`          datetime(0)   NOT NULL DEFAULT CURRENT_TIMESTAMP,
+    `expire_time`          timestamp   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,
+    `extension_id` bigint(20)             DEFAULT NULL,
+    `no`                   varchar(64)   NULL,
+    `refund_price`         bigint(20)    NOT NULL,
     `channel_user_id`      varchar(255)           DEFAULT NULL,
     `channel_order_no`     varchar(64)            DEFAULT NULL,
     `creator`              varchar(64)            DEFAULT '',
@@ -62,6 +60,26 @@ CREATE TABLE IF NOT EXISTS `pay_order` (
     PRIMARY KEY ("id")
 ) COMMENT = '支付订单';
 
+CREATE TABLE IF NOT EXISTS `pay_order_extension` (
+    "id" number NOT NULL GENERATED BY DEFAULT AS IDENTITY,
+    `no`           varchar(64)         NOT NULL,
+    `order_id`           bigint(20)    NOT NULL,
+    `channel_id`         bigint(20)    NOT NULL,
+    `channel_code`       varchar(32)   NOT NULL,
+    `user_ip`            varchar(50)   NULL     DEFAULT NULL,
+    `status`             tinyint(4)    NOT NULL,
+    `channel_extras`     varchar(1024) NULL     DEFAULT NULL,
+    `channel_error_code`  varchar(64)  NULL,
+    `channel_error_msg` varchar(64)    NULL,
+    `channel_notify_data` varchar(1024)  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 `pay_refund` (
     "id" number NOT NULL GENERATED BY DEFAULT AS IDENTITY,
     `app_id`             bigint(20)    NOT NULL,

+ 9 - 22
yudao-ui-admin/src/api/pay/app.js

@@ -1,6 +1,6 @@
 import request from '@/utils/request'
 
-// 创建支付应用信息
+// 创建支付应用
 export function createApp(data) {
   return request({
     url: '/pay/app/create',
@@ -9,7 +9,7 @@ export function createApp(data) {
   })
 }
 
-// 更新支付应用信息
+// 更新支付应用
 export function updateApp(data) {
   return request({
     url: '/pay/app/update',
@@ -18,7 +18,7 @@ export function updateApp(data) {
   })
 }
 
-// 支付应用信息状态修改
+// 支付应用状态修改
 export function changeAppStatus(id, status) {
   const data = {
     id,
@@ -31,7 +31,7 @@ export function changeAppStatus(id, status) {
   })
 }
 
-// 删除支付应用信息
+// 删除支付应用
 export function deleteApp(id) {
   return request({
     url: '/pay/app/delete?id=' + id,
@@ -39,7 +39,7 @@ export function deleteApp(id) {
   })
 }
 
-// 获得支付应用信息
+// 获得支付应用
 export function getApp(id) {
   return request({
     url: '/pay/app/get?id=' + id,
@@ -47,7 +47,7 @@ export function getApp(id) {
   })
 }
 
-// 获得支付应用信息分页
+// 获得支付应用分页
 export function getAppPage(query) {
   return request({
     url: '/pay/app/page',
@@ -56,23 +56,10 @@ export function getAppPage(query) {
   })
 }
 
-// 导出支付应用信息 Excel
-export function exportAppExcel(query) {
+// 获得支付应用列表
+export function getAppList() {
   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
-    },
+    url: '/pay/app/list',
     method: 'get'
   })
 }

+ 18 - 0
yudao-ui-admin/src/api/pay/notify.js

@@ -0,0 +1,18 @@
+import request from '@/utils/request'
+
+// 获得支付通知明细
+export function getNotifyTaskDetail(id) {
+  return request({
+    url: '/pay/notify/get-detail?id=' + id,
+    method: 'get'
+  })
+}
+
+// 获得支付通知分页
+export function getNotifyTaskPage(query) {
+  return request({
+    url: '/pay/notify/page',
+    method: 'get',
+    params: query
+  })
+}

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

@@ -206,42 +206,6 @@ export const PayOrderStatusEnum = {
   }
 }
 
-/**
- * 支付订单回调状态枚举
- */
-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: '全部退款'
-  }
-}
-
 /**
  * 支付退款订单状态枚举
  */

+ 4 - 7
yudao-ui-admin/src/utils/dict.js

@@ -51,15 +51,12 @@ export const DICT_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: 'pay_channel_code', // 支付渠道编码类型
-  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', // 退款订单类别
+  PAY_REFUND_STATUS: 'pay_refund_status', // 退款订单状态
+  PAY_NOTIFY_STATUS: 'pay_notify_status', // 商户支付回调状态
+  PAY_NOTIFY_TYPE: 'pay_notify_type', // 商户支付回调状态
 
   // ========== MP 模块 ==========
   MP_AUTO_REPLY_REQUEST_MATCH: 'mp_auto_reply_request_match', // 自动回复请求匹配类型

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

@@ -1,349 +0,0 @@
-<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: '',
-  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,
-    }
-  },
-  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.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;
-          // 只有在初次进来为编辑 并且为加载中的时候才回去请求数据
-          if (newVal.edit === true && newVal.loading) {
-            this.title = "编辑支付渠道";
-            this.init();
-          } else {
-            this.title = "创建支付渠道";
-          }
-        }
-      }
-    }
-  },
-
-  methods: {
-    init() {
-      getChannel(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.$modal.msgSuccess("修改成功");
-              this.close();
-            }
-
-          })
-        } else {
-
-          createChannel(data).then(response => {
-            if (response.code === 0) {
-              this.$modal.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>

+ 268 - 0
yudao-ui-admin/src/views/pay/app/components/alipayChannelForm.vue

@@ -0,0 +1,268 @@
+<template>
+  <div>
+    <el-dialog :visible.sync="dialogVisible" :title="title" @closed="close" append-to-body width="800px">
+      <el-form ref="form" :model="formData" :rules="rules" size="medium" label-width="100px" v-loading="formLoading">
+        <el-form-item label-width="180px" label="渠道费率" prop="feeRate">
+          <el-input v-model="formData.feeRate" placeholder="请输入渠道费率" clearable :style="{width: '100%'}">
+            <template slot="append">%</template>
+          </el-input>
+        </el-form-item>
+        <el-form-item label-width="180px" label="开放平台 APPID" prop="config.appId">
+          <el-input v-model="formData.config.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="formData.status" size="medium">
+            <el-radio v-for="dict in this.getDictDatas(DICT_TYPE.COMMON_STATUS)" :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="config.serverUrl">
+          <el-radio-group v-model="formData.config.serverUrl" size="medium">
+            <el-radio label="https://openapi.alipay.com/gateway.do">线上环境</el-radio>
+            <el-radio label="https://openapi-sandbox.dl.alipaydev.com/gateway.do">沙箱环境</el-radio>
+          </el-radio-group>
+        </el-form-item>
+        <el-form-item label-width="180px" label="算法类型" prop="config.signType">
+          <el-radio-group v-model="formData.config.signType" size="medium">
+            <el-radio key="RSA2" label="RSA2">RSA2</el-radio>
+          </el-radio-group>
+        </el-form-item>
+        <el-form-item label-width="180px" label="公钥类型" prop="config.mode">
+          <el-radio-group v-model="formData.config.mode" size="medium">
+            <el-radio  key="公钥模式" :label="1">公钥模式</el-radio>
+            <el-radio  key="证书模式" :label="2">证书模式</el-radio>
+          </el-radio-group>
+        </el-form-item>
+        <div v-if="formData.config.mode === 1">
+          <el-form-item label-width="180px" label="应用私钥" prop="config.privateKey">
+            <el-input type="textarea" :autosize="{minRows: 8, maxRows: 8}" v-model="formData.config.privateKey"
+                      placeholder="请输入应用私钥" clearable :style="{width: '100%'}">
+            </el-input>
+          </el-form-item>
+          <el-form-item label-width="180px" label="支付宝公钥" prop="config.alipayPublicKey">
+            <el-input
+              type="textarea"
+              :autosize="{minRows: 8, maxRows: 8}"
+              v-model="formData.config.alipayPublicKey"
+              placeholder="请输入支付宝公钥" clearable
+              :style="{width: '100%'}">
+            </el-input>
+          </el-form-item>
+        </div>
+        <div v-if="formData.config.mode === 2">
+          <el-form-item label-width="180px" label="商户公钥应用证书" prop="config.appCertContent">
+            <el-input v-model="formData.config.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="config.alipayPublicCertContent">
+            <el-input v-model="formData.config.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="config.rootCertContent">
+            <el-input
+              v-model="formData.config.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="formData.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="submitForm">确定</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+<script>
+import { createChannel, getChannel, updateChannel } from "@/api/pay/channel";
+import { CommonStatusEnum } from "@/utils/constants";
+
+export default {
+  name: "alipayChannelForm",
+  data() {
+    return {
+      dialogVisible: false,
+      formLoading: false,
+      title:'',
+      formData: {
+        appId: '',
+        code: '',
+        status: undefined,
+        feeRate: undefined,
+        remark: '',
+        config: {
+          appId: '',
+          serverUrl: null,
+          signType: '',
+          mode: null,
+          privateKey: '',
+          alipayPublicKey: '',
+          appCertContent: '',
+          alipayPublicCertContent: '',
+          rootCertContent: ''
+        }
+      },
+      rules: {
+        feeRate: [{ required: true,  message: '请输入渠道费率', trigger: 'blur' }],
+        status: [{ required: true,  message: '渠道状态不能为空',  trigger: 'blur' }],
+        'config.appId': [{ required: true, message: '请输入开放平台上创建的应用的 ID', trigger: 'blur' }],
+        'config.serverUrl': [{ required: true, message: '请传入网关地址', trigger: 'blur' }],
+        'config.signType': [{ required: true, message: '请传入签名算法类型', trigger: 'blur' }],
+        'config.mode': [{ required: true, message: '公钥类型不能为空', trigger: 'blur'}],
+        'config.privateKey': [{ required: true, message: '请输入商户私钥', trigger: 'blur' }],
+        'config.alipayPublicKey': [{ required: true, message: '请输入支付宝公钥字符串', trigger: 'blur' }],
+        'config.appCertContent': [{ required: true,  message: '请上传商户公钥应用证书', trigger: 'blur' }],
+        'config.alipayPublicCertContent': [{ required: true, message: '请上传支付宝公钥证书', trigger: 'blur'}],
+        'config.rootCertContent': [{ required: true, message: '请上传指定根证书', trigger: 'blur' }],
+      },
+      fileAccept: ".crt",
+    }
+  },
+  methods: {
+    open(appId, code) {
+      this.dialogVisible = true;
+      this.formLoading = true;
+      this.reset(appId, code);
+      getChannel(appId, code).then(response => {
+        if (response.data && response.data.id) {
+          this.formData = response.data;
+          this.formData.config = JSON.parse(response.data.config);
+        }
+        this.title = this.formData.id ? '创建支付渠道' : '编辑支付渠道'
+      }).finally(() => {
+        this.formLoading = false;
+      });
+    },
+    close() {
+      this.dialogVisible = false;
+      this.reset(undefined, undefined);
+    },
+    submitForm() {
+      this.$refs['form'].validate(valid => {
+        if (!valid) {
+          return
+        }
+        const data = { ...this.formData };
+        data.config = JSON.stringify(this.formData.config);
+        if (!data.id) {
+          createChannel(data).then(response => {
+            this.$modal.msgSuccess("新增成功");
+            this.$emit('success')
+            this.close();
+          });
+        } else {
+          updateChannel(data).then(response => {
+            this.$modal.msgSuccess("修改成功");
+            this.$emit('success')
+            this.close();
+          })
+        }
+      });
+    },
+    /** 重置表单 */
+    reset(appId, code) {
+      this.formData = {
+        appId: appId,
+        code: code,
+        status: CommonStatusEnum.ENABLE,
+        remark: '',
+        feeRate: null,
+        config: {
+          appId: '',
+          serverUrl: null,
+          signType: 'RSA2',
+          mode: null,
+          privateKey: '',
+          alipayPublicKey: '',
+          appCertContent: '',
+          alipayPublicCertContent: '',
+          rootCertContent: ''
+        }
+      }
+      this.resetForm('form')
+    },
+    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.formData.config.appCertContent = e.target.result
+      }
+      readFile.readAsText(event.file);
+    },
+    alipayPublicCertUpload(event) {
+      const readFile = new FileReader()
+      readFile.onload = (e) => {
+        this.formData.config.alipayPublicCertContent = e.target.result
+      }
+      readFile.readAsText(event.file);
+    },
+    rootCertUpload(event) {
+      const readFile = new FileReader()
+      readFile.onload = (e) => {
+        this.formData.config.rootCertContent = e.target.result
+      }
+      readFile.readAsText(event.file);
+    },
+
+  }
+}
+
+</script>
+<style scoped>
+
+</style>

+ 11 - 18
yudao-ui-admin/src/views/pay/app/index.vue

@@ -213,8 +213,10 @@
         <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>
+
+    <!-- 弹窗 -->
+    <wechat-channel-form :transferParam="channelParam" />
+    <alipay-channel-form ref="alipayChannelFormRef" @success="getList" />
   </div>
 </template>
 
@@ -223,13 +225,13 @@ import { createApp, updateApp, changeAppStatus, deleteApp, getApp, getAppPage }
 import { DICT_TYPE, getDictDatas } from "@/utils/dict";
 import { PayType, PayChannelEnum, CommonStatusEnum } from "@/utils/constants";
 import wechatChannelForm from "@/views/pay/app/components/wechatChannelForm";
-import aliPayChannelForm from "@/views/pay/app/components/aliPayChannelForm";
+import alipayChannelForm from "@/views/pay/app/components/alipayChannelForm";
 
 export default {
   name: "PayApp",
   components: {
     "wechatChannelForm": wechatChannelForm,
-    "aliPayChannelForm": aliPayChannelForm
+    "alipayChannelForm": alipayChannelForm
   },
   data() {
     return {
@@ -283,13 +285,6 @@ export default {
         "appId": null,
         // 渠道编码
         "payCode": null,
-        // 商户对象
-        "payMerchant": {
-          // 编号
-          "id": null,
-          // 名称
-          "name": null
-        },
       }
     };
   },
@@ -424,14 +419,12 @@ export default {
         this.channelParam.aliPayOpen = false;
       }
       if (type === PayType.ALIPAY) {
-        this.channelParam.aliPayOpen = true;
-        this.channelParam.wechatOpen = false;
+        console.log(this.$refs['alipayChannelFormRef'])
+        this.$refs['alipayChannelFormRef'].open(row.id, payCode);
+        return;
+        // 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;
     },
     /**
      * 根据渠道编码判断渠道列表中是否存在

+ 221 - 0
yudao-ui-admin/src/views/pay/notify/index.vue

@@ -0,0 +1,221 @@
+<template>
+  <div class="app-container">
+
+    <!-- 搜索工作栏 -->
+    <el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="100px">
+      <el-form-item label="应用编号" prop="appId">
+        <el-select clearable v-model="queryParams.appId" filterable placeholder="请选择应用信息">
+          <el-option v-for="item in appList" :key="item.id" :label="item.name" :value="item.id" />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="通知类型" prop="type">
+        <el-select v-model="queryParams.type" placeholder="请选择通知类型" clearable size="small">
+          <el-option v-for="dict in this.getDictDatas(DICT_TYPE.PAY_NOTIFY_TYPE)"
+                     :key="dict.value" :label="dict.label" :value="dict.value"/>
+        </el-select>
+      </el-form-item>
+      <el-form-item label="关联编号" prop="dataId">
+        <el-input v-model="queryParams.dataId" placeholder="请输入关联编号" clearable @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 this.getDictDatas(DICT_TYPE.PAY_NOTIFY_STATUS)"
+                     :key="dict.value" :label="dict.label" :value="dict.value"/>
+        </el-select>
+      </el-form-item>
+      <el-form-item label="商户订单编号" prop="merchantOrderId">
+        <el-input v-model="queryParams.merchantOrderId" placeholder="请输入商户订单编号" clearable @keyup.enter.native="handleQuery"/>
+      </el-form-item>
+      <el-form-item label="创建时间" prop="createTime">
+        <el-date-picker v-model="queryParams.createTime" style="width: 240px" value-format="yyyy-MM-dd HH:mm:ss" type="daterange"
+                        range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期" :default-time="['00:00:00', '23:59:59']" />
+      </el-form-item>
+      <el-form-item>
+        <el-button type="primary" icon="el-icon-search" @click="handleQuery">搜索</el-button>
+        <el-button icon="el-icon-refresh" @click="resetQuery">重置</el-button>
+      </el-form-item>
+    </el-form>
+
+    <!-- 列表 -->
+    <el-table v-loading="loading" :data="list">
+      <el-table-column label="任务编号" align="center" prop="id" />
+      <el-table-column label="应用编号" align="center" prop="appName" />
+      <el-table-column label="商户订单编号" align="center" prop="merchantOrderId" />
+      <el-table-column label="通知类型" align="center" prop="type">
+        <template v-slot="scope">
+          <dict-tag :type="DICT_TYPE.PAY_NOTIFY_TYPE" :value="scope.row.type" />
+        </template>
+      </el-table-column>
+      <el-table-column label="关联编号" align="center" prop="dataId" />
+      <el-table-column label="通知状态" align="center" prop="status">
+        <template v-slot="scope">
+          <dict-tag :type="DICT_TYPE.PAY_NOTIFY_STATUS" :value="scope.row.status" />
+        </template>
+      </el-table-column>
+      <el-table-column label="最后通知时间" align="center" prop="lastExecuteTime" width="180">
+        <template v-slot="scope">
+          <span>{{ parseTime(scope.row.lastExecuteTime) }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="下次通知时间" align="center" prop="nextNotifyTime" width="180">
+        <template v-slot="scope">
+          <span>{{ parseTime(scope.row.nextNotifyTime) }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="通知次数" align="center" prop="notifyTimes">
+        <template v-slot="scope">
+          <el-tag size="mini" type="success">
+            {{ scope.row.notifyTimes }} / {{ scope.row.maxNotifyTimes }}
+          </el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
+        <template v-slot="scope">
+          <el-button size="mini" type="text" icon="el-icon-search" @click="handleDetail(scope.row)"
+                     v-hasPermi="['pay:notify:query']">查看详情
+          </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="通知详情" :visible.sync="open" width="700px" v-dialogDrag append-to-body>
+      <el-descriptions :column="2" label-class-name="desc-label">
+        <el-descriptions-item label="商户订单编号">
+          <el-tag size="small">{{ notifyDetail.merchantOrderId }}</el-tag>
+        </el-descriptions-item>
+        <el-descriptions-item label="通知状态">
+          <dict-tag :type="DICT_TYPE.PAY_NOTIFY_STATUS" :value="notifyDetail.status" size="small" />
+        </el-descriptions-item>
+      </el-descriptions>
+      <el-descriptions :column="2" label-class-name="desc-label">
+        <el-descriptions-item label="应用编号">{{ notifyDetail.appId }}</el-descriptions-item>
+        <el-descriptions-item label="应用名称">{{ notifyDetail.appName }}</el-descriptions-item>
+      </el-descriptions>
+      <el-descriptions :column="2" label-class-name="desc-label">
+        <el-descriptions-item label="关联编号">{{ notifyDetail.dataId }}</el-descriptions-item>
+        <el-descriptions-item label="通知类型">
+          <dict-tag :type="DICT_TYPE.PAY_NOTIFY_TYPE" :value="notifyDetail.type" />
+        </el-descriptions-item>
+      </el-descriptions>
+      <el-descriptions :column="2" label-class-name="desc-label">
+        <el-descriptions-item label="通知次数">{{ notifyDetail.notifyTimes }}</el-descriptions-item>
+        <el-descriptions-item label="最大通知次数">{{ notifyDetail.maxNotifyTimes }}</el-descriptions-item>
+      </el-descriptions>
+      <el-descriptions :column="2" label-class-name="desc-label">
+        <el-descriptions-item label="最后通知时间">{{ parseTime(notifyDetail.lastExecuteTime) }}</el-descriptions-item>
+        <el-descriptions-item label="下次通知时间">{{ parseTime(notifyDetail.nextNotifyTime) }}</el-descriptions-item>
+      </el-descriptions>
+      <el-descriptions :column="2" label-class-name="desc-label">
+        <el-descriptions-item label="创建时间">{{ parseTime(notifyDetail.createTime) }}</el-descriptions-item>
+        <el-descriptions-item label="更新时间">{{ parseTime(notifyDetail.updateTime) }}</el-descriptions-item>
+      </el-descriptions>
+      <!-- 分割线 -->
+      <el-divider />
+      <el-descriptions :column="1" label-class-name="desc-label" direction="vertical" border>
+        <el-descriptions-item label="回调日志">
+          <el-table :data="notifyDetail.logs">
+            <el-table-column label="日志编号" align="center" prop="id" />
+            <el-table-column label="通知状态" align="center" prop="status">
+              <template v-slot="scope">
+                <dict-tag :type="DICT_TYPE.PAY_NOTIFY_STATUS" :value="scope.row.status" />
+              </template>
+            </el-table-column>
+            <el-table-column label="通知次数" align="center" prop="notifyTimes" />
+            <el-table-column label="通知时间" align="center" prop="lastExecuteTime" width="180">
+              <template v-slot="scope">
+                <span>{{ parseTime(scope.row.createTime) }}</span>
+              </template>
+            </el-table-column>
+            <el-table-column label="响应结果" align="center" prop="response" />
+          </el-table>
+        </el-descriptions-item>
+      </el-descriptions>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import { getNotifyTaskPage, getNotifyTaskDetail } from "@/api/pay/notify";
+import { getAppList } from "@/api/pay/app";
+
+export default {
+  name: "PayNotify",
+  data() {
+    return {
+      // 遮罩层
+      loading: true,
+      // 导出遮罩层
+      exportLoading: false,
+      // 显示搜索条件
+      showSearch: true,
+      // 总条数
+      total: 0,
+      // 支付通知列表
+      list: [],
+      // 是否显示弹出层
+      open: false,
+      // 查询参数
+      queryParams: {
+        pageNo: 1,
+        pageSize: 10,
+        appId: null,
+        type: null,
+        dataId: null,
+        status: null,
+        merchantOrderId: null,
+        createTime: [],
+      },
+
+      // 支付应用列表集合
+      appList: [],
+      // 通知详情
+      notifyDetail: {
+        logs: []
+      },
+    };
+  },
+  created() {
+    this.getList();
+    // 获得筛选项
+    getAppList().then(response => {
+      this.appList = response.data;
+    })
+  },
+  methods: {
+    /** 查询列表 */
+    getList() {
+      this.loading = true;
+      // 执行查询
+      getNotifyTaskPage(this.queryParams).then(response => {
+        this.list = response.data.list;
+        this.total = response.data.total;
+        this.loading = false;
+      });
+    },
+    /** 搜索按钮操作 */
+    handleQuery() {
+      this.queryParams.pageNo = 1;
+      this.getList();
+    },
+    /** 重置按钮操作 */
+    resetQuery() {
+      this.resetForm("queryForm");
+      this.handleQuery();
+    },
+    /** 详情按钮操作 */
+    handleDetail(row) {
+      this.notifyDetail = {};
+      getNotifyTaskDetail(row.id).then(response => {
+        // 设置值
+        this.notifyDetail = response.data;
+        // 弹窗打开
+        this.open = true;
+      });
+    },
+  }
+};
+</script>

+ 107 - 185
yudao-ui-admin/src/views/pay/order/index.vue

@@ -8,35 +8,26 @@
           <el-option v-for="item in appList" :key="item.id" :label="item.name" :value="item.id" />
         </el-select>
       </el-form-item>
-      <el-form-item label="渠道编码" prop="channelCode">
-        <el-select v-model="queryParams.channelCode" placeholder="请输入渠道编码" clearable
-                   @clear="()=>{queryParams.channelCode = null}">
-          <el-option v-for="dict in payChannelCodeDictDatum" :key="dict.value" :label="dict.label" :value="dict.value"/>
+      <el-form-item label="支付渠道" prop="channelCode">
+        <el-select v-model="queryParams.channelCode" placeholder="请选择支付渠道" clearable>
+          <el-option v-for="dict in this.getDictDatas(DICT_TYPE.PAY_CHANNEL_CODE)" :key="dict.value" :label="dict.label" :value="dict.value"/>
         </el-select>
       </el-form-item>
-      <el-form-item label="商户号" prop="merchantOrderId">
-        <el-input v-model="queryParams.merchantOrderId" placeholder="请输入商户号" clearable
+      <el-form-item label="商户单号" prop="merchantOrderId">
+        <el-input v-model="queryParams.merchantOrderId" placeholder="请输入商户单号" clearable
                   @keyup.enter.native="handleQuery"/>
       </el-form-item>
-      <el-form-item label="渠道订单号" prop="channelOrderNo">
-        <el-input v-model="queryParams.channelOrderNo" placeholder="请输入渠道订单号" clearable
+      <el-form-item label="支付单号" prop="no">
+        <el-input v-model="queryParams.no" placeholder="请输入支付单号" clearable
+                  @keyup.enter.native="handleQuery"/>
+      </el-form-item>
+      <el-form-item label="渠道单号" prop="channelOrderNo">
+        <el-input v-model="queryParams.channelOrderNo" placeholder="请输入渠道单号" clearable
                   @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 payOrderDictDatum" :key="parseInt(dict.value)"
-                     :label="dict.label" :value="parseInt(dict.value)"/>
-        </el-select>
-      </el-form-item>
-      <el-form-item label="退款状态" prop="refundStatus">
-        <el-select v-model="queryParams.refundStatus" placeholder="请选择退款状态" clearable>
-          <el-option v-for="dict in payOrderRefundDictDatum" :key="parseInt(dict.value)"
-                     :label="dict.label" :value="parseInt(dict.value)"/>
-        </el-select>
-      </el-form-item>
-      <el-form-item label="回调商户状态" prop="notifyStatus">
-        <el-select v-model="queryParams.notifyStatus" placeholder="请选择订单回调商户状态" clearable>
-          <el-option v-for="dict in payOrderNotifyDictDatum" :key="parseInt(dict.value)"
+          <el-option v-for="dict in this.getDictDatas(DICT_TYPE.PAY_ORDER_STATUS)" :key="parseInt(dict.value)"
                      :label="dict.label" :value="parseInt(dict.value)"/>
         </el-select>
       </el-form-item>
@@ -62,69 +53,64 @@
 
     <!-- 列表 -->
     <el-table v-loading="loading" :data="list">
-      <el-table-column label="订单编号" align="center" prop="id" width="80"/>
-      <el-table-column label="支付渠道" align="center" width="130">
+      <el-table-column label="编号" align="center" prop="id" width="80"/>
+      <el-table-column label="创建时间" align="center" prop="createTime" width="180">
         <template v-slot="scope">
-          <el-popover trigger="hover" placement="top">
-            <p>应用名称: {{ scope.row.appName }}</p>
-            <p>渠道名称: {{ scope.row.channelCodeName }}</p>
-            <div slot="reference" class="name-wrapper">
-              {{ scope.row.channelCodeName }}
-            </div>
-          </el-popover>
+          <span>{{ parseTime(scope.row.createTime) }}</span>
         </template>
       </el-table-column>
-      <el-table-column label="支付订单" align="left" width="280">
+      <el-table-column label="支付金额" align="center" prop="price" width="100">
         <template v-slot="scope">
-          <p class="order-font"><el-tag size="mini">商户单号</el-tag> {{scope.row.merchantOrderId}}</p>
-          <p class="order-font"><el-tag size="mini" type="warning">支付单号</el-tag> {{scope.row.channelOrderNo}}</p>
+          ¥{{ (scope.row.price / 100.0).toFixed(2) }}
         </template>
       </el-table-column>
-      <el-table-column label="商品标题" align="center" prop="subject" width="180" :show-overflow-tooltip="true"/>
-      <el-table-column label="支付金额" align="center" prop="price" width="100">
+      <el-table-column label="退款金额" align="center" prop="refundPrice" width="100">
         <template v-slot="scope">
-          ¥{{ parseFloat(scope.row.price / 100).toFixed(2) }}
+          ¥{{ (scope.row.refundPrice / 100.0).toFixed(2) }}
         </template>
       </el-table-column>
       <el-table-column label="手续金额" align="center" prop="channelFeePrice" width="100">
         <template v-slot="scope">
-          ¥{{ parseFloat(scope.row.channelFeePrice / 100).toFixed(2) }}
+          ¥{{ (scope.row.channelFeePrice / 100.0).toFixed(2) }}
         </template>
       </el-table-column>
-      <el-table-column label="退款金额" align="center" prop="refundPrice" width="100">
+      <el-table-column label="订单号" align="left" width="300">
         <template v-slot="scope">
-          ¥{{ parseFloat(scope.row.refundPrice / 100).toFixed(2) }}
+          <p class="order-font"><el-tag size="mini">
+            商户</el-tag> {{scope.row.merchantOrderId}}
+          </p>
+          <p class="order-font" v-if="scope.row.no">
+            <el-tag size="mini" type="warning">支付</el-tag> {{scope.row.no}}
+          </p>
+          <p class="order-font" v-if="scope.row.channelOrderNo">
+            <el-tag size="mini" type="success">渠道</el-tag> {{scope.row.channelOrderNo}}
+          </p>
         </template>
       </el-table-column>
       <el-table-column label="支付状态" align="center" prop="status">
         <template v-slot="scope">
           <dict-tag :type="DICT_TYPE.PAY_ORDER_STATUS" :value="scope.row.status" />
         </template>
-
       </el-table-column>
-<!--      <el-table-column label="退款状态" align="center" prop="refundStatus">-->
-<!--        <template v-slot="scope">-->
-<!--          <span>{{ getDictDataLabel(DICT_TYPE.PAY_ORDER_REFUND_STATUS, scope.row.refundStatus) }}</span>-->
-<!--        </template>-->
-<!--      </el-table-column>-->
-      <el-table-column label="回调状态" align="center" prop="notifyStatus" width="100">
+      <el-table-column label="支付渠道" align="center" width="140">
         <template v-slot="scope">
-          <dict-tag :type="DICT_TYPE.PAY_ORDER_NOTIFY_STATUS" :value="scope.row.notifyStatus" />
+          <dict-tag :type="DICT_TYPE.PAY_CHANNEL_CODE" :value="scope.row.channelCode" />
         </template>
       </el-table-column>
-      <el-table-column label="创建时间" align="center" prop="createTime" width="100">
+      <el-table-column label="支付时间" align="center" prop="successTime" width="180">
         <template v-slot="scope">
-          <span>{{ parseTime(scope.row.createTime) }}</span>
+          <span>{{ parseTime(scope.row.successTime) }}</span>
         </template>
       </el-table-column>
-      <el-table-column label="支付时间" align="center" prop="successTime" width="100">
+      <el-table-column label="支付应用" align="center" prop="successTime" width="100">
         <template v-slot="scope">
-          <span>{{ parseTime(scope.row.successTime) }}</span>
+          <span>{{ scope.row.appName }}</span>
         </template>
       </el-table-column>
+      <el-table-column label="商品标题" align="center" prop="subject" width="180" :show-overflow-tooltip="true"/>
       <el-table-column label="操作" align="center" fixed="right" class-name="small-padding fixed-width">
         <template v-slot="scope">
-          <el-button size="mini" type="text" icon="el-icon-search" @click="handleQueryDetails(scope.row)"
+          <el-button size="mini" type="text" icon="el-icon-search" @click="handleDetail(scope.row)"
                      v-hasPermi="['pay:order:query']">查看详情
           </el-button>
         </template>
@@ -134,67 +120,71 @@
     <pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNo" :limit.sync="queryParams.pageSize"
                 @pagination="getList"/>
 
-    <!-- 对话框(添加 / 修改) -->
-    <el-dialog title="订单详情" :visible.sync="open" width="50%">
-      <el-descriptions :column="2" label-class-name="desc-label">
-        <el-descriptions-item label="应用名称">{{ orderDetail.appName }}</el-descriptions-item>
-        <el-descriptions-item label="商品名称">{{ orderDetail.subject }}</el-descriptions-item>
-      </el-descriptions>
-      <el-divider></el-divider>
+    <!-- 对话框(详情) -->
+    <el-dialog title="订单详情" :visible.sync="open" width="700px" v-dialogDrag append-to-body>
       <el-descriptions :column="2" label-class-name="desc-label">
-        <el-descriptions-item label="商户单号">
+        <el-descriptions-item label="商户单号">
           <el-tag size="small">{{ orderDetail.merchantOrderId }}</el-tag>
         </el-descriptions-item>
-        <el-descriptions-item label="渠道订单号">
-          <el-tag class="tag-purple" size="small">{{ orderDetail.channelOrderNo }}</el-tag>
+        <el-descriptions-item label="支付单号">
+          <el-tag type="warning" size="small" v-if="orderDetail.no">{{ orderDetail.no }}</el-tag>
         </el-descriptions-item>
-        <el-descriptions-item label="支付订单号">
-          <el-tag v-if="orderDetail.payOrderExtension.no !== ''" class="tag-pink" size="small">
-            {{ orderDetail.payOrderExtension.no }}
-          </el-tag>
+      </el-descriptions>
+      <el-descriptions :column="2" label-class-name="desc-label">
+        <el-descriptions-item label="应用编号">{{ orderDetail.appId }}</el-descriptions-item>
+        <el-descriptions-item label="应用名称">{{ orderDetail.appName }}</el-descriptions-item>
+      </el-descriptions>
+      <el-descriptions :column="2" label-class-name="desc-label">
+        <el-descriptions-item label="支付状态">
+          <dict-tag :type="DICT_TYPE.PAY_ORDER_STATUS" :value="orderDetail.status" size="small" />
         </el-descriptions-item>
-        <el-descriptions-item label="金额">
-          <el-tag type="success" size="small">{{ parseFloat(orderDetail.price / 100, 2) }}</el-tag>
+        <el-descriptions-item label="支付金额">
+          <el-tag type="success" size="small">¥{{ (orderDetail.price / 100.0).toFixed(2) }}</el-tag>
         </el-descriptions-item>
+      </el-descriptions>
+      <el-descriptions :column="2" label-class-name="desc-label">
         <el-descriptions-item label="手续费">
-          <el-tag type="warning" size="small">{{ parseFloat(orderDetail.channelFeePrice / 100, 2) }}</el-tag>
-        </el-descriptions-item>
-        <el-descriptions-item label="手续费比例">
-          {{ parseFloat(orderDetail.channelFeeRate / 100, 2) }}%
-        </el-descriptions-item>
-        <el-descriptions-item label="支付状态">
-          <dict-tag :type="DICT_TYPE.PAY_ORDER_STATUS" :value="orderDetail.status" />
+          <el-tag type="warning" size="small">¥{{ (orderDetail.channelFeePrice / 100.0).toFixed(2) }}</el-tag>
         </el-descriptions-item>
-        <el-descriptions-item label="回调状态">
-          <dict-tag :type="DICT_TYPE.PAY_ORDER_NOTIFY_STATUS" :value="orderDetail.notifyStatus" />
-        </el-descriptions-item>
-        <el-descriptions-item label="回调地址">{{ orderDetail.notifyUrl }}</el-descriptions-item>
-        <el-descriptions-item label="创建时间">{{ parseTime(orderDetail.createTime) }}</el-descriptions-item>
+        <el-descriptions-item label="手续费比例">{{ (orderDetail.channelFeeRate / 100.0).toFixed(2) }}%</el-descriptions-item>
+      </el-descriptions>
+      <el-descriptions :column="2" label-class-name="desc-label">
         <el-descriptions-item label="支付时间">{{ parseTime(orderDetail.successTime) }}</el-descriptions-item>
         <el-descriptions-item label="失效时间">{{ parseTime(orderDetail.expireTime) }}</el-descriptions-item>
-        <el-descriptions-item label="通知时间">{{ parseTime(orderDetail.notifyTime) }}</el-descriptions-item>
       </el-descriptions>
-      <el-divider></el-divider>
       <el-descriptions :column="2" label-class-name="desc-label">
-        <el-descriptions-item label="支付渠道">{{ orderDetail.channelCodeName }}</el-descriptions-item>
-        <el-descriptions-item label="支付IP">{{ orderDetail.userIp }}</el-descriptions-item>
-        <el-descriptions-item label="退款状态">
-          <dict-tag :type="DICT_TYPE.PAY_ORDER_REFUND_STATUS" :value="orderDetail.refundStatus" />
+        <el-descriptions-item label="创建时间">{{ parseTime(orderDetail.createTime) }}</el-descriptions-item>
+        <el-descriptions-item label="更新时间">{{ parseTime(orderDetail.updateTime) }}</el-descriptions-item>
+      </el-descriptions>
+      <!-- 分割线 -->
+      <el-divider/>
+      <el-descriptions :column="2" label-class-name="desc-label">
+        <el-descriptions-item label="商品标题">{{ orderDetail.subject }}</el-descriptions-item>
+        <el-descriptions-item label="商品描述">{{ orderDetail.body }}</el-descriptions-item>
+      </el-descriptions>
+      <el-descriptions :column="2" label-class-name="desc-label">
+        <el-descriptions-item label="支付渠道">
+          <dict-tag :type="DICT_TYPE.PAY_CHANNEL_CODE" :value="orderDetail.channelCode" />
         </el-descriptions-item>
-        <el-descriptions-item label="退款次数">{{ orderDetail.refundTimes }}</el-descriptions-item>
+        <el-descriptions-item label="支付 IP">{{ orderDetail.userIp }}</el-descriptions-item>
+      </el-descriptions>
+      <el-descriptions :column="2" label-class-name="desc-label">
+        <el-descriptions-item label="渠道单号">
+          <el-tag size="mini" type="success" v-if="orderDetail.channelOrderNo">{{ orderDetail.channelOrderNo }}</el-tag>
+        </el-descriptions-item>
+        <el-descriptions-item label="渠道用户">{{ orderDetail.channelUserId }}</el-descriptions-item>
+      </el-descriptions>
+      <el-descriptions :column="2" label-class-name="desc-label">
         <el-descriptions-item label="退款金额">
-          <el-tag type="warning">
-            {{ parseFloat(orderDetail.refundPrice / 100, 2) }}
-          </el-tag>
+          <el-tag size="mini" type="danger">¥{{ (orderDetail.refundPrice / 100.0).toFixed(2) }}</el-tag>
         </el-descriptions-item>
+        <el-descriptions-item label="通知 URL">{{ orderDetail.notifyUrl }}</el-descriptions-item>
       </el-descriptions>
-      <el-divider></el-divider>
+      <!-- 分割线 -->
+      <el-divider />
       <el-descriptions :column="1" label-class-name="desc-label" direction="vertical" border>
-        <el-descriptions-item label="商品描述">
-          {{ orderDetail.body }}
-        </el-descriptions-item>
         <el-descriptions-item label="支付通道异步回调内容">
-          {{ orderDetail.payOrderExtension.channelNotifyData }}
+          {{ orderDetail.extension.channelNotifyData }}
         </el-descriptions-item>
       </el-descriptions>
     </el-dialog>
@@ -203,35 +193,7 @@
 
 <script>
 import { getOrderDetail, getOrderPage, exportOrderExcel } from "@/api/pay/order";
-import { DICT_TYPE, getDictDatas } from "@/utils/dict";
-import { getNowDateTime} from "@/utils/ruoyi";
-
-const defaultOrderDetail = {
-  appName: '',
-  channelCodeName: '',
-  subject: '',
-  merchantOrderId: null,
-  channelOrderNo: '',
-  body: '',
-  price: null,
-  channelFeeRate: null,
-  channelFeePrice: null,
-  userIp: '',
-  status: null,
-  notifyUrl: '',
-  notifyStatus: null,
-  refundStatus: null,
-  refundTimes: '',
-  refundPrice: null,
-  createTime: '',
-  successTime: '',
-  notifyTime: '',
-  expireTime: '',
-  payOrderExtension: {
-    channelNotifyData: '',
-    no: ''
-  }
-};
+import { getAppList } from "@/api/pay/app";
 
 export default {
   name: "PayOrder",
@@ -250,75 +212,37 @@ export default {
       title: "",
       // 是否显示弹出层
       open: false,
-      // 查询参数 TODO 芋艿:多余字段的清理
+      // 查询参数
       queryParams: {
         pageNo: 1,
         pageSize: 10,
         appId: null,
-        channelId: null,
         channelCode: null,
         merchantOrderId: null,
-        subject: null,
-        body: null,
-        notifyUrl: null,
-        notifyStatus: null,
-        price: null,
-        channelFeeRate: null,
-        channelFeePrice: null,
-        status: null,
-        userIp: null,
-        successExtensionId: null,
-        refundStatus: null,
-        refundTimes: null,
-        refundPrice: null,
-        channelUserId: null,
         channelOrderNo: null,
-        expireTime: [],
-        successTime: [],
-        notifyTime: [],
-        createTime: []
+        no: null,
+        status: null,
+        createTime: [],
       },
-      // 商户加载遮罩层
-      merchantLoading: false,
-      // 商户列表集合
-      merchantList: null,
+
       // 支付应用列表集合
-      appList: null,
-      // 支付渠道编码字典数据集合
-      payChannelCodeDictDatum: getDictDatas(DICT_TYPE.PAY_CHANNEL_CODE),
-      // 订单回调商户状态字典数据集合
-      payOrderNotifyDictDatum: getDictDatas(DICT_TYPE.PAY_ORDER_NOTIFY_STATUS),
-      // 订单状态字典数据集合
-      payOrderDictDatum: getDictDatas(DICT_TYPE.PAY_ORDER_STATUS),
-      // 订单退款状态字典数据集合
-      payOrderRefundDictDatum: getDictDatas(DICT_TYPE.PAY_ORDER_REFUND_STATUS),
-      orderDetail: JSON.parse(JSON.stringify(defaultOrderDetail)),
+      appList: [],
+      // 订单详情
+      orderDetail: {
+        extension: {}
+      },
     };
   },
   created() {
-    // 初始化默认时间
-    this.initTime();
     this.getList();
-    this.handleGetMerchantListByName(null);
+    // 获得筛选项
+    getAppList().then(response => {
+      this.appList = response.data;
+    })
   },
   methods: {
-    initTime(){
-      this.queryParams.createTime = [getNowDateTime("00:00:00"), getNowDateTime("23:59:59")];
-    },
     /** 查询列表 */
     getList() {
-      // 判断选择的日期是否超过了一个月
-      let oneMonthTime = 31 * 24 * 3600 * 1000;
-      if (this.queryParams.createTime == null){
-        this.initTime();
-      } else {
-        let minDateTime = new Date(this.queryParams.createTime[0]).getTime();
-        let maxDateTime = new Date(this.queryParams.createTime[1]).getTime()
-        if (maxDateTime - minDateTime > oneMonthTime) {
-          this.$message.error('时间范围最大为 31 天!');
-          return false;
-        }
-      }
       this.loading = true;
       // 执行查询
       getOrderPage(this.queryParams).then(response => {
@@ -339,22 +263,20 @@ export default {
     /** 重置按钮操作 */
     resetQuery() {
       this.resetForm("queryForm");
-      this.initTime();
       this.handleQuery();
     },
-    /**
-     * 查看订单详情
-     */
-    handleQueryDetails(row) {
-      this.orderDetail = JSON.parse(JSON.stringify(defaultOrderDetail));
+    /** 详情按钮操作 */
+    handleDetail(row) {
+      this.orderDetail = {};
       getOrderDetail(row.id).then(response => {
+        // 设置值
         this.orderDetail = response.data;
-        if (response.data.payOrderExtension === null) {
-          this.orderDetail.payOrderExtension = Object.assign(defaultOrderDetail.payOrderExtension, {});
+        if (!this.orderDetail.extension) {
+          this.orderDetail.extension = {}
         }
+        // 弹窗打开
         this.open = true;
       });
-
     },
     /** 导出按钮操作 */
     handleExport() {

+ 97 - 234
yudao-ui-admin/src/views/pay/refund/index.vue

@@ -7,31 +7,30 @@
           <el-option v-for="item in appList" :key="item.id" :label="item.name" :value="item.id" />
         </el-select>
       </el-form-item>
-      <el-form-item label="渠道编码" prop="channelCode">
-        <el-select v-model="queryParams.channelCode" placeholder="请输入渠道编码" clearable
-                   @clear="()=>{queryParams.channelCode = null}">
-          <el-option v-for="dict in payChannelCodeDictDatum" :key="dict.value" :label="dict.label" :value="dict.value"/>
+      <el-form-item label="退款渠道" prop="channelCode">
+        <el-select v-model="queryParams.channelCode" placeholder="请选择退款渠道" clearable>
+          <el-option v-for="dict in this.getDictDatas(DICT_TYPE.PAY_CHANNEL_CODE)" :key="dict.value" :label="dict.label" :value="dict.value"/>
         </el-select>
       </el-form-item>
-      <el-form-item label="退款类型" prop="type">
-        <el-select v-model="queryParams.type" placeholder="请选择退款类型" clearable>
-          <el-option v-for="dict in payRefundOrderTypeDictDatum" :key="parseInt(dict.value)"
-                     :label="dict.label" :value="parseInt(dict.value)"/>
-        </el-select>
+      <el-form-item label="商户支付单号" prop="merchantOrderId">
+        <el-input v-model="queryParams.merchantOrderId" placeholder="请输入商户支付单号" clearable
+                  @keyup.enter.native="handleQuery"/>
+      </el-form-item>
+      <el-form-item label="商户退款单号" prop="merchantRefundId">
+        <el-input v-model="queryParams.merchantRefundId" placeholder="请输入商户退款单号" clearable
+                  @keyup.enter.native="handleQuery"/>
+      </el-form-item>
+      <el-form-item label="渠道支付单号" prop="channelOrderNo">
+        <el-input v-model="queryParams.channelOrderNo" placeholder="请输入渠道支付单号" clearable
+                  @keyup.enter.native="handleQuery"/>
       </el-form-item>
-      <el-form-item label="商户退款订单号" prop="merchantRefundNo">
-        <el-input v-model="queryParams.merchantRefundNo" placeholder="请输入商户退款订单号" clearable
+      <el-form-item label="渠道退款单号" prop="channelRefundNo">
+        <el-input v-model="queryParams.channelRefundNo" placeholder="请输入渠道退款单号" clearable
                   @keyup.enter.native="handleQuery"/>
       </el-form-item>
       <el-form-item label="退款状态" prop="status">
         <el-select v-model="queryParams.status" placeholder="请选择退款状态" clearable>
-          <el-option v-for="dict in payRefundOrderDictDatum" :key="parseInt(dict.value)"
-                     :label="dict.label" :value="parseInt(dict.value)"/>
-        </el-select>
-      </el-form-item>
-      <el-form-item label="退款回调状态" prop="notifyStatus">
-        <el-select v-model="queryParams.notifyStatus" placeholder="请选择通知商户退款结果的回调状态" clearable>
-          <el-option v-for="dict in payOrderNotifyDictDatum" :key="parseInt(dict.value)"
+          <el-option v-for="dict in this.getDictDatas(DICT_TYPE.PAY_REFUND_STATUS)" :key="parseInt(dict.value)"
                      :label="dict.label" :value="parseInt(dict.value)"/>
         </el-select>
       </el-form-item>
@@ -58,77 +57,62 @@
     <!-- 列表 -->
     <el-table v-loading="loading" :data="list">
       <el-table-column label="编号" align="center" prop="id"/>
-      <el-table-column label="支付渠道" align="center" width="130">
+      <el-table-column label="创建时间" align="center" prop="createTime" width="180">
         <template v-slot="scope">
-          <el-popover trigger="hover" placement="top">
-            <p>应用名称: {{ scope.row.appName }}</p>
-            <p>渠道名称: {{ scope.row.channelCodeName }}</p>
-            <div slot="reference" class="name-wrapper">
-              {{ scope.row.channelCodeName }}
-            </div>
-          </el-popover>
+          <span>{{ parseTime(scope.row.createTime) }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="支付金额" align="center" prop="payPrice" width="100">
+        <template v-slot="scope" class="">
+          ¥{{ (scope.row.payPrice / 100.0).toFixed(2) }}
         </template>
       </el-table-column>
-      <!--      <el-table-column label="交易订单号" align="center" prop="tradeNo" width="140"/>-->
-      <!--      <el-table-column label="商户订单编号" align="center" prop="merchantOrderId" width="140"/>-->
-      <el-table-column label="商户订单号" align="left" width="230">
+      <el-table-column label="退款金额" align="center" prop="refundPrice" width="100">
+        <template v-slot="scope">
+          ¥{{ (scope.row.refundPrice / 100.0).toFixed(2) }}
+        </template>
+      </el-table-column>
+      <el-table-column label="退款订单号" align="left" width="300">
         <template v-slot="scope">
           <p class="order-font">
-            <el-tag size="mini">退款</el-tag>
-            {{ scope.row.merchantRefundNo }}
+            <el-tag size="mini">商户</el-tag> {{scope.row.merchantRefundId}}
           </p>
           <p class="order-font">
-            <el-tag type="success">交易</el-tag>
-            {{ scope.row.merchantOrderId }}
+            <el-tag size="mini" type="warning">退款</el-tag> {{scope.row.no}}
+          </p>
+          <p class="order-font" v-if="scope.row.channelRefundNo">
+            <el-tag size="mini" type="success">渠道</el-tag> {{scope.row.channelRefundNo}}
           </p>
         </template>
       </el-table-column>
-      <el-table-column label="支付订单号" align="center" prop="merchantRefundNo" width="250">
+      <el-table-column label="支付订单号" align="left" width="300">
         <template v-slot="scope">
           <p class="order-font">
-            <el-tag size="mini">交易</el-tag>
-            {{ scope.row.tradeNo }}
+            <el-tag size="mini">商户</el-tag> {{scope.row.merchantOrderId}}
           </p>
           <p class="order-font">
-            <el-tag size="mini" type="warning">渠道</el-tag>
-            {{ scope.row.channelOrderNo }}
+            <el-tag size="mini" type="success">渠道</el-tag> {{scope.row.channelOrderNo}}
           </p>
         </template>
       </el-table-column>
-      <el-table-column label="支付金额(元)" align="center" prop="payPrice" width="100">
-        <template v-slot="scope" class="">
-          ¥{{ parseFloat(scope.row.payPrice / 100).toFixed(2) }}
-        </template>
-      </el-table-column>
-      <el-table-column label="退款金额(元)" align="center" prop="refundPrice" width="100">
-        <template v-slot="scope">
-          ¥{{ parseFloat(scope.row.refundPrice / 100).toFixed(2) }}
-        </template>
-      </el-table-column>
-      <el-table-column label="退款类型" align="center" prop="type" width="80">
-        <template v-slot="scope">
-          <dict-tag :type="DICT_TYPE.PAY_REFUND_ORDER_TYPE" :value="scope.row.type" />
-        </template>
-      </el-table-column>
       <el-table-column label="退款状态" align="center" prop="status">
         <template v-slot="scope">
-          <dict-tag :type="DICT_TYPE.PAY_REFUND_ORDER_STATUS" :value="scope.row.status" />
+          <dict-tag :type="DICT_TYPE.PAY_REFUND_STATUS" :value="scope.row.status" />
         </template>
       </el-table-column>
-      <el-table-column label="回调状态" align="center" prop="notifyStatus">
+      <el-table-column label="退款渠道" align="center" width="140">
         <template v-slot="scope">
-          <dict-tag :type="DICT_TYPE.PAY_ORDER_NOTIFY_STATUS" :value="scope.row.notifyStatus" />
+          <dict-tag :type="DICT_TYPE.PAY_CHANNEL_CODE" :value="scope.row.channelCode" />
         </template>
       </el-table-column>
-      <el-table-column label="退款原因" align="center" prop="reason" width="140" :show-overflow-tooltip="true"/>
-      <el-table-column label="创建时间" align="center" prop="createTime" width="100">
+      <el-table-column label="成功时间" align="center" prop="successTime" width="180">
         <template v-slot="scope">
-          <span>{{ parseTime(scope.row.createTime) }}</span>
+          <span>{{ parseTime(scope.row.successTime) }}</span>
         </template>
       </el-table-column>
-      <el-table-column label="退款成功时间" align="center" prop="successTime" width="100">
+      <el-table-column label="支付应用" align="center" prop="successTime" width="100">
         <template v-slot="scope">
-          <span>{{ parseTime(scope.row.successTime) }}</span>
+          <span>{{ scope.row.appName }}</span>
         </template>
       </el-table-column>
       <el-table-column label="操作" align="center" fixed="right" class-name="small-padding fixed-width">
@@ -143,107 +127,76 @@
     <pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNo" :limit.sync="queryParams.pageSize"
                 @pagination="getList"/>
 
-    <!-- 对话框(添加 / 修改) -->
-    <el-dialog title="退款订单详情" :visible.sync="open" width="700px" append-to-body>
+    <!-- 对话框(详情) -->
+    <el-dialog title="退款订单详情" :visible.sync="open" width="700px" v-dialogDrag append-to-body>
       <el-descriptions :column="2" label-class-name="desc-label">
-        <el-descriptions-item label="应用名称">{{ refundDetail.appName }}</el-descriptions-item>
-        <el-descriptions-item label="商品名称">{{ refundDetail.subject }}</el-descriptions-item>
+        <el-descriptions-item label="商户退款单号">
+          <el-tag size="small">{{ refundDetail.merchantRefundId }}</el-tag>
+        </el-descriptions-item>
+        <el-descriptions-item label="渠道退款单号">
+          <el-tag type="success" size="small" v-if="refundDetail.channelRefundNo">{{ refundDetail.channelRefundNo }}</el-tag>
+        </el-descriptions-item>
       </el-descriptions>
-      <el-divider></el-divider>
       <el-descriptions :column="2" label-class-name="desc-label">
-        <el-descriptions-item label="商户退款单号">
-          <el-tag  size="mini">{{ refundDetail.merchantRefundNo }}</el-tag>
+        <el-descriptions-item label="商户支付单号">
+          <el-tag size="small">{{ refundDetail.merchantOrderId }}</el-tag>
         </el-descriptions-item>
-        <el-descriptions-item label="商户订单号">{{ refundDetail.merchantOrderId }}</el-descriptions-item>
-        <el-descriptions-item label="交易订单号">{{ refundDetail.tradeNo }}</el-descriptions-item>
+        <el-descriptions-item label="渠道支付单号">
+          <el-tag type="success" size="small">{{ refundDetail.channelOrderNo }}</el-tag>
+        </el-descriptions-item>
+      </el-descriptions>
+      <el-descriptions :column="2" label-class-name="desc-label">
+        <el-descriptions-item label="应用编号">{{ refundDetail.appId }}</el-descriptions-item>
+        <el-descriptions-item label="应用名称">{{ refundDetail.appName }}</el-descriptions-item>
       </el-descriptions>
-      <el-divider></el-divider>
       <el-descriptions :column="2" label-class-name="desc-label">
         <el-descriptions-item label="支付金额">
-          {{ parseFloat(refundDetail.payPrice / 100).toFixed(2) }}
+          <el-tag type="success" size="small">¥{{ (refundDetail.payPrice / 100.0).toFixed(2) }}</el-tag>
         </el-descriptions-item>
-        <el-descriptions-item label="退款金额" size="mini">
-          <el-tag class="tag-purple" size="mini">{{ parseFloat(refundDetail.refundPrice / 100).toFixed(2) }}</el-tag>
-        </el-descriptions-item>
-        <el-descriptions-item label="退款类型">
-          <template v-slot="scope">
-            <dict-tag :type="DICT_TYPE.PAY_REFUND_ORDER_TYPE" :value="refundDetail.type" />
-          </template>
+        <el-descriptions-item label="退款金额">
+          <el-tag size="mini" type="danger">¥{{ (refundDetail.refundPrice / 100.0).toFixed(2) }}</el-tag>
         </el-descriptions-item>
+      </el-descriptions>
+      <el-descriptions :column="2" label-class-name="desc-label">
         <el-descriptions-item label="退款状态">
-          <dict-tag :type="DICT_TYPE.PAY_REFUND_ORDER_STATUS" :value="refundDetail.status" />
+          <dict-tag :type="DICT_TYPE.PAY_REFUND_STATUS" :value="refundDetail.status" />
         </el-descriptions-item>
+        <el-descriptions-item label="退款时间">{{ parseTime(refundDetail.successTime) }}</el-descriptions-item>
+      </el-descriptions>
+      <el-descriptions :column="2" label-class-name="desc-label">
         <el-descriptions-item label="创建时间">{{ parseTime(refundDetail.createTime) }}</el-descriptions-item>
-        <el-descriptions-item label="退款成功时间">{{ parseTime(refundDetail.successTime) }}</el-descriptions-item>
-        <el-descriptions-item label="退款失效时间">{{ parseTime(refundDetail.expireTime) }}</el-descriptions-item>
         <el-descriptions-item label="更新时间">{{ parseTime(refundDetail.updateTime) }}</el-descriptions-item>
       </el-descriptions>
-      <el-divider></el-divider>
+      <!-- 分割线 -->
+      <el-divider />
       <el-descriptions :column="2" label-class-name="desc-label">
-        <el-descriptions-item label="支付渠道">
-          {{ refundDetail.channelCodeName }}
-        </el-descriptions-item>
-        <el-descriptions-item label="支付IP" size="mini">
-          {{refundDetail.userIp}}
-        </el-descriptions-item>
-        <el-descriptions-item label="回调地址">{{ refundDetail.notifyUrl }}</el-descriptions-item>
-        <el-descriptions-item label="回调状态">
-          <dict-tag :type="DICT_TYPE.PAY_ORDER_NOTIFY_STATUS" :value="refundDetail.notifyStatus" />
+        <el-descriptions-item label="退款渠道">
+          <dict-tag :type="DICT_TYPE.PAY_CHANNEL_CODE" :value="refundDetail.channelCode" />
         </el-descriptions-item>
-        <el-descriptions-item label="回调时间">{{ parseTime(refundDetail.notifyTime) }}</el-descriptions-item>
+        <el-descriptions-item label="退款原因">{{ refundDetail.reason }}</el-descriptions-item>
+      </el-descriptions>
+      <el-descriptions :column="2" label-class-name="desc-label">
+        <el-descriptions-item label="退款 IP">{{ refundDetail.userIp }}</el-descriptions-item>
+        <el-descriptions-item label="通知 URL">{{ refundDetail.notifyUrl }}</el-descriptions-item>
       </el-descriptions>
-      <el-divider></el-divider>
+      <!-- 分割线 -->
+      <el-divider />
       <el-descriptions :column="2" label-class-name="desc-label">
-        <el-descriptions-item label="渠道订单号">{{ refundDetail.channelOrderNo }}</el-descriptions-item>
-        <el-descriptions-item label="渠道退款单号">{{ refundDetail.channelRefundNo }}</el-descriptions-item>
         <el-descriptions-item label="渠道错误码">{{refundDetail.channelErrorCode}}</el-descriptions-item>
         <el-descriptions-item label="渠道错误码描述">{{refundDetail.channelErrorMsg}}</el-descriptions-item>
       </el-descriptions>
-      <br>
       <el-descriptions :column="1" label-class-name="desc-label" direction="vertical" border>
-        <el-descriptions-item label="渠道额外参数">{{ refundDetail.channelExtras }}</el-descriptions-item>
-        <el-descriptions-item label="退款原因">{{ refundDetail.reason }}</el-descriptions-item>
+        <el-descriptions-item label="支付通道异步回调内容">
+          {{ refundDetail.channelNotifyData }}
+        </el-descriptions-item>
       </el-descriptions>
     </el-dialog>
   </div>
 </template>
 
 <script>
-import {getRefundPage, exportRefundExcel, getRefund} from "@/api/pay/refund";
-import { DICT_TYPE, getDictDatas } from "@/utils/dict";
-import { PayOrderRefundStatusEnum, PayRefundStatusEnum } from "@/utils/constants";
-import { getNowDateTime } from "@/utils/ruoyi";
-
-const defaultRefundDetail = {
-  id: null,
-  appId: null,
-  appName: '',
-  channelCode: '',
-  channelCodeName: '',
-  channelErrorCode: '',
-  channelErrorMsg: '',
-  channelExtras: '',
-  channelId: null,
-  channelOrderNo: '',
-  channelRefundNo: '',
-  createTime: null,
-  expireTime: null,
-  merchantOrderId: '',
-  merchantRefundNo: '',
-  notifyStatus: null,
-  notifyTime: null,
-  notifyUrl: '',
-  orderId: null,
-  payPrice: null,
-  reason: '',
-  refundPrice: null,
-  status: null,
-  subject: '',
-  successTime: null,
-  tradeNo: '',
-  type: null,
-  userIp: ''
-}
+import { getRefundPage, exportRefundExcel, getRefund } from "@/api/pay/refund";
+import { getAppList } from "@/api/pay/app";
 
 export default {
   name: "PayRefund",
@@ -267,73 +220,30 @@ export default {
         pageNo: 1,
         pageSize: 10,
         appId: null,
-        channelId: null,
         channelCode: null,
-        orderId: null,
-        tradeNo: null,
         merchantOrderId: null,
-        merchantRefundNo: null,
-        notifyUrl: null,
-        notifyStatus: null,
-        status: null,
-        type: null,
-        payPrice: null,
-        refundPrice: null,
-        reason: null,
-        userIp: null,
+        merchantRefundId: null,
         channelOrderNo: null,
         channelRefundNo: null,
-        channelErrorCode: null,
-        channelErrorMsg: null,
-        channelExtras: null,
-        expireTime: [],
-        successTime: [],
-        notifyTime: [],
+        status: null,
         createTime: []
       },
-      // 商户加载遮罩层
-      merchantLoading: false,
-      // 商户列表集合
-      merchantList: null,
       // 支付应用列表集合
-      appList: null,
-      // 支付渠道编码字典数据集合
-      payChannelCodeDictDatum: getDictDatas(DICT_TYPE.PAY_CHANNEL_CODE),
-      // 订单退款状态字典数据集合
-      payRefundOrderDictDatum: getDictDatas(DICT_TYPE.PAY_REFUND_ORDER_STATUS),
-      // 退款订单类别字典数据集合
-      payRefundOrderTypeDictDatum: getDictDatas(DICT_TYPE.PAY_REFUND_ORDER_TYPE),
-      // 订单回调商户状态字典数据集合
-      payOrderNotifyDictDatum: getDictDatas(DICT_TYPE.PAY_ORDER_NOTIFY_STATUS),
-      // el-tag订单退款状态type值
-      refundStatusType: '',
+      appList: [],
       // 退款订单详情
-      refundDetail: JSON.parse(JSON.stringify(defaultRefundDetail)),
+      refundDetail: {},
     };
   },
   created() {
-    this.initTime();
     this.getList();
-    this.handleGetMerchantListByName(null);
+    // 获得筛选项
+    getAppList().then(response => {
+      this.appList = response.data;
+    })
   },
   methods: {
-    initTime(){
-      this.queryParams.createTime = [getNowDateTime("00:00:00"), getNowDateTime("23:59:59")];
-    },
     /** 查询列表 */
     getList() {
-      // 判断选择的日期是否超过了一个月
-      let oneMonthTime = 31 * 24 * 3600 * 1000;
-      if (this.queryParams.createTime == null){
-        this.initTime();
-      } else {
-        let minDateTime = new Date(this.queryParams.createTime[0]).getTime();
-        let maxDateTime = new Date(this.queryParams.createTime[1]).getTime()
-        if (maxDateTime - minDateTime > oneMonthTime) {
-          this.$message.error('时间范围最大为 31 天!');
-          return false;
-        }
-      }
       this.loading = true;
       // 执行查询
       getRefundPage(this.queryParams).then(response => {
@@ -369,60 +279,13 @@ export default {
         this.$download.excel(response, '退款订单.xls');
       }).catch(() => {});
     },
-    /**
-     * 根据商户名称模糊匹配商户信息
-     * @param name 商户名称
-     */
-    handleGetMerchantListByName(name) {
-      getMerchantListByName(name).then(response => {
-        this.merchantList = response.data;
-        this.merchantLoading = false;
-      });
-    },
-    /**
-     * 根据退款类别得到样式名称
-     * @param refundType 退款类别
-     */
-    findByRefundTypeGetStyle(refundType) {
-      switch (refundType) {
-        case PayOrderRefundStatusEnum.NO.status:
-          return "success";
-        case PayOrderRefundStatusEnum.SOME.status:
-          return "warning";
-        case PayOrderRefundStatusEnum.ALL.status:
-          return "danger";
-      }
-    },
-    /**
-     * 根据退款状态得到样式名称
-     * @param refundStatus 退款状态
-     */
-    findByRefundStatusGetStyle(refundStatus) {
-      switch (refundStatus) {
-        case PayRefundStatusEnum.CREATE.status:
-          return "info";
-        case PayRefundStatusEnum.SUCCESS.status:
-          return "success";
-        case PayRefundStatusEnum.FAILURE.status:
-        case PayRefundStatusEnum.CLOSE.status:
-          return "danger";
-        case PayRefundStatusEnum.PROCESSING_NOTIFY.status:
-        case PayRefundStatusEnum.PROCESSING_QUERY.status:
-        case PayRefundStatusEnum.UNKNOWN_RETRY.status:
-        case PayRefundStatusEnum.UNKNOWN_QUERY.status:
-          return "warning";
-      }
-    },
-    /**
-     * 查看订单详情
-     */
+    /** 详情按钮操作 */
     handleQueryDetails(row) {
-      this.refundDetail = JSON.parse(JSON.stringify(defaultRefundDetail));
+      this.refundDetail = {};
       getRefund(row.id).then(response => {
         this.refundDetail = response.data;
         this.open = true;
       });
-
     },
   }
 };