|
|
@@ -0,0 +1,171 @@
|
|
|
+package com.ruoyi.app.order;
|
|
|
+
|
|
|
+import com.ruoyi.app.omgpay.OmgPaymentQueryService;
|
|
|
+import com.ruoyi.app.omgpay.dto.OmgQueryPaymentResponse;
|
|
|
+import com.ruoyi.app.pay.LinePayService;
|
|
|
+import com.ruoyi.common.exception.ServiceException;
|
|
|
+import com.ruoyi.common.utils.MessageUtils;
|
|
|
+import com.ruoyi.system.domain.OrderCheckoutBatch;
|
|
|
+import com.ruoyi.system.domain.OrderCheckoutMember;
|
|
|
+import com.ruoyi.system.domain.PosOrder;
|
|
|
+import com.ruoyi.system.domain.PosOrderLinePayment;
|
|
|
+import com.ruoyi.system.mapper.OrderCheckoutMapper;
|
|
|
+import com.ruoyi.system.mapper.PosOrderMapper;
|
|
|
+import com.ruoyi.system.omgpay.domain.OmgPaymentAttempt;
|
|
|
+import com.ruoyi.system.omgpay.service.IOmgPaymentAttemptService;
|
|
|
+import com.ruoyi.system.service.IPosOrderLinePaymentService;
|
|
|
+import org.junit.jupiter.api.AfterEach;
|
|
|
+import org.junit.jupiter.api.BeforeEach;
|
|
|
+import org.junit.jupiter.api.Test;
|
|
|
+import org.mockito.MockedStatic;
|
|
|
+
|
|
|
+import java.time.LocalDateTime;
|
|
|
+import java.util.List;
|
|
|
+
|
|
|
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
|
|
+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.Mockito.mock;
|
|
|
+import static org.mockito.Mockito.mockStatic;
|
|
|
+import static org.mockito.Mockito.never;
|
|
|
+import static org.mockito.Mockito.verify;
|
|
|
+import static org.mockito.Mockito.when;
|
|
|
+
|
|
|
+class MultiStoreCheckoutCaptureVerifierTest {
|
|
|
+ private static final String ALREADY_PAID = "no.pay.switch.already.paid";
|
|
|
+ private static final String DISPATCHED = "multi.store.checkout.payment.dispatched";
|
|
|
+
|
|
|
+ private MockedStatic<MessageUtils> messages;
|
|
|
+ private final OrderCheckoutMapper checkout = mock(OrderCheckoutMapper.class);
|
|
|
+ private final PosOrderMapper orders = mock(PosOrderMapper.class);
|
|
|
+ private final IPosOrderLinePaymentService linePayments = mock(IPosOrderLinePaymentService.class);
|
|
|
+ private final IOmgPaymentAttemptService omgAttempts = mock(IOmgPaymentAttemptService.class);
|
|
|
+ private final OmgPaymentQueryService omgQueryService = mock(OmgPaymentQueryService.class);
|
|
|
+ private final LinePayService linePayService = mock(LinePayService.class);
|
|
|
+
|
|
|
+ @BeforeEach
|
|
|
+ void setUp() {
|
|
|
+ messages = mockStatic(MessageUtils.class);
|
|
|
+ messages.when(() -> MessageUtils.message(anyString())).thenAnswer(call -> call.getArgument(0));
|
|
|
+ messages.when(() -> MessageUtils.message(anyString(), any())).thenAnswer(call -> call.getArgument(0));
|
|
|
+ }
|
|
|
+
|
|
|
+ @AfterEach
|
|
|
+ void tearDown() {
|
|
|
+ messages.close();
|
|
|
+ }
|
|
|
+
|
|
|
+ private MultiStoreCheckoutCaptureVerifier subject() {
|
|
|
+ return new MultiStoreCheckoutCaptureVerifier(checkout, orders, linePayments,
|
|
|
+ omgAttempts, omgQueryService, linePayService);
|
|
|
+ }
|
|
|
+
|
|
|
+ private PosOrderLinePayment linePayment(String status) {
|
|
|
+ PosOrderLinePayment payment = new PosOrderLinePayment();
|
|
|
+ payment.setStatus(status);
|
|
|
+ return payment;
|
|
|
+ }
|
|
|
+
|
|
|
+ private OmgQueryPaymentResponse omgStatus(String status) {
|
|
|
+ return new OmgQueryPaymentResponse(status, null, null, null, 0, null, null, null, null, null);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void linePaidOrCapturedStopsWithAlreadyPaid() {
|
|
|
+ when(linePayments.getByDdId("DD1")).thenReturn(List.of(linePayment("PAID")));
|
|
|
+ assertEquals(ALREADY_PAID, assertThrows(ServiceException.class,
|
|
|
+ () -> subject().verifyNoCapturedPayment("DD1", "3")).getMessage());
|
|
|
+
|
|
|
+ when(linePayments.getByDdId("DD1")).thenReturn(List.of(linePayment("PENDING")));
|
|
|
+ when(linePayService.isCapturedForSwitch(any(PosOrderLinePayment.class))).thenReturn(true);
|
|
|
+ assertEquals(ALREADY_PAID, assertThrows(ServiceException.class,
|
|
|
+ () -> subject().verifyNoCapturedPayment("DD1", "3")).getMessage());
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void lineManualReviewOrInconclusiveQueryKeepsDispatchedRefusal() {
|
|
|
+ when(linePayments.getByDdId("DD1")).thenReturn(List.of(linePayment("MANUAL_REVIEW")));
|
|
|
+ assertEquals(DISPATCHED, assertThrows(ServiceException.class,
|
|
|
+ () -> subject().verifyNoCapturedPayment("DD1", "3")).getMessage());
|
|
|
+
|
|
|
+ when(linePayments.getByDdId("DD1")).thenReturn(List.of(linePayment("PENDING")));
|
|
|
+ when(linePayService.isCapturedForSwitch(any(PosOrderLinePayment.class)))
|
|
|
+ .thenThrow(new IllegalStateException("credential missing"));
|
|
|
+ assertEquals(DISPATCHED, assertThrows(ServiceException.class,
|
|
|
+ () -> subject().verifyNoCapturedPayment("DD1", "3")).getMessage());
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void lineTerminalUnpaidAndOmgVerifiedUnpaidAllowPassing() {
|
|
|
+ when(linePayments.getByDdId("DD1")).thenReturn(List.of(linePayment("FAILED")));
|
|
|
+ when(linePayService.isCapturedForSwitch(any(PosOrderLinePayment.class))).thenReturn(false);
|
|
|
+ OmgPaymentAttempt created = mock(OmgPaymentAttempt.class);
|
|
|
+ when(omgAttempts.selectActiveCreatedByDdId("DD1")).thenReturn(created);
|
|
|
+ when(omgQueryService.reconcile(created)).thenReturn(omgStatus("UNPAID"));
|
|
|
+ assertDoesNotThrow(() -> subject().verifyNoCapturedPayment("DD1", "2"));
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void omgLocalPaidOrGatewayUnknownStopsSwitch() {
|
|
|
+ when(linePayments.getByDdId("DD1")).thenReturn(List.of());
|
|
|
+ when(omgAttempts.selectPaidByDdId("DD1")).thenReturn(mock(OmgPaymentAttempt.class));
|
|
|
+ assertEquals(ALREADY_PAID, assertThrows(ServiceException.class,
|
|
|
+ () -> subject().verifyNoCapturedPayment("DD1", "2")).getMessage());
|
|
|
+
|
|
|
+ when(omgAttempts.selectPaidByDdId("DD1")).thenReturn(null);
|
|
|
+ OmgPaymentAttempt created = mock(OmgPaymentAttempt.class);
|
|
|
+ when(omgAttempts.selectActiveCreatedByDdId("DD1")).thenReturn(created);
|
|
|
+ when(omgQueryService.reconcile(created)).thenReturn(omgStatus("UNKNOWN"));
|
|
|
+ assertEquals(DISPATCHED, assertThrows(ServiceException.class,
|
|
|
+ () -> subject().verifyNoCapturedPayment("DD1", "2")).getMessage());
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void preLockCheckSkipsGatewayWhenNoInFlightAttempts() {
|
|
|
+ arrangeMember(0L);
|
|
|
+ assertEquals(0, subject().verifyInFlightBeforeLock(99L, 100L, 42L));
|
|
|
+ verify(linePayments, never()).getByDdId(anyString());
|
|
|
+ verify(omgQueryService, never()).reconcile(any(OmgPaymentAttempt.class));
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void preLockCheckVerifiesGatewayAndReturnsVerifiedCount() {
|
|
|
+ arrangeMember(2L);
|
|
|
+ when(linePayments.getByDdId("DD100")).thenReturn(List.of());
|
|
|
+ when(omgAttempts.selectActiveCreatedByDdId("DD100")).thenReturn(null);
|
|
|
+ assertEquals(2, subject().verifyInFlightBeforeLock(99L, 100L, 42L));
|
|
|
+ verify(linePayments).getByDdId("DD100");
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void preLockCheckTreatsForeignBatchOrMissingOrderAsUnverified() {
|
|
|
+ OrderCheckoutBatch batch = new OrderCheckoutBatch();
|
|
|
+ batch.setId(99L); batch.setUserId(42L);
|
|
|
+ when(checkout.selectBatchById(99L)).thenReturn(batch);
|
|
|
+ when(checkout.selectMembersByBatchId(99L)).thenReturn(List.of());
|
|
|
+ assertEquals(0, subject().verifyInFlightBeforeLock(99L, 100L, 42L));
|
|
|
+
|
|
|
+ arrangeMember(1L);
|
|
|
+ when(orders.selectById(100L)).thenReturn(null);
|
|
|
+ assertEquals(0, subject().verifyInFlightBeforeLock(99L, 100L, 42L));
|
|
|
+ verify(linePayments, never()).getByDdId(anyString());
|
|
|
+ }
|
|
|
+
|
|
|
+ private void arrangeMember(long attempts) {
|
|
|
+ OrderCheckoutBatch batch = new OrderCheckoutBatch();
|
|
|
+ batch.setId(99L); batch.setUserId(42L);
|
|
|
+ batch.setCheckoutStatus("PENDING");
|
|
|
+ batch.setOperationDeadlineAt(LocalDateTime.now().plusMinutes(2));
|
|
|
+ when(checkout.selectBatchById(99L)).thenReturn(batch);
|
|
|
+ OrderCheckoutMember member = new OrderCheckoutMember();
|
|
|
+ member.setId(1L); member.setBatchId(99L); member.setPosOrderId(100L);
|
|
|
+ member.setStoreId(1L); member.setPaymentState("ONLINE_PENDING"); member.setPayType("3");
|
|
|
+ when(checkout.selectMembersByBatchId(99L)).thenReturn(List.of(member));
|
|
|
+ when(checkout.countPaymentAttemptsByMemberId(1L)).thenReturn(attempts);
|
|
|
+ PosOrder order = new PosOrder();
|
|
|
+ order.setId(100L); order.setDdId("DD100");
|
|
|
+ when(orders.selectById(100L)).thenReturn(order);
|
|
|
+ }
|
|
|
+}
|