7.13 支付回调处理
This commit is contained in:
parent
267e250820
commit
26b3302a56
@ -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){
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
||||
|
||||
}
|
||||
|
@ -101,6 +101,7 @@ public class AliPayController implements IPayService {
|
||||
log.info("支付回调,买家付款金额: {}", params.get("buyer_pay_amount"));
|
||||
log.info("支付回调,支付回调,更新订单 {}", tradeNo);
|
||||
|
||||
orderService.changeOrderPaySuccess(tradeNo);
|
||||
return "success";
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,4 +0,0 @@
|
||||
/**
|
||||
* 任务服务,可以选择使用 Spring 默认提供的 Schedule https://bugstack.cn/md/road-map/quartz.html
|
||||
*/
|
||||
package edu.whut.trigger.job;
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user