fix: correct order lifecycle refunds and add coverage
Some checks failed
Build and Push Backend / docker (push) Failing after 5s
Some checks failed
Build and Push Backend / docker (push) Failing after 5s
This commit is contained in:
@@ -11,6 +11,7 @@ import com.starry.admin.modules.custom.module.entity.PlayCustomUserInfoEntity;
|
||||
import com.starry.admin.modules.custom.service.IPlayCustomUserInfoService;
|
||||
import com.starry.admin.modules.order.mapper.PlayOrderInfoMapper;
|
||||
import com.starry.admin.modules.order.module.constant.OrderConstant;
|
||||
import com.starry.admin.modules.order.module.constant.OrderConstant.OrderStatus;
|
||||
import com.starry.admin.modules.order.module.dto.CommodityInfo;
|
||||
import com.starry.admin.modules.order.module.dto.OrderCreationRequest;
|
||||
import com.starry.admin.modules.order.module.dto.PaymentInfo;
|
||||
@@ -450,7 +451,7 @@ class PlayOrderInfoServiceTest {
|
||||
String orderId = "order_force_cancel";
|
||||
PlayOrderInfoEntity inProgressOrder = new PlayOrderInfoEntity();
|
||||
inProgressOrder.setId(orderId);
|
||||
inProgressOrder.setOrderStatus(OrderConstant.ORDER_STATUS_2);
|
||||
inProgressOrder.setOrderStatus(OrderStatus.IN_PROGRESS.getCode());
|
||||
inProgressOrder.setAcceptBy("clerk-1");
|
||||
inProgressOrder.setPurchaserBy("customer-1");
|
||||
inProgressOrder.setFinalAmount(BigDecimal.valueOf(100));
|
||||
@@ -458,7 +459,7 @@ class PlayOrderInfoServiceTest {
|
||||
|
||||
PlayOrderInfoEntity cancelledOrder = new PlayOrderInfoEntity();
|
||||
cancelledOrder.setId(orderId);
|
||||
cancelledOrder.setOrderStatus(OrderConstant.ORDER_STATUS_4);
|
||||
cancelledOrder.setOrderStatus(OrderStatus.CANCELLED.getCode());
|
||||
|
||||
PlayCustomUserInfoEntity customUserInfo = new PlayCustomUserInfoEntity();
|
||||
customUserInfo.setId("customer-1");
|
||||
@@ -472,18 +473,22 @@ class PlayOrderInfoServiceTest {
|
||||
any(BigDecimal.class), anyString(), anyString(), any(BigDecimal.class), any(BigDecimal.class), eq(orderId));
|
||||
doNothing().when(playOrderRefundInfoService).add(eq(orderId), eq("customer-1"), eq("clerk-1"), anyString(),
|
||||
anyString(), any(BigDecimal.class), anyString(), anyString(), anyString(), anyString(), anyString());
|
||||
doNothing().when(wxCustomMpService).sendOrderCancelMessage(any(PlayOrderInfoEntity.class), anyString());
|
||||
doNothing().when(wxCustomMpService).sendOrderCancelMessageAsync(any(PlayOrderInfoEntity.class), anyString());
|
||||
|
||||
assertDoesNotThrow(() -> orderService.forceCancelOngoingOrder("2", "admin-1", orderId,
|
||||
BigDecimal.valueOf(80), "管理员取消测试", Collections.emptyList()));
|
||||
|
||||
verify(orderInfoMapper, times(1)).updateById(any(PlayOrderInfoEntity.class));
|
||||
verify(customUserInfoService, times(1)).updateAccountBalanceById(eq("customer-1"), any(BigDecimal.class),
|
||||
any(BigDecimal.class), anyString(), anyString(), eq(BigDecimal.valueOf(80)), eq(BigDecimal.ZERO),
|
||||
eq(orderId));
|
||||
verify(playOrderRefundInfoService, times(1)).add(eq(orderId), eq("customer-1"), eq("clerk-1"), anyString(),
|
||||
eq("0"), eq(BigDecimal.valueOf(80)), eq("管理员取消测试"), eq("2"), eq("admin-1"), eq("0"), eq("0"));
|
||||
verify(wxCustomMpService, times(1)).sendOrderCancelMessage(any(PlayOrderInfoEntity.class),
|
||||
any(BigDecimal.class), eq(OrderConstant.BalanceOperationType.REFUND.getCode()), eq("订单取消退款"),
|
||||
eq(BigDecimal.valueOf(80)), eq(BigDecimal.ZERO), eq(orderId));
|
||||
verify(playOrderRefundInfoService, times(1)).add(eq(orderId), eq("customer-1"), eq("clerk-1"),
|
||||
eq(inProgressOrder.getPayMethod()),
|
||||
eq(OrderConstant.OrderRefundRecordType.PARTIAL.getCode()),
|
||||
eq(BigDecimal.valueOf(80)), eq("管理员取消测试"), eq("2"), eq("admin-1"),
|
||||
eq(OrderConstant.OrderRefundState.PROCESSING.getCode()),
|
||||
eq(OrderConstant.ReviewRequirement.NOT_REQUIRED.getCode()));
|
||||
verify(wxCustomMpService, times(1)).sendOrderCancelMessageAsync(any(PlayOrderInfoEntity.class),
|
||||
eq("管理员取消测试"));
|
||||
}
|
||||
|
||||
@@ -493,7 +498,7 @@ class PlayOrderInfoServiceTest {
|
||||
String orderId = "order_invalid_force_cancel";
|
||||
PlayOrderInfoEntity pendingOrder = new PlayOrderInfoEntity();
|
||||
pendingOrder.setId(orderId);
|
||||
pendingOrder.setOrderStatus(OrderConstant.ORDER_STATUS_0);
|
||||
pendingOrder.setOrderStatus(OrderStatus.PENDING.getCode());
|
||||
pendingOrder.setAcceptBy("clerk-1");
|
||||
pendingOrder.setPurchaserBy("customer-1");
|
||||
pendingOrder.setFinalAmount(BigDecimal.valueOf(50));
|
||||
|
||||
@@ -0,0 +1,216 @@
|
||||
package com.starry.admin.modules.order.service.impl;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper;
|
||||
import com.starry.admin.common.exception.CustomException;
|
||||
import com.starry.admin.modules.custom.module.entity.PlayCustomUserInfoEntity;
|
||||
import com.starry.admin.modules.custom.service.IPlayCustomUserInfoService;
|
||||
import com.starry.admin.modules.order.mapper.PlayOrderInfoMapper;
|
||||
import com.starry.admin.modules.order.module.constant.OrderConstant.BalanceOperationType;
|
||||
import com.starry.admin.modules.order.module.constant.OrderConstant.OperatorType;
|
||||
import com.starry.admin.modules.order.module.constant.OrderConstant.OrderRefundFlag;
|
||||
import com.starry.admin.modules.order.module.constant.OrderConstant.OrderRefundRecordType;
|
||||
import com.starry.admin.modules.order.module.constant.OrderConstant.OrderRefundState;
|
||||
import com.starry.admin.modules.order.module.constant.OrderConstant.OrderStatus;
|
||||
import com.starry.admin.modules.order.module.constant.OrderConstant.OrderTriggerSource;
|
||||
import com.starry.admin.modules.order.module.constant.OrderConstant.ReviewRequirement;
|
||||
import com.starry.admin.modules.order.module.dto.OrderCompletionContext;
|
||||
import com.starry.admin.modules.order.module.dto.OrderRefundContext;
|
||||
import com.starry.admin.modules.order.module.entity.PlayOrderInfoEntity;
|
||||
import com.starry.admin.modules.order.service.IPlayOrderRefundInfoService;
|
||||
import com.starry.admin.modules.weichat.service.WxCustomMpService;
|
||||
import com.starry.admin.modules.withdraw.entity.EarningsLineEntity;
|
||||
import com.starry.admin.modules.withdraw.service.IEarningsService;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.UUID;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class OrderLifecycleServiceImplTest {
|
||||
|
||||
@InjectMocks
|
||||
private OrderLifecycleServiceImpl lifecycleService;
|
||||
|
||||
@Mock
|
||||
private PlayOrderInfoMapper orderInfoMapper;
|
||||
|
||||
@Mock
|
||||
private IEarningsService earningsService;
|
||||
|
||||
@Mock
|
||||
private WxCustomMpService wxCustomMpService;
|
||||
|
||||
@Mock
|
||||
private IPlayOrderRefundInfoService orderRefundInfoService;
|
||||
|
||||
@Mock
|
||||
private IPlayCustomUserInfoService customUserInfoService;
|
||||
|
||||
@Test
|
||||
void completeOrder_inProgress_createsEarningsAndNotifies() {
|
||||
String orderId = UUID.randomUUID().toString();
|
||||
PlayOrderInfoEntity inProgress = buildOrder(orderId, OrderStatus.IN_PROGRESS.getCode());
|
||||
inProgress.setOrderEndTime(null);
|
||||
|
||||
PlayOrderInfoEntity completed = buildOrder(orderId, OrderStatus.COMPLETED.getCode());
|
||||
completed.setOrderEndTime(LocalDateTime.now());
|
||||
|
||||
when(orderInfoMapper.selectById(orderId)).thenReturn(inProgress, completed);
|
||||
when(orderInfoMapper.updateById(any(PlayOrderInfoEntity.class))).thenReturn(1);
|
||||
mockEarningsCounts(0L, 1L);
|
||||
|
||||
OrderCompletionContext context = OrderCompletionContext.of(
|
||||
OperatorType.CLERK.getCode(),
|
||||
inProgress.getAcceptBy(),
|
||||
OrderTriggerSource.WX_CLERK);
|
||||
|
||||
lifecycleService.completeOrder(orderId, context);
|
||||
|
||||
verify(orderInfoMapper).updateById(argThat(entity ->
|
||||
orderId.equals(entity.getId())
|
||||
&& OrderStatus.COMPLETED.getCode().equals(entity.getOrderStatus())
|
||||
&& entity.getOrderEndTime() != null));
|
||||
verify(earningsService).createFromOrder(completed);
|
||||
verify(wxCustomMpService).sendOrderFinishMessageAsync(completed);
|
||||
}
|
||||
|
||||
@Test
|
||||
void completeOrder_alreadyCompleted_skipsEarningsCreation() {
|
||||
String orderId = UUID.randomUUID().toString();
|
||||
PlayOrderInfoEntity alreadyCompleted = buildOrder(orderId, OrderStatus.COMPLETED.getCode());
|
||||
alreadyCompleted.setOrderEndTime(null);
|
||||
|
||||
PlayOrderInfoEntity completedWithEnd = buildOrder(orderId, OrderStatus.COMPLETED.getCode());
|
||||
completedWithEnd.setOrderEndTime(LocalDateTime.now());
|
||||
|
||||
when(orderInfoMapper.selectById(orderId)).thenReturn(alreadyCompleted, completedWithEnd);
|
||||
when(orderInfoMapper.updateById(any(PlayOrderInfoEntity.class))).thenReturn(1);
|
||||
mockEarningsCounts(1L);
|
||||
|
||||
lifecycleService.completeOrder(orderId, OrderCompletionContext.of(
|
||||
OperatorType.ADMIN.getCode(),
|
||||
"admin-1",
|
||||
OrderTriggerSource.ADMIN_CONSOLE));
|
||||
|
||||
verify(earningsService, never()).createFromOrder(any());
|
||||
verify(wxCustomMpService).sendOrderFinishMessageAsync(completedWithEnd);
|
||||
}
|
||||
|
||||
@Test
|
||||
void refundOrder_partialAmount_updatesLedgerAndRecords() {
|
||||
String orderId = UUID.randomUUID().toString();
|
||||
BigDecimal finalAmount = BigDecimal.valueOf(100);
|
||||
BigDecimal refundAmount = BigDecimal.valueOf(40);
|
||||
|
||||
PlayOrderInfoEntity order = buildOrder(orderId, OrderStatus.ACCEPTED.getCode());
|
||||
order.setFinalAmount(finalAmount);
|
||||
order.setOrderMoney(finalAmount);
|
||||
order.setRefundType(OrderRefundFlag.NOT_REFUNDED.getCode());
|
||||
order.setPayMethod("1");
|
||||
|
||||
when(orderInfoMapper.selectById(orderId)).thenReturn(order);
|
||||
when(orderInfoMapper.updateById(any(PlayOrderInfoEntity.class))).thenReturn(1);
|
||||
|
||||
PlayCustomUserInfoEntity customer = new PlayCustomUserInfoEntity();
|
||||
customer.setId(order.getPurchaserBy());
|
||||
customer.setAccountBalance(BigDecimal.valueOf(10));
|
||||
when(customUserInfoService.getById(order.getPurchaserBy())).thenReturn(customer);
|
||||
|
||||
OrderRefundContext context = new OrderRefundContext();
|
||||
context.setOrderId(orderId);
|
||||
context.setRefundAmount(refundAmount);
|
||||
context.setRefundReason("部分退款测试");
|
||||
context.setOperatorType(OperatorType.ADMIN.getCode());
|
||||
context.setOperatorId("admin-1");
|
||||
context.withTriggerSource(OrderTriggerSource.ADMIN_API);
|
||||
|
||||
lifecycleService.refundOrder(context);
|
||||
|
||||
ArgumentCaptor<PlayOrderInfoEntity> updateCaptor = ArgumentCaptor.forClass(PlayOrderInfoEntity.class);
|
||||
verify(orderInfoMapper).updateById(updateCaptor.capture());
|
||||
PlayOrderInfoEntity updated = updateCaptor.getValue();
|
||||
assertEquals(OrderStatus.CANCELLED.getCode(), updated.getOrderStatus());
|
||||
assertEquals(OrderRefundFlag.REFUNDED.getCode(), updated.getRefundType());
|
||||
assertEquals(refundAmount, updated.getRefundAmount());
|
||||
|
||||
verify(customUserInfoService).updateAccountBalanceById(
|
||||
eq(order.getPurchaserBy()),
|
||||
eq(customer.getAccountBalance()),
|
||||
eq(customer.getAccountBalance().add(refundAmount)),
|
||||
eq(BalanceOperationType.REFUND.getCode()),
|
||||
eq("订单退款"),
|
||||
eq(refundAmount),
|
||||
eq(BigDecimal.ZERO),
|
||||
eq(orderId));
|
||||
|
||||
verify(orderRefundInfoService).add(
|
||||
eq(orderId),
|
||||
eq(order.getPurchaserBy()),
|
||||
eq(order.getAcceptBy()),
|
||||
eq(order.getPayMethod()),
|
||||
eq(OrderRefundRecordType.PARTIAL.getCode()),
|
||||
eq(refundAmount),
|
||||
eq("部分退款测试"),
|
||||
eq(OperatorType.ADMIN.getCode()),
|
||||
eq("admin-1"),
|
||||
eq(OrderRefundState.PROCESSING.getCode()),
|
||||
eq(ReviewRequirement.NOT_REQUIRED.getCode()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void refundOrder_throwsWhenAlreadyRefunded() {
|
||||
String orderId = UUID.randomUUID().toString();
|
||||
PlayOrderInfoEntity order = buildOrder(orderId, OrderStatus.ACCEPTED.getCode());
|
||||
order.setFinalAmount(BigDecimal.TEN);
|
||||
order.setOrderMoney(BigDecimal.TEN);
|
||||
order.setRefundType(OrderRefundFlag.REFUNDED.getCode());
|
||||
|
||||
when(orderInfoMapper.selectById(orderId)).thenReturn(order);
|
||||
|
||||
OrderRefundContext context = new OrderRefundContext();
|
||||
context.setOrderId(orderId);
|
||||
context.setRefundAmount(BigDecimal.ONE);
|
||||
context.withTriggerSource(OrderTriggerSource.ADMIN_API);
|
||||
|
||||
assertThrows(CustomException.class, () -> lifecycleService.refundOrder(context));
|
||||
verify(orderInfoMapper, never()).updateById(any());
|
||||
verify(orderRefundInfoService, never()).add(anyString(), anyString(), anyString(), anyString(), anyString(), any(), anyString(), anyString(), anyString(), anyString(), anyString());
|
||||
}
|
||||
|
||||
private PlayOrderInfoEntity buildOrder(String orderId, String status) {
|
||||
PlayOrderInfoEntity entity = new PlayOrderInfoEntity();
|
||||
entity.setId(orderId);
|
||||
entity.setOrderStatus(status);
|
||||
entity.setAcceptBy("clerk-1");
|
||||
entity.setPurchaserBy("customer-1");
|
||||
entity.setTenantId("tenant-1");
|
||||
return entity;
|
||||
}
|
||||
|
||||
private void mockEarningsCounts(long... counts) {
|
||||
LambdaQueryChainWrapper<EarningsLineEntity> chain = Mockito.mock(LambdaQueryChainWrapper.class);
|
||||
when(chain.eq(any(), any())).thenReturn(chain);
|
||||
if (counts.length == 0) {
|
||||
when(chain.count()).thenReturn(0L);
|
||||
} else {
|
||||
org.mockito.stubbing.OngoingStubbing<Long> stubbing = when(chain.count()).thenReturn(counts[0]);
|
||||
for (int i = 1; i < counts.length; i++) {
|
||||
stubbing = stubbing.thenReturn(counts[i]);
|
||||
}
|
||||
}
|
||||
when(earningsService.lambdaQuery()).thenReturn(chain);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user