7.13 支付回调处理

This commit is contained in:
zhangsan 2025-07-13 19:29:17 +08:00
parent 267e250820
commit 26b3302a56
15 changed files with 358 additions and 8 deletions

View File

@ -3,9 +3,11 @@ package edu.whut;
import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
@SpringBootApplication
@Configurable
@EnableScheduling
public class Application {
public static void main(String[] args){

View File

@ -2,6 +2,8 @@ package edu.whut.config;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.eventbus.EventBus;
import edu.whut.trigger.listener.OrderPaySuccessListener;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@ -26,4 +28,16 @@ public class GuavaConfig {
.build();
}
/**
* 定义全局 EventBus用于发布并订阅应用内事件
* 在此注册 OrderPaySuccessListener使其能够接收支付成功事件
*/
@Bean
public EventBus eventBusListener(OrderPaySuccessListener listener){
EventBus eventBus = new EventBus();
// 注册监听器监听器会被自动调用事件处理方法
eventBus.register(listener);
return eventBus;
}
}

View File

@ -37,4 +37,28 @@
where order_id = #{orderId}
</update>
<update id="changeOrderPaySuccess" parameterType="edu.whut.infrastructure.dao.po.PayOrder">
update pay_order set status = #{status}, pay_time = now(), update_time = now()
where order_id = #{orderId}
</update>
<update id="changeOrderClose" parameterType="java.lang.String">
update pay_order set status = 'CLOSE', pay_time = now(), update_time = now()
where order_id = #{orderId}
</update>
<select id="queryTimeoutCloseOrderList" parameterType="java.lang.String" resultType="java.lang.String">
SELECT order_id as orderId FROM pay_order
WHERE status = 'PAY_WAIT' AND NOW() >= order_time + INTERVAL 30 MINUTE
ORDER BY id ASC
LIMIT 50
</select>
<select id="queryNoPayNotifyOrder" parameterType="java.lang.String" resultType="java.lang.String">
SELECT order_id as orderId FROM pay_order
WHERE status = 'PAY_WAIT' AND NOW() >= order_time + INTERVAL 1 MINUTE
ORDER BY id ASC
LIMIT 10
</select>
</mapper>

View File

@ -0,0 +1,39 @@
package edu.whut.domain.order.adapter.event;
import edu.whut.types.event.BaseEvent;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.apache.commons.lang3.RandomStringUtils;
import org.springframework.stereotype.Component;
import java.util.Date;
@Component
public class PaySuccessMessageEvent extends BaseEvent<PaySuccessMessageEvent.PaySuccessMessage> {
@Override
public EventMessage<PaySuccessMessage> buildEventMessage(PaySuccessMessage data) {
return EventMessage.<PaySuccessMessage>builder()
.id(RandomStringUtils.randomNumeric(11))
.timestamp(new Date())
.data(data)
.build();
}
@Override
public String topic() {
return "pay_success";
}
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public static class PaySuccessMessage{
private String userId;
private String tradeNo;
}
}

View File

@ -5,10 +5,20 @@ import edu.whut.domain.order.model.entity.OrderEntity;
import edu.whut.domain.order.model.entity.PayOrderEntity;
import edu.whut.domain.order.model.entity.ShopCartEntity;
import java.util.List;
public interface IOrderRepository {
void doSaveOrder(CreateOrderAggregate orderAggregate);
OrderEntity queryUnPayOrder(ShopCartEntity shopCartEntity);
void updateOrderPayInfo(PayOrderEntity payOrderEntity);
void changeOrderPaySuccess(String orderId);
List<String> queryNoPayNotifyOrder();
List<String> queryTimeoutCloseOrderList();
boolean changeOrderClose(String orderId);
}

View File

@ -4,8 +4,18 @@ package edu.whut.domain.order.service;
import edu.whut.domain.order.model.entity.PayOrderEntity;
import edu.whut.domain.order.model.entity.ShopCartEntity;
import java.util.List;
public interface IOrderService {
PayOrderEntity createOrder(ShopCartEntity shopCartEntity) throws Exception;
void changeOrderPaySuccess(String orderId);
List<String> queryNoPayNotifyOrder();
List<String> queryTimeoutCloseOrderList();
boolean changeOrderClose(String orderId);
}

View File

@ -14,6 +14,7 @@ import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.List;
@Slf4j
@Service
@ -65,4 +66,35 @@ public class OrderService extends AbstractOrderService{
return payOrderEntity;
}
/**
* 标记订单为支付成功并发布支付成功事件
*/
@Override
public void changeOrderPaySuccess(String orderId) {
repository.changeOrderPaySuccess(orderId);
}
/**
* 查询超过1分钟未支付且待通知的订单列表
*/
@Override
public List<String> queryNoPayNotifyOrder() {
return repository.queryNoPayNotifyOrder();
}
/**
* 查询超过30分钟仍未支付的订单用于批量关闭
*/
@Override
public List<String> queryTimeoutCloseOrderList() {
return repository.queryTimeoutCloseOrderList();
}
/**
* 将指定订单状态改为 CLOSE关闭
*/
@Override
public boolean changeOrderClose(String orderId) {
return repository.changeOrderClose(orderId);
}
}

View File

@ -1,5 +1,7 @@
package edu.whut.infrastructure.adapter.repository;
import com.google.common.eventbus.EventBus;
import edu.whut.domain.order.adapter.event.PaySuccessMessageEvent;
import edu.whut.domain.order.adapter.repository.IOrderRepository;
import edu.whut.domain.order.model.aggregate.CreateOrderAggregate;
import edu.whut.domain.order.model.entity.OrderEntity;
@ -9,21 +11,33 @@ import edu.whut.domain.order.model.entity.ShopCartEntity;
import edu.whut.domain.order.model.valobj.OrderStatusVO;
import edu.whut.infrastructure.dao.IOrderDao;
import edu.whut.infrastructure.dao.po.PayOrder;
import edu.whut.types.event.BaseEvent;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;
import javax.annotation.Resource;
import java.util.List;
@Repository
@RequiredArgsConstructor
public class OrderRepository implements IOrderRepository {
private final IOrderDao orderDao;
private final PaySuccessMessageEvent paySuccessMessageEvent;
private final EventBus eventBus;
/**
* 保存新订单到数据库
*/
@Override
public void doSaveOrder(CreateOrderAggregate orderAggregate) {
String userId = orderAggregate.getUserId();
ProductEntity productEntity = orderAggregate.getProductEntity();
OrderEntity orderEntity = orderAggregate.getOrderEntity();
// 构造持久层 PayOrder 对象并赋值
PayOrder order = new PayOrder();
order.setUserId(userId);
order.setProductId(productEntity.getProductId());
@ -33,21 +47,25 @@ public class OrderRepository implements IOrderRepository {
order.setTotalAmount(productEntity.getPrice());
order.setStatus(orderEntity.getOrderStatusVO().getCode());
// 插入数据库
orderDao.insert(order);
}
/**
* 查询最新一条未支付订单用于预支付流程复用已有订单
*/
@Override
public OrderEntity queryUnPayOrder(ShopCartEntity shopCartEntity) {
// 1. 封装参数
// 构建查询条件
PayOrder orderReq = new PayOrder();
orderReq.setUserId(shopCartEntity.getUserId());
orderReq.setProductId(shopCartEntity.getProductId());
// 2. 查询到订单
// 执行查询
PayOrder order = orderDao.queryUnPayOrder(orderReq);
if (null == order) return null;
if (null == order) return null; // 未找到
// 3. 返回结果
// 映射到领域实体返回
return OrderEntity.builder()
.productId(order.getProductId())
.productName(order.getProductName())
@ -59,6 +77,10 @@ public class OrderRepository implements IOrderRepository {
.build();
}
/**
* 更新订单支付信息支付 URL状态
*/
@Override
public void updateOrderPayInfo(PayOrderEntity payOrderEntity) {
PayOrder payOrderReq = PayOrder.builder()
@ -69,4 +91,49 @@ public class OrderRepository implements IOrderRepository {
.build();
orderDao.updateOrderPayInfo(payOrderReq);
}
/**
* 标记订单为支付成功并发布支付成功事件
*/
@Override
public void changeOrderPaySuccess(String orderId) {
// 更新状态为 PAY_SUCCESS 并记录支付时间
PayOrder payOrderReq = new PayOrder();
payOrderReq.setOrderId(orderId);
payOrderReq.setStatus(OrderStatusVO.PAY_SUCCESS.getCode());
orderDao.changeOrderPaySuccess(payOrderReq);
// 构建并发布支付成功消息给 EventBus
BaseEvent.EventMessage<PaySuccessMessageEvent.PaySuccessMessage> evtMsg = paySuccessMessageEvent.buildEventMessage(
PaySuccessMessageEvent.PaySuccessMessage.builder()
.tradeNo(orderId)
.build()
);
eventBus.post(evtMsg.getData());
}
/**
* 查询超过1分钟未支付且待通知的订单列表
*/
@Override
public List<String> queryNoPayNotifyOrder() {
return orderDao.queryNoPayNotifyOrder();
}
/**
* 查询超过30分钟仍未支付的订单用于批量关闭
*/
@Override
public List<String> queryTimeoutCloseOrderList() {
return orderDao.queryTimeoutCloseOrderList();
}
/**
* 将指定订单状态改为 CLOSE关闭
*/
@Override
public boolean changeOrderClose(String orderId) {
return orderDao.changeOrderClose(orderId);
}
}

View File

@ -2,6 +2,8 @@ package edu.whut.infrastructure.dao;
import edu.whut.infrastructure.dao.po.PayOrder;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@Mapper
public interface IOrderDao {
@ -11,4 +13,13 @@ public interface IOrderDao {
void updateOrderPayInfo(PayOrder payOrder);
void changeOrderPaySuccess(PayOrder payOrderReq);
List<String> queryNoPayNotifyOrder();
List<String> queryTimeoutCloseOrderList();
boolean changeOrderClose(String orderId);
}

View File

@ -101,6 +101,7 @@ public class AliPayController implements IPayService {
log.info("支付回调,买家付款金额: {}", params.get("buyer_pay_amount"));
log.info("支付回调,支付回调,更新订单 {}", tradeNo);
orderService.changeOrderPaySuccess(tradeNo);
return "success";
}

View File

@ -0,0 +1,56 @@
package edu.whut.trigger.job;
import com.alipay.api.AlipayClient;
import com.alipay.api.domain.AlipayTradeQueryModel;
import com.alipay.api.request.AlipayTradeQueryRequest;
import com.alipay.api.response.AlipayTradeQueryResponse;
import edu.whut.domain.order.service.IOrderService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.List;
/**
* 检测未接收到或未正确处理的支付回调通知
*/
@Slf4j
@Component()
public class NoPayNotifyOrderJob {
@Resource
private IOrderService orderService;
@Resource
private AlipayClient alipayClient;
/**
* 每3秒执行一次扫描超过1分钟未收到回调的待支付订单
*/
@Scheduled(cron = "0/3 * * * * ?")
public void exec() {
try {
log.info("任务;检测未接收到或未正确处理的支付回调通知");
List<String> orderIds = orderService.queryNoPayNotifyOrder();
if (null == orderIds || orderIds.isEmpty()) return;
// 遍历订单调用支付宝查询接口核实支付结果
for (String orderId : orderIds) {
AlipayTradeQueryRequest request = new AlipayTradeQueryRequest();
AlipayTradeQueryModel bizModel = new AlipayTradeQueryModel();
bizModel.setOutTradeNo(orderId);
request.setBizModel(bizModel);
// 发起同步查询
AlipayTradeQueryResponse alipayTradeQueryResponse = alipayClient.execute(request);
String code = alipayTradeQueryResponse.getCode();
// 如果支付宝返回业务成功码(10000)则更新订单为支付成功
if ("10000".equals(code)) {
orderService.changeOrderPaySuccess(orderId);
}
}
} catch (Exception e) {
log.error("检测未接收到或未正确处理的支付回调通知失败", e);
}
}
}

View File

@ -0,0 +1,42 @@
package edu.whut.trigger.job;
import edu.whut.domain.order.service.IOrderService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.List;
/**
* 超时关单
*/
@Slf4j
@Component()
public class TimeoutCloseOrderJob {
@Resource
private IOrderService orderService;
/**
* 每10分钟执行一次扫描超时未支付订单
*/
@Scheduled(cron = "0 0/10 * * * ?")
public void exec() {
try {
log.info("任务超时30分钟订单关闭");
List<String> orderIds = orderService.queryTimeoutCloseOrderList();
if (null == orderIds || orderIds.isEmpty()) {
log.info("定时任务超时30分钟订单关闭暂无超时未支付订单 orderIds is null");
return;
}
// 遍历订单逐一关闭并记录结果
for (String orderId : orderIds) {
boolean status = orderService.changeOrderClose(orderId);
log.info("定时任务超时30分钟订单关闭 orderId: {} status{}", orderId, status);
}
} catch (Exception e) {
log.error("定时任务超时15分钟订单关闭失败", e);
}
}
}

View File

@ -1,4 +0,0 @@
/**
* 任务服务可以选择使用 Spring 默认提供的 Schedule https://bugstack.cn/md/road-map/quartz.html
*/
package edu.whut.trigger.job;

View File

@ -0,0 +1,19 @@
package edu.whut.trigger.listener;
import com.google.common.eventbus.Subscribe;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
/**
* 支付成功回调消息
*/
@Slf4j
@Component
public class OrderPaySuccessListener {
@Subscribe
public void handleEvent(String paySuccessMessage) {
log.info("收到支付成功消息,可以做接下来的事情,如;发货、充值、开户员、返利 {}", paySuccessMessage);
}
}

View File

@ -0,0 +1,27 @@
package edu.whut.types.event;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Date;
@Data
public abstract class BaseEvent<T> {
public abstract EventMessage<T> buildEventMessage(T data);
public abstract String topic();
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public static class EventMessage<T> {
private String id;
private Date timestamp;
private T data;
}
}