|
|
@@ -0,0 +1,235 @@
|
|
|
+package com.ruoyi.app.pay;
|
|
|
+
|
|
|
+import com.baomidou.mybatisplus.core.conditions.Wrapper;
|
|
|
+import com.ruoyi.app.omgpay.OmgPaymentCreateOutcome;
|
|
|
+import com.ruoyi.app.omgpay.OmgPaymentCreateService;
|
|
|
+import com.ruoyi.app.omgpay.OmgPaymentMethod;
|
|
|
+import com.ruoyi.app.omgpay.dto.OmgCreatePaymentResponse;
|
|
|
+import com.ruoyi.app.pay.dto.PayMethodSwitchRequest;
|
|
|
+import com.ruoyi.common.core.domain.AjaxResult;
|
|
|
+import com.ruoyi.common.exception.ServiceException;
|
|
|
+import com.ruoyi.common.utils.spring.SpringUtils;
|
|
|
+import com.ruoyi.system.domain.InfoUser;
|
|
|
+import com.ruoyi.system.domain.PosOrder;
|
|
|
+import com.ruoyi.system.service.IInfoUserService;
|
|
|
+import com.ruoyi.system.service.IPosOrderService;
|
|
|
+import com.ruoyi.system.utils.OrderLogHelper;
|
|
|
+import org.junit.jupiter.api.AfterAll;
|
|
|
+import org.junit.jupiter.api.BeforeAll;
|
|
|
+import org.junit.jupiter.api.Test;
|
|
|
+import org.mockito.ArgumentCaptor;
|
|
|
+import org.mockito.InOrder;
|
|
|
+import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
|
|
+import org.springframework.context.support.StaticMessageSource;
|
|
|
+import org.springframework.test.util.ReflectionTestUtils;
|
|
|
+
|
|
|
+import java.util.Map;
|
|
|
+
|
|
|
+import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
|
+import static org.junit.jupiter.api.Assertions.assertNotNull;
|
|
|
+import static org.junit.jupiter.api.Assertions.assertNull;
|
|
|
+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.inOrder;
|
|
|
+import static org.mockito.Mockito.mock;
|
|
|
+import static org.mockito.Mockito.never;
|
|
|
+import static org.mockito.Mockito.times;
|
|
|
+import static org.mockito.Mockito.verify;
|
|
|
+import static org.mockito.Mockito.when;
|
|
|
+
|
|
|
+/**
|
|
|
+ * 待支付订单更换支付方式(#661)服务层测试。
|
|
|
+ * 覆盖:线下方式回写与日志、外送单拒线下、跨渠道委派与先回写后拉起的顺序、
|
|
|
+ * 同方式不重复回写、已支付/非法方式/非本人单/多门店子单拒绝。
|
|
|
+ */
|
|
|
+class PayMethodSwitchServiceTest {
|
|
|
+
|
|
|
+ private static ConfigurableListableBeanFactory originalBeanFactory;
|
|
|
+
|
|
|
+ @BeforeAll
|
|
|
+ static void initializeMessages() {
|
|
|
+ originalBeanFactory = (ConfigurableListableBeanFactory)
|
|
|
+ ReflectionTestUtils.getField(SpringUtils.class, "beanFactory");
|
|
|
+ // MessageUtils 取 key 本身,断言直接比对 i18n key
|
|
|
+ StaticMessageSource messageSource = new StaticMessageSource();
|
|
|
+ messageSource.setUseCodeAsDefaultMessage(true);
|
|
|
+ org.springframework.beans.factory.support.DefaultListableBeanFactory beanFactory =
|
|
|
+ new org.springframework.beans.factory.support.DefaultListableBeanFactory();
|
|
|
+ beanFactory.registerSingleton("messageSource", messageSource);
|
|
|
+ new SpringUtils().postProcessBeanFactory(beanFactory);
|
|
|
+ }
|
|
|
+
|
|
|
+ @AfterAll
|
|
|
+ static void restoreMessages() {
|
|
|
+ new SpringUtils().postProcessBeanFactory(originalBeanFactory);
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 待支付 LINE Pay 自取单:ddId=dd-1、userId=7、type=1、payType=3 */
|
|
|
+ private static PosOrder baseOrder() {
|
|
|
+ PosOrder order = new PosOrder();
|
|
|
+ order.setDdId("dd-1");
|
|
|
+ order.setParentDdId("dd-1");
|
|
|
+ order.setUserId(7L);
|
|
|
+ order.setState(0L);
|
|
|
+ order.setPayStatus(0L);
|
|
|
+ order.setAmount(90);
|
|
|
+ order.setType(1L);
|
|
|
+ order.setPayType("3");
|
|
|
+ return order;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static PayMethodSwitchRequest request(String orderId, String method) {
|
|
|
+ PayMethodSwitchRequest request = new PayMethodSwitchRequest();
|
|
|
+ request.setOrderId(orderId);
|
|
|
+ request.setPaymentMethod(method);
|
|
|
+ return request;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void switchLinePayOrderToOfflineUpdatesPayTypeWritesLogAndSkipsPayment() {
|
|
|
+ IPosOrderService orderService = mock(IPosOrderService.class);
|
|
|
+ OmgPaymentCreateService omgPaymentCreateService = mock(OmgPaymentCreateService.class);
|
|
|
+ LinePayService linePayService = mock(LinePayService.class);
|
|
|
+ OrderLogHelper orderLogHelper = mock(OrderLogHelper.class);
|
|
|
+ IInfoUserService infoUserService = mock(IInfoUserService.class);
|
|
|
+ InfoUser user = new InfoUser();
|
|
|
+ user.setUserId(7L);
|
|
|
+ user.setNickName("小明");
|
|
|
+ when(infoUserService.getById(7L)).thenReturn(user);
|
|
|
+ PosOrder order = baseOrder();
|
|
|
+ when(orderService.getOne(any(Wrapper.class))).thenReturn(order);
|
|
|
+ when(orderService.updateById(any(PosOrder.class))).thenReturn(true);
|
|
|
+ PayMethodSwitchService service = new PayMethodSwitchService(orderService,
|
|
|
+ omgPaymentCreateService, linePayService, orderLogHelper, infoUserService);
|
|
|
+
|
|
|
+ AjaxResult result = service.switchMethod(7L, request("dd-1", "1"));
|
|
|
+
|
|
|
+ ArgumentCaptor<PosOrder> saved = ArgumentCaptor.forClass(PosOrder.class);
|
|
|
+ verify(orderService).updateById(saved.capture());
|
|
|
+ assertEquals("1", saved.getValue().getPayType(), "应回写为目标方式货到付款");
|
|
|
+ verify(orderLogHelper).log(eq("dd-1"), eq(4), eq(7L), eq("小明"), anyString());
|
|
|
+ verify(linePayService, never()).create(any(), anyString());
|
|
|
+ assertEquals("1", ((Map<?, ?>) result.get("data")).get("payType"));
|
|
|
+ assertNull(((Map<?, ?>) result.get("data")).get("payParams"), "到付无支付环节,payParams 应为空");
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void switchDeliveryOrderToCashIsRejected() {
|
|
|
+ IPosOrderService orderService = mock(IPosOrderService.class);
|
|
|
+ PosOrder order = baseOrder();
|
|
|
+ order.setType(0L);
|
|
|
+ when(orderService.getOne(any(Wrapper.class))).thenReturn(order);
|
|
|
+ PayMethodSwitchService service = new PayMethodSwitchService(orderService,
|
|
|
+ mock(OmgPaymentCreateService.class), mock(LinePayService.class),
|
|
|
+ mock(OrderLogHelper.class), mock(IInfoUserService.class));
|
|
|
+
|
|
|
+ ServiceException exception = assertThrows(ServiceException.class,
|
|
|
+ () -> service.switchMethod(7L, request("dd-1", "4")));
|
|
|
+ assertEquals("no.pay.switch.offline.restricted", exception.getMessage());
|
|
|
+ verify(orderService, never()).updateById(any(PosOrder.class));
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ @SuppressWarnings({"unchecked", "rawtypes"})
|
|
|
+ void switchToCreditCardRewritesPayTypeBeforeOmgCreate() {
|
|
|
+ IPosOrderService orderService = mock(IPosOrderService.class);
|
|
|
+ OmgPaymentCreateService omgPaymentCreateService = mock(OmgPaymentCreateService.class);
|
|
|
+ when(omgPaymentCreateService.create(eq(7L), eq("dd-1"), eq(OmgPaymentMethod.CREDIT)))
|
|
|
+ .thenReturn(new OmgPaymentCreateOutcome(mock(OmgCreatePaymentResponse.class), 1L, "dd-1", 7L, 5L, 90, "abcde***fghi"));
|
|
|
+ PosOrder order = baseOrder();
|
|
|
+ when(orderService.getOne(any(Wrapper.class))).thenReturn(order);
|
|
|
+ when(orderService.updateById(any(PosOrder.class))).thenReturn(true);
|
|
|
+ PayMethodSwitchService service = new PayMethodSwitchService(orderService,
|
|
|
+ omgPaymentCreateService, mock(LinePayService.class), mock(OrderLogHelper.class),
|
|
|
+ mock(IInfoUserService.class));
|
|
|
+
|
|
|
+ AjaxResult result = service.switchMethod(7L, request("dd-1", "2"));
|
|
|
+
|
|
|
+ // 顺序约束:必须先回写 payType 再调 OMG 创建,否则创建校验按旧 payType 拒绝
|
|
|
+ InOrder inOrder = inOrder(orderService, omgPaymentCreateService);
|
|
|
+ inOrder.verify(orderService).updateById(any(PosOrder.class));
|
|
|
+ inOrder.verify(omgPaymentCreateService).create(eq(7L), eq("dd-1"), eq(OmgPaymentMethod.CREDIT));
|
|
|
+ Map<?, ?> data = (Map<?, ?>) result.get("data");
|
|
|
+ assertEquals("2", data.get("payType"));
|
|
|
+ assertNotNull(data.get("payParams"), "在线方式应带渠道支付参数");
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void sameMethodSkipsRewriteButStillDelegates() {
|
|
|
+ IPosOrderService orderService = mock(IPosOrderService.class);
|
|
|
+ OmgPaymentCreateService omgPaymentCreateService = mock(OmgPaymentCreateService.class);
|
|
|
+ when(omgPaymentCreateService.create(any(), anyString(), any(OmgPaymentMethod.class)))
|
|
|
+ .thenReturn(new OmgPaymentCreateOutcome(mock(OmgCreatePaymentResponse.class), 1L, "dd-1", 7L, 5L, 90, "abcde***fghi"));
|
|
|
+ PosOrder order = baseOrder();
|
|
|
+ order.setPayType("2");
|
|
|
+ when(orderService.getOne(any(Wrapper.class))).thenReturn(order);
|
|
|
+ PayMethodSwitchService service = new PayMethodSwitchService(orderService,
|
|
|
+ omgPaymentCreateService, mock(LinePayService.class), mock(OrderLogHelper.class),
|
|
|
+ mock(IInfoUserService.class));
|
|
|
+
|
|
|
+ service.switchMethod(7L, request("dd-1", "2"));
|
|
|
+
|
|
|
+ verify(orderService, never()).updateById(any(PosOrder.class));
|
|
|
+ verify(omgPaymentCreateService, times(1)).create(eq(7L), eq("dd-1"), eq(OmgPaymentMethod.CREDIT));
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void alreadyPaidOrderIsRejected() {
|
|
|
+ IPosOrderService orderService = mock(IPosOrderService.class);
|
|
|
+ PosOrder order = baseOrder();
|
|
|
+ order.setPayStatus(1L);
|
|
|
+ when(orderService.getOne(any(Wrapper.class))).thenReturn(order);
|
|
|
+ PayMethodSwitchService service = new PayMethodSwitchService(orderService,
|
|
|
+ mock(OmgPaymentCreateService.class), mock(LinePayService.class),
|
|
|
+ mock(OrderLogHelper.class), mock(IInfoUserService.class));
|
|
|
+
|
|
|
+ ServiceException exception = assertThrows(ServiceException.class,
|
|
|
+ () -> service.switchMethod(7L, request("dd-1", "2")));
|
|
|
+ assertEquals("no.pay.switch.already.paid", exception.getMessage());
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void unknownMethodIsRejectedBeforeOrderLookup() {
|
|
|
+ IPosOrderService orderService = mock(IPosOrderService.class);
|
|
|
+ PayMethodSwitchService service = new PayMethodSwitchService(orderService,
|
|
|
+ mock(OmgPaymentCreateService.class), mock(LinePayService.class),
|
|
|
+ mock(OrderLogHelper.class), mock(IInfoUserService.class));
|
|
|
+
|
|
|
+ ServiceException exception = assertThrows(ServiceException.class,
|
|
|
+ () -> service.switchMethod(7L, request("dd-1", "9")));
|
|
|
+ assertEquals("no.pay.switch.method.invalid", exception.getMessage());
|
|
|
+ verify(orderService, never()).getOne(any(Wrapper.class));
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void orderOfAnotherUserIsRejected() {
|
|
|
+ IPosOrderService orderService = mock(IPosOrderService.class);
|
|
|
+ PosOrder order = baseOrder();
|
|
|
+ order.setUserId(8L);
|
|
|
+ when(orderService.getOne(any(Wrapper.class))).thenReturn(order);
|
|
|
+ PayMethodSwitchService service = new PayMethodSwitchService(orderService,
|
|
|
+ mock(OmgPaymentCreateService.class), mock(LinePayService.class),
|
|
|
+ mock(OrderLogHelper.class), mock(IInfoUserService.class));
|
|
|
+
|
|
|
+ ServiceException exception = assertThrows(ServiceException.class,
|
|
|
+ () -> service.switchMethod(7L, request("dd-1", "2")));
|
|
|
+ assertEquals("no.pay.switch.order.invalid", exception.getMessage());
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void multiStoreSubOrderIsRejected() {
|
|
|
+ IPosOrderService orderService = mock(IPosOrderService.class);
|
|
|
+ PosOrder order = baseOrder();
|
|
|
+ order.setParentDdId("dd-parent");
|
|
|
+ when(orderService.getOne(any(Wrapper.class))).thenReturn(order);
|
|
|
+ PayMethodSwitchService service = new PayMethodSwitchService(orderService,
|
|
|
+ mock(OmgPaymentCreateService.class), mock(LinePayService.class),
|
|
|
+ mock(OrderLogHelper.class), mock(IInfoUserService.class));
|
|
|
+
|
|
|
+ ServiceException exception = assertThrows(ServiceException.class,
|
|
|
+ () -> service.switchMethod(7L, request("dd-1", "2")));
|
|
|
+ assertEquals("no.pay.switch.order.invalid", exception.getMessage());
|
|
|
+ }
|
|
|
+}
|