PayMethodSwitchServiceTest.java 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237
  1. package com.ruoyi.app.pay;
  2. import com.baomidou.mybatisplus.core.conditions.Wrapper;
  3. import com.ruoyi.app.omgpay.OmgPaymentCreateOutcome;
  4. import com.ruoyi.app.omgpay.OmgPaymentCreateService;
  5. import com.ruoyi.app.omgpay.OmgPaymentRetryService;
  6. import com.ruoyi.app.omgpay.OmgPaymentMethod;
  7. import com.ruoyi.app.omgpay.dto.OmgCreatePaymentResponse;
  8. import com.ruoyi.app.pay.dto.PayMethodSwitchRequest;
  9. import com.ruoyi.common.core.domain.AjaxResult;
  10. import com.ruoyi.common.exception.ServiceException;
  11. import com.ruoyi.common.utils.spring.SpringUtils;
  12. import com.ruoyi.system.domain.InfoUser;
  13. import com.ruoyi.system.domain.PosOrder;
  14. import com.ruoyi.system.service.IInfoUserService;
  15. import com.ruoyi.system.service.IPosOrderService;
  16. import com.ruoyi.system.utils.OrderLogHelper;
  17. import org.junit.jupiter.api.AfterAll;
  18. import org.junit.jupiter.api.BeforeAll;
  19. import org.junit.jupiter.api.Test;
  20. import org.mockito.ArgumentCaptor;
  21. import org.mockito.InOrder;
  22. import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
  23. import org.springframework.context.support.StaticMessageSource;
  24. import org.springframework.test.util.ReflectionTestUtils;
  25. import java.util.Map;
  26. import static org.junit.jupiter.api.Assertions.assertEquals;
  27. import static org.junit.jupiter.api.Assertions.assertNotNull;
  28. import static org.junit.jupiter.api.Assertions.assertNull;
  29. import static org.junit.jupiter.api.Assertions.assertThrows;
  30. import static org.mockito.ArgumentMatchers.any;
  31. import static org.mockito.ArgumentMatchers.anyString;
  32. import static org.mockito.ArgumentMatchers.eq;
  33. import static org.mockito.Mockito.inOrder;
  34. import static org.mockito.Mockito.mock;
  35. import static org.mockito.Mockito.never;
  36. import static org.mockito.Mockito.times;
  37. import static org.mockito.Mockito.verify;
  38. import static org.mockito.Mockito.when;
  39. /**
  40. * 待支付订单更换支付方式(#661)服务层测试。
  41. * 覆盖:线下方式回写与日志、外送单拒线下、跨渠道委派与先回写后拉起的顺序、
  42. * 同方式不重复回写、已支付/非法方式/非本人单/多门店子单拒绝。
  43. */
  44. class PayMethodSwitchServiceTest {
  45. private static ConfigurableListableBeanFactory originalBeanFactory;
  46. @BeforeAll
  47. static void initializeMessages() {
  48. originalBeanFactory = (ConfigurableListableBeanFactory)
  49. ReflectionTestUtils.getField(SpringUtils.class, "beanFactory");
  50. // MessageUtils 取 key 本身,断言直接比对 i18n key
  51. StaticMessageSource messageSource = new StaticMessageSource();
  52. messageSource.setUseCodeAsDefaultMessage(true);
  53. org.springframework.beans.factory.support.DefaultListableBeanFactory beanFactory =
  54. new org.springframework.beans.factory.support.DefaultListableBeanFactory();
  55. beanFactory.registerSingleton("messageSource", messageSource);
  56. new SpringUtils().postProcessBeanFactory(beanFactory);
  57. }
  58. @AfterAll
  59. static void restoreMessages() {
  60. new SpringUtils().postProcessBeanFactory(originalBeanFactory);
  61. }
  62. /** 待支付 LINE Pay 自取单:ddId=dd-1、userId=7、type=1、payType=3 */
  63. private static PosOrder baseOrder() {
  64. PosOrder order = new PosOrder();
  65. order.setDdId("dd-1");
  66. order.setParentDdId("dd-1");
  67. order.setUserId(7L);
  68. order.setState(0L);
  69. order.setPayStatus(0L);
  70. order.setAmount(90);
  71. order.setType(1L);
  72. order.setPayType("3");
  73. return order;
  74. }
  75. private static PayMethodSwitchRequest request(String orderId, String method) {
  76. PayMethodSwitchRequest request = new PayMethodSwitchRequest();
  77. request.setOrderId(orderId);
  78. request.setPaymentMethod(method);
  79. return request;
  80. }
  81. @Test
  82. void switchLinePayOrderToOfflineUpdatesPayTypeWritesLogAndSkipsPayment() {
  83. IPosOrderService orderService = mock(IPosOrderService.class);
  84. OmgPaymentRetryService omgPaymentRetryService = mock(OmgPaymentRetryService.class);
  85. LinePayService linePayService = mock(LinePayService.class);
  86. OrderLogHelper orderLogHelper = mock(OrderLogHelper.class);
  87. IInfoUserService infoUserService = mock(IInfoUserService.class);
  88. InfoUser user = new InfoUser();
  89. user.setUserId(7L);
  90. user.setNickName("小明");
  91. when(infoUserService.getById(7L)).thenReturn(user);
  92. PosOrder order = baseOrder();
  93. when(orderService.getOne(any(Wrapper.class))).thenReturn(order);
  94. when(orderService.updateById(any(PosOrder.class))).thenReturn(true);
  95. PayMethodSwitchService service = new PayMethodSwitchService(orderService,
  96. omgPaymentRetryService, linePayService, orderLogHelper, infoUserService);
  97. AjaxResult result = service.switchMethod(7L, request("dd-1", "1"));
  98. ArgumentCaptor<PosOrder> saved = ArgumentCaptor.forClass(PosOrder.class);
  99. verify(orderService).updateById(saved.capture());
  100. assertEquals("1", saved.getValue().getPayType(), "应回写为目标方式货到付款");
  101. verify(orderLogHelper).log(eq("dd-1"), eq(4), eq(7L), eq("小明"), anyString());
  102. verify(linePayService, never()).create(any(), anyString());
  103. assertEquals("1", ((Map<?, ?>) result.get("data")).get("payType"));
  104. assertNull(((Map<?, ?>) result.get("data")).get("payParams"), "到付无支付环节,payParams 应为空");
  105. }
  106. @Test
  107. void switchDeliveryOrderToCashIsRejected() {
  108. IPosOrderService orderService = mock(IPosOrderService.class);
  109. PosOrder order = baseOrder();
  110. order.setType(0L);
  111. when(orderService.getOne(any(Wrapper.class))).thenReturn(order);
  112. PayMethodSwitchService service = new PayMethodSwitchService(orderService,
  113. mock(OmgPaymentRetryService.class), mock(LinePayService.class),
  114. mock(OrderLogHelper.class), mock(IInfoUserService.class));
  115. ServiceException exception = assertThrows(ServiceException.class,
  116. () -> service.switchMethod(7L, request("dd-1", "4")));
  117. assertEquals("no.pay.switch.offline.restricted", exception.getMessage());
  118. verify(orderService, never()).updateById(any(PosOrder.class));
  119. }
  120. @Test
  121. @SuppressWarnings({"unchecked", "rawtypes"})
  122. void switchToCreditCardRewritesPayTypeBeforeOmgCreate() {
  123. IPosOrderService orderService = mock(IPosOrderService.class);
  124. OmgPaymentRetryService omgPaymentRetryService = mock(OmgPaymentRetryService.class);
  125. when(omgPaymentRetryService.retry(eq(7L), eq("dd-1"), eq(OmgPaymentMethod.CREDIT)))
  126. .thenReturn(new OmgPaymentCreateOutcome(mock(OmgCreatePaymentResponse.class), 1L, "dd-1", 7L, 5L, 90, "abcde***fghi"));
  127. PosOrder order = baseOrder();
  128. when(orderService.getOne(any(Wrapper.class))).thenReturn(order);
  129. when(orderService.updateById(any(PosOrder.class))).thenReturn(true);
  130. PayMethodSwitchService service = new PayMethodSwitchService(orderService,
  131. omgPaymentRetryService, mock(LinePayService.class), mock(OrderLogHelper.class),
  132. mock(IInfoUserService.class));
  133. AjaxResult result = service.switchMethod(7L, request("dd-1", "2"));
  134. // 顺序约束:先回写 payType 再拉起渠道;走 retry 路径:旧 Apple Pay 尝试仍激活时
  135. // 替换重建而非 PAYMENT_ATTEMPT_EXISTS 死锁(组内 5->2 切换线上事故场景)
  136. InOrder inOrder = inOrder(orderService, omgPaymentRetryService);
  137. inOrder.verify(orderService).updateById(any(PosOrder.class));
  138. inOrder.verify(omgPaymentRetryService).retry(eq(7L), eq("dd-1"), eq(OmgPaymentMethod.CREDIT));
  139. Map<?, ?> data = (Map<?, ?>) result.get("data");
  140. assertEquals("2", data.get("payType"));
  141. assertNotNull(data.get("payParams"), "在线方式应带渠道支付参数");
  142. }
  143. @Test
  144. void sameMethodSkipsRewriteButStillDelegates() {
  145. IPosOrderService orderService = mock(IPosOrderService.class);
  146. OmgPaymentRetryService omgPaymentRetryService = mock(OmgPaymentRetryService.class);
  147. when(omgPaymentRetryService.retry(any(), anyString(), any(OmgPaymentMethod.class)))
  148. .thenReturn(new OmgPaymentCreateOutcome(mock(OmgCreatePaymentResponse.class), 1L, "dd-1", 7L, 5L, 90, "abcde***fghi"));
  149. PosOrder order = baseOrder();
  150. order.setPayType("2");
  151. when(orderService.getOne(any(Wrapper.class))).thenReturn(order);
  152. PayMethodSwitchService service = new PayMethodSwitchService(orderService,
  153. omgPaymentRetryService, mock(LinePayService.class), mock(OrderLogHelper.class),
  154. mock(IInfoUserService.class));
  155. service.switchMethod(7L, request("dd-1", "2"));
  156. verify(orderService, never()).updateById(any(PosOrder.class));
  157. verify(omgPaymentRetryService, times(1)).retry(eq(7L), eq("dd-1"), eq(OmgPaymentMethod.CREDIT));
  158. }
  159. @Test
  160. void alreadyPaidOrderIsRejected() {
  161. IPosOrderService orderService = mock(IPosOrderService.class);
  162. PosOrder order = baseOrder();
  163. order.setPayStatus(1L);
  164. when(orderService.getOne(any(Wrapper.class))).thenReturn(order);
  165. PayMethodSwitchService service = new PayMethodSwitchService(orderService,
  166. mock(OmgPaymentRetryService.class), mock(LinePayService.class),
  167. mock(OrderLogHelper.class), mock(IInfoUserService.class));
  168. ServiceException exception = assertThrows(ServiceException.class,
  169. () -> service.switchMethod(7L, request("dd-1", "2")));
  170. assertEquals("no.pay.switch.already.paid", exception.getMessage());
  171. }
  172. @Test
  173. void unknownMethodIsRejectedBeforeOrderLookup() {
  174. IPosOrderService orderService = mock(IPosOrderService.class);
  175. PayMethodSwitchService service = new PayMethodSwitchService(orderService,
  176. mock(OmgPaymentRetryService.class), mock(LinePayService.class),
  177. mock(OrderLogHelper.class), mock(IInfoUserService.class));
  178. ServiceException exception = assertThrows(ServiceException.class,
  179. () -> service.switchMethod(7L, request("dd-1", "9")));
  180. assertEquals("no.pay.switch.method.invalid", exception.getMessage());
  181. verify(orderService, never()).getOne(any(Wrapper.class));
  182. }
  183. @Test
  184. void orderOfAnotherUserIsRejected() {
  185. IPosOrderService orderService = mock(IPosOrderService.class);
  186. PosOrder order = baseOrder();
  187. order.setUserId(8L);
  188. when(orderService.getOne(any(Wrapper.class))).thenReturn(order);
  189. PayMethodSwitchService service = new PayMethodSwitchService(orderService,
  190. mock(OmgPaymentRetryService.class), mock(LinePayService.class),
  191. mock(OrderLogHelper.class), mock(IInfoUserService.class));
  192. ServiceException exception = assertThrows(ServiceException.class,
  193. () -> service.switchMethod(7L, request("dd-1", "2")));
  194. assertEquals("no.pay.switch.order.invalid", exception.getMessage());
  195. }
  196. @Test
  197. void multiStoreSubOrderIsRejected() {
  198. IPosOrderService orderService = mock(IPosOrderService.class);
  199. PosOrder order = baseOrder();
  200. order.setParentDdId("dd-parent");
  201. when(orderService.getOne(any(Wrapper.class))).thenReturn(order);
  202. PayMethodSwitchService service = new PayMethodSwitchService(orderService,
  203. mock(OmgPaymentRetryService.class), mock(LinePayService.class),
  204. mock(OrderLogHelper.class), mock(IInfoUserService.class));
  205. ServiceException exception = assertThrows(ServiceException.class,
  206. () -> service.switchMethod(7L, request("dd-1", "2")));
  207. assertEquals("no.pay.switch.order.invalid", exception.getMessage());
  208. }
  209. }