Commit dd2d8a2ba90115c03f4b79e223243cee3d77ee1d

Authored by YunaiV
1 parent 72b64dc5

优化支付通知的逻辑,解决并发的问题。

同时,收到支付结果时,立马回调业务,避免延迟
Showing 16 changed files with 363 additions and 10 deletions
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/job/notify/PayNotifyJob.java 0 → 100644
  1 +package cn.iocoder.yudao.adminserver.modules.pay.job.notify;
  2 +
  3 +import cn.iocoder.yudao.coreservice.modules.pay.service.notify.PayNotifyCoreService;
  4 +import cn.iocoder.yudao.framework.quartz.core.handler.JobHandler;
  5 +import lombok.extern.slf4j.Slf4j;
  6 +import org.springframework.stereotype.Component;
  7 +
  8 +import javax.annotation.Resource;
  9 +
  10 +/**
  11 + * 支付通知 Job
  12 + * 通过不断扫描待通知的 PayNotifyTaskDO 记录,回调业务线的回调接口
  13 + *
  14 + * @author 芋道源码
  15 + */
  16 +@Component
  17 +@Slf4j
  18 +public class PayNotifyJob implements JobHandler {
  19 +
  20 + @Resource
  21 + private PayNotifyCoreService payNotifyCoreService;
  22 +
  23 + @Override
  24 + public String execute(String param) throws Exception {
  25 + int notifyCount = payNotifyCoreService.executeNotify();
  26 + return String.format("执行支付通知 %s 个", notifyCount);
  27 + }
  28 +
  29 +}
... ...
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/job/package-info.java 0 → 100644
  1 +package cn.iocoder.yudao.adminserver.modules.pay.job;
... ...
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/system/enums/SysErrorCodeConstants.java
1 1 package cn.iocoder.yudao.adminserver.modules.system.enums;
2 2  
3 3 import cn.iocoder.yudao.framework.common.exception.ErrorCode;
4   -import javafx.beans.binding.MapExpression;
5 4  
6 5 /**
7 6 * System 错误码枚举类
... ...
yudao-admin-server/src/main/resources/application-local.yaml
... ... @@ -166,6 +166,9 @@ yudao:
166 166 exclude-urls: # 如下两个 url,仅仅是为了演示,去掉配置也没关系
167 167 - ${spring.boot.admin.context-path}/** # 不处理 Spring Boot Admin 的请求
168 168 - ${management.endpoints.web.base-path}/** # 不处理 Actuator 的请求
  169 + pay:
  170 + pay-notify-url: http://niubi.natapp1.cc/api/pay/order/notify
  171 + refund-notify-url: http://niubi.natapp1.cc/api/pay/refund/notify
169 172 demo: false # 关闭演示模式
170 173  
171 174 justauth:
... ...
yudao-core-service/pom.xml
... ... @@ -77,6 +77,12 @@
77 77 <artifactId>yudao-spring-boot-starter-mq</artifactId>
78 78 </dependency>
79 79  
  80 + <!-- 服务保障相关 -->
  81 + <dependency>
  82 + <groupId>cn.iocoder.boot</groupId>
  83 + <artifactId>yudao-spring-boot-starter-protection</artifactId>
  84 + </dependency>
  85 +
80 86 <!-- Test 测试相关 -->
81 87 <dependency>
82 88 <groupId>cn.iocoder.boot</groupId>
... ...
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/dal/dataobject/notify/PayNotifyLogDO.java
... ... @@ -34,15 +34,11 @@ public class PayNotifyLogDO extends BaseDO {
34 34 */
35 35 private Integer notifyTimes;
36 36 /**
37   - * 请求参数
38   - */
39   - private String request;
40   - /**
41   - * 响应结果
  37 + * HTTP 响应结果
42 38 */
43 39 private String response;
44 40 /**
45   - * 状态
  41 + * 支付通知状态
46 42 *
47 43 * 外键 {@link PayNotifyStatusEnum}
48 44 */
... ...
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/dal/mysql/notify/PayNotifyTaskCoreMapper.java
1 1 package cn.iocoder.yudao.coreservice.modules.pay.dal.mysql.notify;
2 2  
3   -import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayAppDO;
4 3 import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.notify.PayNotifyTaskDO;
  4 +import cn.iocoder.yudao.coreservice.modules.pay.enums.notify.PayNotifyStatusEnum;
5 5 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
  6 +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
6 7 import org.apache.ibatis.annotations.Mapper;
7 8  
  9 +import java.util.Date;
  10 +import java.util.List;
  11 +
8 12 @Mapper
9 13 public interface PayNotifyTaskCoreMapper extends BaseMapperX<PayNotifyTaskDO> {
  14 +
  15 + /**
  16 + * 获得需要通知的 PayNotifyTaskDO 记录。需要满足如下条件:
  17 + *
  18 + * 1. status 非成功
  19 + * 2. nextNotifyTime 小于当前时间
  20 + *
  21 + * @return PayTransactionNotifyTaskDO 数组
  22 + */
  23 + default List<PayNotifyTaskDO> selectListByNotify() {
  24 + return selectList(new QueryWrapper<PayNotifyTaskDO>()
  25 + .in("status", PayNotifyStatusEnum.WAITING.getStatus(), PayNotifyStatusEnum.REQUEST_SUCCESS.getStatus(),
  26 + PayNotifyStatusEnum.REQUEST_FAILURE.getStatus())
  27 + .le("next_notify_time", new Date()));
  28 + }
  29 +
10 30 }
... ...
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/dal/redis/PayRedisKeyCoreConstants.java 0 → 100644
  1 +package cn.iocoder.yudao.coreservice.modules.pay.dal.redis;
  2 +
  3 +import cn.iocoder.yudao.framework.redis.core.RedisKeyDefine;
  4 +import org.redisson.api.RLock;
  5 +
  6 +import static cn.iocoder.yudao.framework.redis.core.RedisKeyDefine.KeyTypeEnum.HASH;
  7 +
  8 +/**
  9 + * Lock4j Redis Key 枚举类
  10 + *
  11 + * @author 芋道源码
  12 + */
  13 +public interface PayRedisKeyCoreConstants {
  14 +
  15 + RedisKeyDefine PAY_NOTIFY_LOCK = new RedisKeyDefine("通知任务的分布式锁",
  16 + "pay_notify:lock:", // 参数来自 DefaultLockKeyBuilder 类
  17 + HASH, RLock.class, RedisKeyDefine.TimeoutTypeEnum.DYNAMIC); // Redisson 的 Lock 锁,使用 Hash 数据结构
  18 +
  19 +}
... ...
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/dal/redis/notify/PayNotifyLockCoreRedisDAO.java 0 → 100644
  1 +package cn.iocoder.yudao.coreservice.modules.pay.dal.redis.notify;
  2 +
  3 +import org.redisson.api.RLock;
  4 +import org.redisson.api.RedissonClient;
  5 +import org.springframework.stereotype.Repository;
  6 +
  7 +import javax.annotation.Resource;
  8 +import java.util.concurrent.TimeUnit;
  9 +
  10 +import static cn.iocoder.yudao.coreservice.modules.pay.dal.redis.PayRedisKeyCoreConstants.PAY_NOTIFY_LOCK;
  11 +
  12 +/**
  13 + * 支付通知的锁 Redis DAO
  14 + *
  15 + * @author 芋道源码
  16 + */
  17 +@Repository
  18 +public class PayNotifyLockCoreRedisDAO {
  19 +
  20 + @Resource
  21 + private RedissonClient redissonClient;
  22 +
  23 + public void lock(Long id, Long timeoutMillis, Runnable runnable) {
  24 + String lockKey = formatKey(id);
  25 + RLock lock = redissonClient.getLock(lockKey);
  26 + try {
  27 + lock.lock(timeoutMillis, TimeUnit.MILLISECONDS);
  28 + // 执行逻辑
  29 + runnable.run();
  30 + } finally {
  31 + lock.unlock();
  32 + }
  33 + }
  34 +
  35 + private static String formatKey(Long id) {
  36 + return String.format(PAY_NOTIFY_LOCK.getKeyTemplate(), id);
  37 + }
  38 +
  39 +}
... ...
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/notify/PayNotifyCoreService.java
... ... @@ -22,7 +22,8 @@ public interface PayNotifyCoreService {
22 22 * 执行支付通知
23 23 *
24 24 * 注意,该方法提供给定时任务调用。目前是 yudao-admin-server 进行调用
  25 + * @return 通知数量
25 26 */
26   - void executeNotify();
  27 + int executeNotify() throws InterruptedException;
27 28  
28 29 }
... ...
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/notify/impl/PayNotifyCoreServiceImpl.java
1 1 package cn.iocoder.yudao.coreservice.modules.pay.service.notify.impl;
2 2  
  3 +import cn.hutool.core.collection.CollUtil;
  4 +import cn.hutool.http.HttpUtil;
3 5 import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.notify.PayNotifyTaskDO;
4 6 import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayOrderDO;
5 7 import cn.iocoder.yudao.coreservice.modules.pay.dal.mysql.notify.PayNotifyTaskCoreMapper;
  8 +import cn.iocoder.yudao.coreservice.modules.pay.dal.redis.notify.PayNotifyLockCoreRedisDAO;
6 9 import cn.iocoder.yudao.coreservice.modules.pay.enums.notify.PayNotifyStatusEnum;
7 10 import cn.iocoder.yudao.coreservice.modules.pay.enums.notify.PayNotifyTypeEnum;
8 11 import cn.iocoder.yudao.coreservice.modules.pay.service.notify.PayNotifyCoreService;
9 12 import cn.iocoder.yudao.coreservice.modules.pay.service.notify.dto.PayNotifyTaskCreateReqDTO;
  13 +import cn.iocoder.yudao.coreservice.modules.pay.service.notify.vo.PayNotifyOrderReqVO;
  14 +import cn.iocoder.yudao.coreservice.modules.pay.service.notify.vo.PayRefundOrderReqVO;
10 15 import cn.iocoder.yudao.coreservice.modules.pay.service.order.PayOrderCoreService;
  16 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  17 +import cn.iocoder.yudao.framework.common.util.date.DateUtils;
  18 +import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
11 19 import lombok.extern.slf4j.Slf4j;
12 20 import org.springframework.context.annotation.Lazy;
  21 +import org.springframework.scheduling.annotation.Async;
13 22 import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
14 23 import org.springframework.stereotype.Service;
  24 +import org.springframework.transaction.annotation.Transactional;
15 25  
16 26 import javax.annotation.Resource;
17 27 import javax.validation.Valid;
18 28 import java.util.Date;
  29 +import java.util.List;
19 30 import java.util.Objects;
  31 +import java.util.concurrent.CountDownLatch;
  32 +import java.util.concurrent.TimeUnit;
  33 +
  34 +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.SECOND_MILLIS;
  35 +import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
20 36  
21 37 /**
22 38 * 支付通知 Core Service 实现类
... ... @@ -28,6 +44,15 @@ import java.util.Objects;
28 44 @Slf4j
29 45 public class PayNotifyCoreServiceImpl implements PayNotifyCoreService {
30 46  
  47 + /**
  48 + * 通知超时时间,单位:秒
  49 + */
  50 + public static final int NOTIFY_TIMEOUT = 120;
  51 + /**
  52 + * {@link #NOTIFY_TIMEOUT} 的毫秒
  53 + */
  54 + public static final long NOTIFY_TIMEOUT_MILLIS = 120 * SECOND_MILLIS;
  55 +
31 56 @Resource
32 57 @Lazy // 循环依赖,避免报错
33 58 private PayOrderCoreService payOrderCoreService;
... ... @@ -38,6 +63,13 @@ public class PayNotifyCoreServiceImpl implements PayNotifyCoreService {
38 63 @Resource
39 64 private ThreadPoolTaskExecutor threadPoolTaskExecutor; // TODO 芋艿:未来提供独立的线程池
40 65  
  66 + @Resource
  67 + private PayNotifyLockCoreRedisDAO payNotifyLockCoreRedisDAO;
  68 +
  69 + @Resource
  70 + @Lazy // 循环依赖(自己依赖自己),避免报错
  71 + private PayNotifyCoreServiceImpl self;
  72 +
41 73 @Override
42 74 public void createPayNotifyTask(PayNotifyTaskCreateReqDTO reqDTO) {
43 75 PayNotifyTaskDO task = new PayNotifyTaskDO();
... ... @@ -56,11 +88,132 @@ public class PayNotifyCoreServiceImpl implements PayNotifyCoreService {
56 88  
57 89 // 执行插入
58 90 payNotifyTaskCoreMapper.insert(task);
  91 +
  92 + // 异步直接发起任务。虽然会有定时任务扫描,但是会导致延迟
  93 + self.executeNotifyAsync(task);
59 94 }
60 95  
61 96 @Override
62   - public void executeNotify() {
  97 + public int executeNotify() throws InterruptedException {
  98 + // 获得需要通知的任务
  99 + List<PayNotifyTaskDO> tasks = payNotifyTaskCoreMapper.selectListByNotify();
  100 + if (CollUtil.isEmpty(tasks)) {
  101 + return 0;
  102 + }
  103 +
  104 + // 遍历,逐个通知
  105 + CountDownLatch latch = new CountDownLatch(tasks.size());
  106 + tasks.forEach(task -> threadPoolTaskExecutor.execute(() -> {
  107 + try {
  108 + executeNotify(task);
  109 + } finally {
  110 + latch.countDown();
  111 + }
  112 + }));
  113 + // 等待完成
  114 + this.awaitExecuteNotify(latch);
  115 + // 返回执行完成的任务数(成功 + 失败)
  116 + return tasks.size();
  117 + }
  118 +
  119 + /**
  120 + * 等待全部支付通知的完成
  121 + * 每 1 秒会打印一次剩余任务数量
  122 + *
  123 + * @param latch Latch
  124 + * @throws InterruptedException 如果被打断
  125 + */
  126 + private void awaitExecuteNotify(CountDownLatch latch) throws InterruptedException {
  127 + long size = latch.getCount();
  128 + for (int i = 0; i < NOTIFY_TIMEOUT; i++) {
  129 + if (latch.await(1L, TimeUnit.SECONDS)) {
  130 + return;
  131 + }
  132 + log.info("[awaitExecuteNotify][任务处理中, 总任务数({}) 剩余任务数({})]", size, latch.getCount());
  133 + }
  134 + log.error("[awaitExecuteNotify][任务未处理完,总任务数({}) 剩余任务数({})]", size, latch.getCount());
  135 + }
  136 +
  137 + /**
  138 + * 异步执行单个支付通知
  139 + *
  140 + * @param task 通知任务
  141 + */
  142 + @Async
  143 + public void executeNotifyAsync(PayNotifyTaskDO task) {
  144 + self.executeNotify(task); // 使用 self,避免事务不发起
  145 + }
63 146  
  147 + /**
  148 + * 同步执行单个支付通知
  149 + *
  150 + * @param task 通知任务
  151 + */
  152 + public void executeNotify(PayNotifyTaskDO task) {
  153 + // 分布式锁,避免并发问题
  154 + payNotifyLockCoreRedisDAO.lock(task.getId(), NOTIFY_TIMEOUT_MILLIS, () -> {
  155 + // 校验,当前任务是否已经被通知过
  156 + // 虽然已经通过分布式加锁,但是可能同时满足通知的条件,然后都去获得锁。此时,第一个执行完后,第二个还是能拿到锁,然后会再执行一次。
  157 + PayNotifyTaskDO dbTask = payNotifyTaskCoreMapper.selectById(task.getId());
  158 + if (DateUtils.afterNow(dbTask.getNextNotifyTime())) {
  159 + log.info("[executeNotify][dbTask({}) 任务被忽略,原因是未到达下次通知时间,可能是因为并发执行了]", toJsonString(dbTask));
  160 + return;
  161 + }
  162 +
  163 + // 执行通知
  164 + executeNotify0(dbTask);
  165 + });
  166 + }
  167 +
  168 + @Transactional
  169 + public void executeNotify0(PayNotifyTaskDO task) {
  170 + // 发起回调
  171 + CommonResult<?> invokeResult = null;
  172 + Throwable invokeException = null;
  173 + try {
  174 + invokeResult = executeNotifyInvoke(task);
  175 + } catch (Throwable e) {
  176 + invokeException = e;
  177 + }
  178 +
  179 + // 设置通用的更新 PayNotifyTaskDO 的字段
  180 + PayNotifyTaskDO updateTask = new PayNotifyTaskDO()
  181 + .setId(task.getId())
  182 + .setLastExecuteTime(new Date())
  183 + .setNotifyTimes(task.getNotifyTimes() + 1);
  184 +
  185 + // 情况一:调用成功
  186 +
  187 + // 情况二:调用失败
  188 +
  189 + // 调用三:调用异常
  190 +
  191 + // 记录 PayNotifyLog 日志
  192 + }
  193 +
  194 + /**
  195 + * 执行单个支付任务的 HTTP 调用
  196 + *
  197 + * @param task 通知任务
  198 + * @return HTTP 响应
  199 + */
  200 + private CommonResult<?> executeNotifyInvoke(PayNotifyTaskDO task) {
  201 + // 拼接参数
  202 + Object request;
  203 + if (Objects.equals(task.getType(), PayNotifyTypeEnum.ORDER.getType())) {
  204 + request = PayNotifyOrderReqVO.builder().merchantOrderId(task.getMerchantOrderId())
  205 + .payOrderId(task.getDataId()).build();
  206 + } else if (Objects.equals(task.getType(), PayNotifyTypeEnum.REFUND.getType())) {
  207 + request = PayRefundOrderReqVO.builder().merchantOrderId(task.getMerchantOrderId())
  208 + .payRefundId(task.getDataId()).build();
  209 + } else {
  210 + throw new RuntimeException("未知的通知任务类型:" + toJsonString(task));
  211 + }
  212 + // 请求地址
  213 + String response = HttpUtil.post(task.getNotifyUrl(), toJsonString(request),
  214 + (int) NOTIFY_TIMEOUT_MILLIS);
  215 + // 解析结果
  216 + return JsonUtils.parseObject(response, CommonResult.class);
64 217 }
65 218  
66 219 }
... ...
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/notify/vo/PayNotifyOrderReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.coreservice.modules.pay.service.notify.vo;
  2 +
  3 +import io.swagger.annotations.ApiModel;
  4 +import io.swagger.annotations.ApiModelProperty;
  5 +import lombok.AllArgsConstructor;
  6 +import lombok.Builder;
  7 +import lombok.Data;
  8 +import lombok.NoArgsConstructor;
  9 +
  10 +import javax.validation.constraints.NotEmpty;
  11 +import javax.validation.constraints.NotNull;
  12 +
  13 +@ApiModel(value = "支付单的通知 Request VO", description = "业务方接入支付回调时,使用该 VO 对象")
  14 +@Data
  15 +@Builder
  16 +@NoArgsConstructor
  17 +@AllArgsConstructor
  18 +public class PayNotifyOrderReqVO {
  19 +
  20 + @ApiModelProperty(value = "商户订单编号", required = true, example = "10")
  21 + @NotEmpty(message = "商户订单号不能为空")
  22 + private String merchantOrderId;
  23 +
  24 + @ApiModelProperty(value = "支付订单编号", required = true, example = "20")
  25 + @NotNull(message = "支付订单编号不能为空")
  26 + private Long payOrderId;
  27 +
  28 +}
... ...
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/notify/vo/PayRefundOrderReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.coreservice.modules.pay.service.notify.vo;
  2 +
  3 +import io.swagger.annotations.ApiModel;
  4 +import io.swagger.annotations.ApiModelProperty;
  5 +import lombok.AllArgsConstructor;
  6 +import lombok.Builder;
  7 +import lombok.Data;
  8 +import lombok.NoArgsConstructor;
  9 +
  10 +import javax.validation.constraints.NotEmpty;
  11 +import javax.validation.constraints.NotNull;
  12 +
  13 +@ApiModel(value = "退款单的通知 Request VO", description = "业务方接入退款回调时,使用该 VO 对象")
  14 +@Data
  15 +@Builder
  16 +@NoArgsConstructor
  17 +@AllArgsConstructor
  18 +public class PayRefundOrderReqVO {
  19 +
  20 + @ApiModelProperty(value = "商户订单编号", required = true, example = "10")
  21 + @NotEmpty(message = "商户订单号不能为空")
  22 + private String merchantOrderId;
  23 +
  24 + @ApiModelProperty(value = "支付退款编号", required = true, example = "20")
  25 + @NotNull(message = "支付退款编号不能为空")
  26 + private Long payRefundId;
  27 +
  28 +}
... ...
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/notify/vo/package-info.java 0 → 100644
  1 +/**
  2 + * 这里的 VO 包有点特殊,是提供给接入支付模块的业务,提供回调接口时,可以直接使用 VO
  3 + *
  4 + * 例如说,支付单的回调,使用
  5 + */
  6 +package cn.iocoder.yudao.coreservice.modules.pay.service.notify.vo;
... ...
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/date/DateUtils.java
... ... @@ -6,6 +6,8 @@ import java.util.Date;
6 6  
7 7 /**
8 8 * 时间工具类
  9 + *
  10 + * @author 芋道源码
9 11 */
10 12 public class DateUtils {
11 13  
... ... @@ -14,6 +16,11 @@ public class DateUtils {
14 16 */
15 17 public static final String TIME_ZONE_DEFAULT = "GMT+8";
16 18  
  19 + /**
  20 + * 秒转换成毫秒
  21 + */
  22 + public static final long SECOND_MILLIS = 1000;
  23 +
17 24 public static final String FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND = "yyyy-MM-dd HH:mm:ss";
18 25  
19 26 public static Date addTime(Duration duration) {
... ... @@ -74,4 +81,12 @@ public class DateUtils {
74 81 return a.compareTo(b) > 0 ? a : b;
75 82 }
76 83  
  84 + public static boolean beforeNow(Date date) {
  85 + return date.getTime() < System.currentTimeMillis();
  86 + }
  87 +
  88 + public static boolean afterNow(Date date) {
  89 + return date.getTime() >= System.currentTimeMillis();
  90 + }
  91 +
77 92 }
... ...
yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/shop/controller/ShopOrderController.java
1 1 package cn.iocoder.yudao.userserver.modules.shop.controller;
2 2  
  3 +import cn.iocoder.yudao.coreservice.modules.pay.service.notify.vo.PayNotifyOrderReqVO;
3 4 import cn.iocoder.yudao.coreservice.modules.pay.service.order.PayOrderCoreService;
4 5 import cn.iocoder.yudao.coreservice.modules.pay.service.order.dto.PayOrderCreateReqDTO;
5 6 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
... ... @@ -10,10 +11,12 @@ import io.swagger.annotations.ApiOperation;
10 11 import lombok.extern.slf4j.Slf4j;
11 12 import org.springframework.validation.annotation.Validated;
12 13 import org.springframework.web.bind.annotation.PostMapping;
  14 +import org.springframework.web.bind.annotation.RequestBody;
13 15 import org.springframework.web.bind.annotation.RequestMapping;
14 16 import org.springframework.web.bind.annotation.RestController;
15 17  
16 18 import javax.annotation.Resource;
  19 +import javax.validation.Valid;
17 20 import java.time.Duration;
18 21  
19 22 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
... ... @@ -52,4 +55,11 @@ public class ShopOrderController {
52 55 .payOrderId(payOrderId).build());
53 56 }
54 57  
  58 + @PostMapping("/pay-notify")
  59 + @ApiOperation("支付回调")
  60 + public CommonResult<Boolean> payNotify(@RequestBody @Valid PayNotifyOrderReqVO reqVO) {
  61 + log.info("[payNotify][回调成功]");
  62 + return success(true);
  63 + }
  64 +
55 65 }
... ...