package com.tourguide.modules.order.service;
|
|
import com.alibaba.fastjson.JSONObject;
|
import com.tourguide.common.huifu.properties.HuiFuProperties;
|
import com.tourguide.modules.finance.service.GuideBalanceSettlementService;
|
import com.tourguide.modules.order.domain.OrderStatus;
|
import com.tourguide.modules.order.domain.RefundStatus;
|
import com.tourguide.modules.order.entity.TourOrder;
|
import com.tourguide.modules.order.entity.TourOrderRefund;
|
import com.tourguide.modules.order.mapper.TourOrderMapper;
|
import com.tourguide.modules.order.mapper.TourOrderRefundMapper;
|
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.Test;
|
|
import java.math.BigDecimal;
|
import java.time.LocalDateTime;
|
|
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.never;
|
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.when;
|
|
class HuifuRefundCallbackServiceTest {
|
private TourOrderMapper orderMapper;
|
private HuifuRefundCallbackService callbackService;
|
private GuideSessionTransitionService sessionTransitionService;
|
private GuideBalanceSettlementService balanceSettlementService;
|
|
@BeforeEach
|
void setUp() {
|
HuiFuProperties properties = new HuiFuProperties();
|
properties.setHuifuId("merchant-1");
|
orderMapper = mock(TourOrderMapper.class);
|
sessionTransitionService = mock(GuideSessionTransitionService.class);
|
balanceSettlementService = mock(GuideBalanceSettlementService.class);
|
callbackService = new HuifuRefundCallbackService(properties, orderMapper, null,
|
balanceSettlementService,
|
sessionTransitionService);
|
}
|
|
@Test
|
void successCallbackShouldCompleteRefundOnce() {
|
TourOrder order = refundingOrder();
|
when(orderMapper.selectByRefundOrderNoForUpdate("RF001")).thenReturn(order);
|
|
boolean first = callbackService.handle(successBody());
|
boolean duplicate = callbackService.handle(successBody());
|
|
assertThat(first).isTrue();
|
assertThat(duplicate).isFalse();
|
assertThat(order.getRefundStatus()).isEqualTo(RefundStatus.REFUNDED.getCode());
|
assertThat(order.getStatus()).isEqualTo(OrderStatus.REFUNDED.getCode());
|
assertThat(order.getRefundTime()).isNotNull();
|
verify(orderMapper).updateById(order);
|
}
|
|
@Test
|
void failedCallbackShouldRecordFailureWithoutCompletingRefund() {
|
TourOrder order = refundingOrder();
|
when(orderMapper.selectByRefundOrderNoForUpdate("RF001")).thenReturn(order);
|
JSONObject body = successBody();
|
body.put("refund_stat", "F");
|
|
boolean completed = callbackService.handle(body);
|
|
assertThat(completed).isFalse();
|
assertThat(order.getRefundStatus()).isEqualTo(RefundStatus.REFUND_FAILED.getCode());
|
verify(orderMapper).updateById(order);
|
}
|
|
@Test
|
void duplicateSuccessCallbackShouldNotUpdateAgain() {
|
TourOrder order = refundingOrder();
|
order.setRefundStatus(RefundStatus.REFUNDED.getCode());
|
when(orderMapper.selectByRefundOrderNoForUpdate("RF001")).thenReturn(order);
|
|
boolean completed = callbackService.handle(successBody());
|
|
assertThat(completed).isFalse();
|
verify(orderMapper, never()).updateById(order);
|
}
|
|
@Test
|
void nestedSuccessCallbackShouldCompleteOrder() {
|
TourOrder order = refundingOrder();
|
when(orderMapper.selectByRefundOrderNoForUpdate("RF001")).thenReturn(order);
|
JSONObject data = successBody();
|
JSONObject body = new JSONObject();
|
body.put("data", data);
|
|
assertThat(callbackService.handle(body)).isTrue();
|
assertThat(order.getStatus()).isEqualTo(OrderStatus.REFUNDED.getCode());
|
}
|
|
@Test
|
void partialSuccessCallbackShouldKeepRefundingBalance() {
|
TourOrder order = refundingOrder();
|
order.setRefundAmount(new BigDecimal("20.00"));
|
when(orderMapper.selectByRefundOrderNoForUpdate("RF001")).thenReturn(order);
|
JSONObject body = successBody();
|
body.put("ord_amt", "20.00");
|
|
assertThat(callbackService.handle(body)).isTrue();
|
assertThat(order.getRefundedAmount()).isEqualByComparingTo("20.00");
|
assertThat(order.getRefundStatus()).isEqualTo(RefundStatus.PARTIAL.getCode());
|
assertThat(order.getStatus()).isEqualTo(OrderStatus.CANCELLED.getCode());
|
}
|
|
@Test
|
void successCallbackShouldReverseSettledGuideEarning() {
|
TourOrder order = refundingOrder();
|
order.setGuideId(8L);
|
order.setAmount(new BigDecimal("100.00"));
|
order.setPaymentAmount(new BigDecimal("100.00"));
|
order.setGuideEarningAmount(new BigDecimal("90.00"));
|
order.setSettledAt(LocalDateTime.of(2026, 8, 2, 12, 0));
|
when(orderMapper.selectByRefundOrderNoForUpdate("RF001")).thenReturn(order);
|
|
JSONObject body = successBody();
|
body.put("ord_amt", "36.00");
|
|
assertThat(callbackService.handle(body)).isTrue();
|
|
verify(balanceSettlementService).reverseRefund(order, BigDecimal.ZERO,
|
new BigDecimal("36.00"));
|
}
|
|
@Test
|
void pendingMeetingSuccessCallbackShouldRestoreGuideSession() {
|
TourOrder order = refundingOrder();
|
order.setStatus(OrderStatus.PENDING_MEETING.getCode());
|
order.setGuideId(8L);
|
order.setReceivingSessionId(18L);
|
when(orderMapper.selectByRefundOrderNoForUpdate("RF001")).thenReturn(order);
|
|
assertThat(callbackService.handle(successBody())).isTrue();
|
|
verify(sessionTransitionService).restoreAfterOrderCancellation(order);
|
}
|
|
@Test
|
void followUpSuccessCallbackShouldCompleteRemainingRefund() {
|
TourOrder order = refundingOrder();
|
order.setRefundAmount(new BigDecimal("16.00"));
|
order.setRefundedAmount(new BigDecimal("20.00"));
|
when(orderMapper.selectByRefundOrderNoForUpdate("RF001")).thenReturn(order);
|
JSONObject body = successBody();
|
body.put("ord_amt", "16.00");
|
|
assertThat(callbackService.handle(body)).isTrue();
|
assertThat(order.getRefundedAmount()).isEqualByComparingTo("36.00");
|
assertThat(order.getRefundStatus()).isEqualTo(RefundStatus.REFUNDED.getCode());
|
assertThat(order.getStatus()).isEqualTo(OrderStatus.REFUNDED.getCode());
|
}
|
|
@Test
|
void partialSuccessShouldCompleteOnlyTheMatchedRefundRecord() {
|
TourOrderRefundMapper refundMapper = mock(TourOrderRefundMapper.class);
|
callbackService = new HuifuRefundCallbackService(properties(), orderMapper, refundMapper);
|
TourOrder order = refundingOrder();
|
order.setId(99L);
|
TourOrderRefund record = new TourOrderRefund();
|
record.setOrderId(99L);
|
record.setRefundOrderNo("RF001");
|
record.setRefundAmount(new BigDecimal("20.00"));
|
record.setRefundStatus(RefundStatus.REFUNDING.getCode());
|
when(refundMapper.selectByRefundOrderNoForUpdate("RF001")).thenReturn(record);
|
when(orderMapper.selectForUpdate(99L)).thenReturn(order);
|
JSONObject body = successBody();
|
body.put("ord_amt", "20.00");
|
|
assertThat(callbackService.handle(body)).isTrue();
|
|
assertThat(order.getRefundStatus()).isEqualTo(RefundStatus.PARTIAL.getCode());
|
assertThat(record.getRefundStatus()).isEqualTo(RefundStatus.REFUNDED.getCode());
|
verify(refundMapper).updateById(record);
|
}
|
|
private HuiFuProperties properties() {
|
HuiFuProperties properties = new HuiFuProperties();
|
properties.setHuifuId("merchant-1");
|
return properties;
|
}
|
|
private TourOrder refundingOrder() {
|
TourOrder order = new TourOrder();
|
order.setRefundOrderNo("RF001");
|
order.setRefundStatus(RefundStatus.REFUNDING.getCode());
|
order.setRefundAmount(new BigDecimal("36.00"));
|
order.setPaymentAmount(new BigDecimal("36.00"));
|
return order;
|
}
|
|
private JSONObject successBody() {
|
JSONObject body = new JSONObject();
|
body.put("req_seq_id", "RF001");
|
body.put("huifu_id", "merchant-1");
|
body.put("refund_stat", "S");
|
body.put("ord_amt", "36.00");
|
body.put("end_time", "20260803180000");
|
return body;
|
}
|
}
|