Ver Fonte

mall + pay:
1. 增加通知管理

YunaiV há 1 ano atrás
pai
commit
654b70c514
16 ficheiros alterados com 584 adições e 27 exclusões
  1. 49 2
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/notify/PayNotifyController.java
  2. 45 0
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/notify/vo/PayNotifyTaskBaseVO.java
  3. 54 0
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/notify/vo/PayNotifyTaskDetailRespVO.java
  4. 39 0
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/notify/vo/PayNotifyTaskPageReqVO.java
  5. 22 0
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/notify/vo/PayNotifyTaskRespVO.java
  6. 43 0
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/convert/notify/PayNotifyTaskConvert.java
  7. 1 1
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/notify/PayNotifyTaskDO.java
  8. 7 0
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/notify/PayNotifyLogMapper.java
  9. 20 6
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/notify/PayNotifyTaskMapper.java
  10. 32 3
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/notify/PayNotifyService.java
  11. 28 11
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/notify/PayNotifyServiceImpl.java
  12. 18 0
      yudao-ui-admin/src/api/pay/notify.js
  13. 2 1
      yudao-ui-admin/src/utils/dict.js
  14. 221 0
      yudao-ui-admin/src/views/pay/notify/index.vue
  15. 1 1
      yudao-ui-admin/src/views/pay/order/index.vue
  16. 2 2
      yudao-ui-admin/src/views/pay/refund/index.vue

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

@@ -1,25 +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.CHANNEL_NOT_FOUND;
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+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
@@ -30,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;
@@ -76,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;
+
+}

+ 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);
+}

+ 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 芋道源码

+ 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));
     }
 
 }

+ 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);
+    }
+
 }

+ 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
+  })
+}

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

@@ -56,9 +56,10 @@ export const DICT_TYPE = {
   PAY_CHANNEL_ALIPAY_SERVER_TYPE: 'pay_channel_alipay_server_type', // 支付宝网关地址
 
   PAY_CHANNEL_CODE: 'pay_channel_code', // 支付渠道编码类型
-  PAY_NOTIFY_STATUS: 'pay_notify_status', // 商户支付回调状态
   PAY_ORDER_STATUS: 'pay_order_status', // 商户支付订单状态
   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', // 自动回复请求匹配类型

+ 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>

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

@@ -121,7 +121,7 @@
                 @pagination="getList"/>
 
     <!-- 对话框(详情) -->
-    <el-dialog title="订单详情" :visible.sync="open" width="50%">
+    <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">{{ orderDetail.merchantOrderId }}</el-tag>

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

@@ -128,7 +128,7 @@
                 @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="商户退款单号">
           <el-tag size="small">{{ refundDetail.merchantRefundId }}</el-tag>
@@ -180,7 +180,7 @@
         <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.channelErrorCode}}</el-descriptions-item>
         <el-descriptions-item label="渠道错误码描述">{{refundDetail.channelErrorMsg}}</el-descriptions-item>