결제와 관련된 비즈니스 상황에서는, 주문이 생성된 후 일정 시간 내에 결제가 완료되지 않으면 시스템이 자동으로 해당 주문을 취소하는 기능이 종종 필요합니다.
이번 글에서는 Spring Boot를 사용해 주문이 생성된 후 30분 이내에 결제가 완료되지 않았을 때 자동으로 주문이 취소되도록 하는 여러 가지 방법을 예제 코드와 함께 설명해 보겠습니다.
방법 1: 스케줄 작업 (Scheduled Tasks) 사용하기
@Scheduled
어노테이션을 이용하면, 주기적으로 주문 내역을 스캔하는 스케줄 작업을 손쉽게 구현할 수 있습니다.
30분이 지나고 결제가 완료되지 않은 주문이 있으면 해당 주문은 자동으로 취소됩니다.
@Component
public class OrderSchedule {
private final OrderService orderService;
public OrderSchedule(OrderService orderService) {
this.orderService = orderService;
}
@Scheduled(cron = "0 0/1 * * * ?")
public void cancelUnpaidOrders() {
LocalDateTime now = LocalDateTime.now();
List<Integer> idList = orderService.getUnpaidOrders(now.minusMinutes(30));
orderService.cancelOrderList(idList);
}
}
방법 2: 지연 큐 (Delayed Queue) 사용하기
메시지 큐와 지연 큐를 활용하면, 주문이 생성될 때 해당 주문의 ID를 30분 후에 만료되도록 지연 큐에 푸시할 수 있습니다.
지연 시간이 지나면 메시지가 처리되고, 결제가 완료되지 않은 주문은 자동으로 취소됩니다.
@Service
public class OrderService {
private final RabbitTemplate rabbitTemplate;
public OrderService(RabbitTemplate rabbitTemplate) {
this.rabbitTemplate = rabbitTemplate;
}
public void createOrder(Order order) {
saveOrder(order);
sendOrderToDelayQueue(order.getId(), 30 * 60 * 1000);
}
private void sendOrderToDelayQueue(Integer orderId, int delayInMillis) {
rabbitTemplate.convertAndSend("orderDelayExchange", "orderDelayKey", orderId, message -> {
message.getMessageProperties().setDelay(delayInMillis);
return message;
});
}
}
@Component
public class OrderDelayConsumer {
private final OrderService orderService;
@Autowired
public OrderDelayConsumer(OrderService orderService) {
this.orderService = orderService;
}
@RabbitHandler
@RabbitListener(queues = "orderDelayQueue")
public void cancelOrder(String orderId) {
orderService.cancelOrder(orderId);
}
}
방법 3: Redis 만료 이벤트 사용하기
Redis의 키 만료 이벤트를 활용하면, 주문이 생성될 때 해당 주문의 키를 30분 뒤에 만료되도록 Redis에 저장할 수 있습니다.
키가 만료되면 Redis가 이벤트를 트리거하여 해당 주문을 취소하도록 합니다.
@Service
public class OrderService {
private final StringRedisTemplate redisTemplate;
@Autowired
public OrderService(StringRedisTemplate redisTemplate) {
this.redisTemplate = redisTemplate;
}
public void createOrder(Order order) {
saveOrder(order);
storeOrderInRedis(order.getId(), 30, TimeUnit.MINUTES);
}
private void storeOrderInRedis(String orderId, long timeout, TimeUnit unit) {
redisTemplate.opsForValue().set("order:" + orderId, orderId, timeout, unit);
}
public void onOrderKeyExpired(String orderId) {
cancelOrder(orderId);
}
}
참고: Redis의 키 만료 알림 기능은 일반적인 퍼블리시-구독 패턴을 따릅니다.
Redis에서는 키 만료와 같은 특정 이벤트를 구독할 수 있습니다. 이 기능을 사용하려면 Redis 서버를 적절히 설정해야 하며, 구체적인 설정 방법은 다음 글에서 다룰 예정입니다.
마무리
위에서 설명한 세 가지 방법 모두 주문 생성 후 30분 이내에 결제가 이루어지지 않으면 자동으로 주문을 취소하는 요구사항을 충족할 수 있습니다.
비즈니스 요구사항, 시스템 부하, 기타 요인을 고려해 본인 시스템에 가장 적합한 방법을 선택할 수 있습니다.
각 방법마다 장단점이 있으므로 신중하게 고려해 적절한 방법을 선택해야 합니다.