|
|
@@ -0,0 +1,447 @@
|
|
|
+package com.payment.platform.module.payment.unified.controller.open;
|
|
|
+
|
|
|
+import com.fasterxml.jackson.databind.JsonNode;
|
|
|
+import com.fasterxml.jackson.databind.ObjectMapper;
|
|
|
+import com.payment.platform.common.config.JacksonConfig;
|
|
|
+import com.payment.platform.common.exception.BusinessException;
|
|
|
+import com.payment.platform.common.response.Result;
|
|
|
+import com.payment.platform.core.security.TenantApiKeyAuthFilter;
|
|
|
+import com.payment.platform.module.payment.unified.dto.req.UnifiedChannelUserJumpCommand;
|
|
|
+import com.payment.platform.module.payment.unified.dto.req.UnifiedOrderCreateCommand;
|
|
|
+import com.payment.platform.module.payment.unified.dto.req.UnifiedOrderQueryRequest;
|
|
|
+import com.payment.platform.module.payment.unified.dto.req.UnifiedRefundCreateCommand;
|
|
|
+import com.payment.platform.module.payment.unified.dto.req.UnifiedRefundQueryRequest;
|
|
|
+import com.payment.platform.module.payment.unified.dto.vo.UnifiedChannelUserJumpVO;
|
|
|
+import com.payment.platform.module.payment.unified.dto.vo.UnifiedOrderVO;
|
|
|
+import com.payment.platform.module.payment.unified.dto.vo.UnifiedRefundVO;
|
|
|
+import com.payment.platform.module.payment.unified.entity.UnifiedOrderEntity;
|
|
|
+import com.payment.platform.module.payment.unified.entity.UnifiedRefundEntity;
|
|
|
+import com.payment.platform.module.payment.unified.enums.UnifiedSource;
|
|
|
+import com.payment.platform.module.payment.unified.service.UnifiedChannelUserService;
|
|
|
+import com.payment.platform.module.payment.unified.service.UnifiedOrderService;
|
|
|
+import com.payment.platform.module.payment.unified.service.UnifiedRefundService;
|
|
|
+import org.junit.jupiter.api.Test;
|
|
|
+import org.junit.jupiter.api.extension.ExtendWith;
|
|
|
+import org.junit.jupiter.api.function.Executable;
|
|
|
+import org.mockito.ArgumentCaptor;
|
|
|
+import org.mockito.Mock;
|
|
|
+import org.mockito.junit.jupiter.MockitoExtension;
|
|
|
+import org.springframework.mock.web.MockHttpServletRequest;
|
|
|
+import org.springframework.web.bind.annotation.PostMapping;
|
|
|
+import org.springframework.web.bind.annotation.RequestMapping;
|
|
|
+
|
|
|
+import java.lang.reflect.Field;
|
|
|
+import java.lang.reflect.Method;
|
|
|
+import java.util.Arrays;
|
|
|
+import java.util.List;
|
|
|
+import java.util.Set;
|
|
|
+import java.util.stream.Collectors;
|
|
|
+
|
|
|
+import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
|
+import static org.junit.jupiter.api.Assertions.assertFalse;
|
|
|
+import static org.junit.jupiter.api.Assertions.assertNotNull;
|
|
|
+import static org.junit.jupiter.api.Assertions.assertNull;
|
|
|
+import static org.junit.jupiter.api.Assertions.assertSame;
|
|
|
+import static org.junit.jupiter.api.Assertions.assertThrows;
|
|
|
+import static org.junit.jupiter.api.Assertions.assertTrue;
|
|
|
+import static org.mockito.ArgumentMatchers.any;
|
|
|
+import static org.mockito.ArgumentMatchers.anyLong;
|
|
|
+import static org.mockito.ArgumentMatchers.eq;
|
|
|
+import static org.mockito.Mockito.never;
|
|
|
+import static org.mockito.Mockito.verify;
|
|
|
+import static org.mockito.Mockito.verifyNoInteractions;
|
|
|
+import static org.mockito.Mockito.when;
|
|
|
+
|
|
|
+/**
|
|
|
+ * 统一支付开放 API 控制器(Task 15)。
|
|
|
+ *
|
|
|
+ * <p>直调方法,角色由 {@link MockHttpServletRequest} 上由认证 filter 注入的两个属性承担:
|
|
|
+ * {@code openapi.tenantId} 与 {@code openapi.apiKey}。本类只钉控制器自己的职责:
|
|
|
+ * <ol>
|
|
|
+ * <li>租户上下文缺失时**先于任何服务调用**返 401;</li>
|
|
|
+ * <li>下单时 {@code clientIp} 只在调用方没传时补 {@code request.getRemoteAddr()};</li>
|
|
|
+ * <li>查询以 {@code orderNo}/{@code refundNo} 优先,且租户条件显式传给服务;</li>
|
|
|
+ * <li>开放视图不暴露内部 {@code id}(序列化后按字段名断言,不做子串匹配);</li>
|
|
|
+ * <li>5 条映射与 {@code TenantApiKeyAuthFilter.OPENAPI_ACCOUNT_PATHS} 逐字一致 ——
|
|
|
+ * 两边都反射读真实注解/数组,不在测试里抄一份路径清单。</li>
|
|
|
+ * </ol>
|
|
|
+ */
|
|
|
+@ExtendWith(MockitoExtension.class)
|
|
|
+class UnifiedOpenControllerTest {
|
|
|
+
|
|
|
+ private static final String PREFIX = "/payment/openapi/unified";
|
|
|
+ /** 全局 mapper(与 Web 层同一个 {@link JacksonConfig}),用于钉「开放视图没有 id」。 */
|
|
|
+ private static final ObjectMapper GLOBAL_MAPPER = new JacksonConfig().objectMapper();
|
|
|
+
|
|
|
+ @Mock private UnifiedOrderService orderService;
|
|
|
+ @Mock private UnifiedRefundService refundService;
|
|
|
+ @Mock private UnifiedChannelUserService channelUserService;
|
|
|
+
|
|
|
+ // ────────────────────────────── 认证上下文 ──────────────────────────────
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 五个端点共用同一道守卫:没有 {@code openapi.tenantId} 就 401,且一次服务都不许调。
|
|
|
+ *
|
|
|
+ * <p>逐个端点跑,确保将来某个方法漏掉守卫会立刻变红(而不是被别的方法代跑)。
|
|
|
+ */
|
|
|
+ @Test
|
|
|
+ void allFiveEndpoints_withoutOpenapiTenant_throw401BeforeAnyServiceCall() {
|
|
|
+ UnifiedOpenController controller = controller(request(null, null));
|
|
|
+
|
|
|
+ for (Executable call : List.<Executable>of(
|
|
|
+ () -> controller.orderCreate(createCmd("T1")),
|
|
|
+ () -> controller.orderQuery(orderQuery("UO1", null)),
|
|
|
+ () -> controller.refund(refundCmd()),
|
|
|
+ () -> controller.refundQuery(refundQuery("UR1", null)),
|
|
|
+ () -> controller.channelUserJumpUrl(jumpCmd()))) {
|
|
|
+ BusinessException ex = assertThrows(BusinessException.class, call);
|
|
|
+ assertEquals(401, ex.getCode(), "无 API Key 上下文时必须 401");
|
|
|
+ }
|
|
|
+
|
|
|
+ verifyNoInteractions(orderService, refundService, channelUserService);
|
|
|
+ }
|
|
|
+
|
|
|
+ // ────────────────────────────── 下单 ──────────────────────────────
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void create_blankClientIp_filledFromRemoteAddr_andPassesTenantApiKeySource() {
|
|
|
+ MockHttpServletRequest request = request(2L, "key-2");
|
|
|
+ request.setRemoteAddr("203.0.113.9");
|
|
|
+ UnifiedOpenController controller = controller(request);
|
|
|
+
|
|
|
+ UnifiedOrderCreateCommand cmd = createCmd("T1");
|
|
|
+ cmd.setClientIp(" ");
|
|
|
+ UnifiedOrderEntity saved = order(11L, 2L, "UO1", "T1");
|
|
|
+ when(orderService.create(any(), eq(2L), eq("key-2"), eq(UnifiedSource.OPENAPI)))
|
|
|
+ .thenReturn(saved);
|
|
|
+ when(orderService.refundableAmount(saved)).thenReturn(900L);
|
|
|
+
|
|
|
+ Result<UnifiedOrderVO> result = controller.orderCreate(cmd);
|
|
|
+
|
|
|
+ ArgumentCaptor<UnifiedOrderCreateCommand> captor =
|
|
|
+ ArgumentCaptor.forClass(UnifiedOrderCreateCommand.class);
|
|
|
+ verify(orderService).create(captor.capture(), eq(2L), eq("key-2"), eq(UnifiedSource.OPENAPI));
|
|
|
+ assertEquals("203.0.113.9", captor.getValue().getClientIp(),
|
|
|
+ "调用方未传 clientIp 时必须补 request.getRemoteAddr()");
|
|
|
+ assertEquals(0, result.getCode());
|
|
|
+ assertNotNull(result.getData());
|
|
|
+ assertEquals("UO1", result.getData().getOrderNo());
|
|
|
+ assertEquals(900L, result.getData().getRefundableAmount(),
|
|
|
+ "开放视图必须带服务层算出的可退金额");
|
|
|
+ assertNull(result.getData().getId(), "开放视图不得暴露内部主键");
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void create_explicitClientIp_isNotOverwritten() {
|
|
|
+ MockHttpServletRequest request = request(2L, "key-2");
|
|
|
+ request.setRemoteAddr("203.0.113.9");
|
|
|
+ UnifiedOpenController controller = controller(request);
|
|
|
+
|
|
|
+ UnifiedOrderCreateCommand cmd = createCmd("T1");
|
|
|
+ cmd.setClientIp("198.51.100.7");
|
|
|
+ when(orderService.create(any(), eq(2L), eq("key-2"), eq(UnifiedSource.OPENAPI)))
|
|
|
+ .thenReturn(order(11L, 2L, "UO1", "T1"));
|
|
|
+
|
|
|
+ controller.orderCreate(cmd);
|
|
|
+
|
|
|
+ ArgumentCaptor<UnifiedOrderCreateCommand> captor =
|
|
|
+ ArgumentCaptor.forClass(UnifiedOrderCreateCommand.class);
|
|
|
+ verify(orderService).create(captor.capture(), eq(2L), eq("key-2"), eq(UnifiedSource.OPENAPI));
|
|
|
+ assertEquals("198.51.100.7", captor.getValue().getClientIp(),
|
|
|
+ "调用方已传 clientIp 时不得用 remoteAddr 覆盖");
|
|
|
+ }
|
|
|
+
|
|
|
+ // ────────────────────────────── 查单 ──────────────────────────────
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void orderQuery_orderNoWins_overOutTradeNo() {
|
|
|
+ UnifiedOpenController controller = controller(request(2L, "key-2"));
|
|
|
+ UnifiedOrderEntity row = order(11L, 2L, "UO1", "T1");
|
|
|
+ when(orderService.findByOrderNo(2L, "UO1")).thenReturn(row);
|
|
|
+ when(orderService.refundableAmount(row)).thenReturn(500L);
|
|
|
+
|
|
|
+ Result<UnifiedOrderVO> result = controller.orderQuery(orderQuery("UO1", "T1"));
|
|
|
+
|
|
|
+ verify(orderService).findByOrderNo(2L, "UO1");
|
|
|
+ verify(orderService, never()).findByOutTradeNo(any(), any());
|
|
|
+ assertEquals("UO1", result.getData().getOrderNo());
|
|
|
+ assertEquals(500L, result.getData().getRefundableAmount());
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void orderQuery_fallsBackToOutTradeNo_whenOrderNoBlank() {
|
|
|
+ UnifiedOpenController controller = controller(request(2L, "key-2"));
|
|
|
+ UnifiedOrderEntity row = order(11L, 2L, "UO1", "T1");
|
|
|
+ when(orderService.findByOutTradeNo(2L, "T1")).thenReturn(row);
|
|
|
+
|
|
|
+ Result<UnifiedOrderVO> result = controller.orderQuery(orderQuery(" ", "T1"));
|
|
|
+
|
|
|
+ verify(orderService).findByOutTradeNo(2L, "T1");
|
|
|
+ verify(orderService, never()).findByOrderNo(any(), any());
|
|
|
+ assertEquals("T1", result.getData().getOutTradeNo());
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void orderQuery_unknown_is404_notDistinguishedByTenant() {
|
|
|
+ UnifiedOpenController controller = controller(request(2L, "key-2"));
|
|
|
+ when(orderService.findByOrderNo(2L, "UO-NOPE")).thenReturn(null);
|
|
|
+
|
|
|
+ BusinessException ex = assertThrows(BusinessException.class,
|
|
|
+ () -> controller.orderQuery(orderQuery("UO-NOPE", null)));
|
|
|
+ assertEquals(404, ex.getCode());
|
|
|
+ }
|
|
|
+
|
|
|
+ // ────────────────────────────── 退款 ──────────────────────────────
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void refund_callsCreateRefundWithOpenapiSource() {
|
|
|
+ UnifiedOpenController controller = controller(request(2L, "key-2"));
|
|
|
+ UnifiedRefundCreateCommand cmd = refundCmd();
|
|
|
+ UnifiedRefundEntity refund = refund(31L, "UR1", "UO1");
|
|
|
+ when(refundService.createRefund(any(), eq(2L), eq("key-2"), eq(UnifiedSource.OPENAPI)))
|
|
|
+ .thenReturn(refund);
|
|
|
+
|
|
|
+ Result<UnifiedRefundVO> result = controller.refund(cmd);
|
|
|
+
|
|
|
+ ArgumentCaptor<UnifiedRefundCreateCommand> captor =
|
|
|
+ ArgumentCaptor.forClass(UnifiedRefundCreateCommand.class);
|
|
|
+ verify(refundService).createRefund(captor.capture(), eq(2L), eq("key-2"),
|
|
|
+ eq(UnifiedSource.OPENAPI));
|
|
|
+ assertSame(cmd, captor.getValue(), "请求体必须原样交给服务层校验");
|
|
|
+ assertEquals("UR1", result.getData().getRefundNo());
|
|
|
+ assertNull(result.getData().getId(), "开放视图不得暴露内部主键");
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 缺 {@code outRefundNo} 由**服务层**校验({@code UnifiedRefundService#validateCreate}),
|
|
|
+ * 控制器不吞异常:这里用 mock 让服务抛 400,断言它原样到达调用方。
|
|
|
+ */
|
|
|
+ @Test
|
|
|
+ void refund_serviceRejects400_isPropagated() {
|
|
|
+ UnifiedOpenController controller = controller(request(2L, "key-2"));
|
|
|
+ when(refundService.createRefund(any(), eq(2L), eq("key-2"), eq(UnifiedSource.OPENAPI)))
|
|
|
+ .thenThrow(new BusinessException(400, "out_refund_no 不能为空"));
|
|
|
+
|
|
|
+ BusinessException ex = assertThrows(BusinessException.class,
|
|
|
+ () -> controller.refund(refundCmd()));
|
|
|
+ assertEquals(400, ex.getCode());
|
|
|
+ }
|
|
|
+
|
|
|
+ // ────────────────────────────── 退款查询 ──────────────────────────────
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void refundQuery_refundNoWins_overOutRefundNo() {
|
|
|
+ UnifiedOpenController controller = controller(request(2L, "key-2"));
|
|
|
+ when(refundService.findByRefundNo(2L, "UR1")).thenReturn(refund(31L, "UR1", "UO1"));
|
|
|
+
|
|
|
+ Result<UnifiedRefundVO> result = controller.refundQuery(refundQuery("UR1", "RF1"));
|
|
|
+
|
|
|
+ verify(refundService).findByRefundNo(2L, "UR1");
|
|
|
+ verify(refundService, never()).findByOutRefundNo(any(), any());
|
|
|
+ assertEquals("UR1", result.getData().getRefundNo());
|
|
|
+ assertNull(result.getData().getId());
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void refundQuery_fallsBackToOutRefundNo_whenRefundNoBlank() {
|
|
|
+ UnifiedOpenController controller = controller(request(2L, "key-2"));
|
|
|
+ UnifiedRefundEntity row = refund(31L, "UR1", "UO1");
|
|
|
+ row.setOutRefundNo("RF1");
|
|
|
+ when(refundService.findByOutRefundNo(2L, "RF1")).thenReturn(row);
|
|
|
+
|
|
|
+ Result<UnifiedRefundVO> result = controller.refundQuery(refundQuery(null, "RF1"));
|
|
|
+
|
|
|
+ verify(refundService).findByOutRefundNo(2L, "RF1");
|
|
|
+ verify(refundService, never()).findByRefundNo(any(), any());
|
|
|
+ assertEquals("RF1", result.getData().getOutRefundNo());
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void refundQuery_unknown_is404() {
|
|
|
+ UnifiedOpenController controller = controller(request(2L, "key-2"));
|
|
|
+ when(refundService.findByRefundNo(2L, "UR-NOPE")).thenReturn(null);
|
|
|
+
|
|
|
+ BusinessException ex = assertThrows(BusinessException.class,
|
|
|
+ () -> controller.refundQuery(refundQuery("UR-NOPE", null)));
|
|
|
+ assertEquals(404, ex.getCode());
|
|
|
+ }
|
|
|
+
|
|
|
+ // ────────────────────────────── 渠道用户跳转 ──────────────────────────────
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void channelUserJump_returnsUrlFromService() {
|
|
|
+ UnifiedOpenController controller = controller(request(2L, "key-2"));
|
|
|
+ UnifiedChannelUserJumpCommand cmd = jumpCmd();
|
|
|
+ UnifiedChannelUserJumpVO vo = new UnifiedChannelUserJumpVO();
|
|
|
+ vo.setUrl("https://gw.example.test/jump?sign=x");
|
|
|
+ when(channelUserService.build(2L, cmd)).thenReturn(vo);
|
|
|
+
|
|
|
+ Result<UnifiedChannelUserJumpVO> result = controller.channelUserJumpUrl(cmd);
|
|
|
+
|
|
|
+ verify(channelUserService).build(2L, cmd);
|
|
|
+ assertEquals("https://gw.example.test/jump?sign=x", result.getData().getUrl());
|
|
|
+ }
|
|
|
+
|
|
|
+ // ────────────────────────────── 开放视图 ──────────────────────────────
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void openOrderView_serializedWithGlobalMapper_hasNoInternalId() throws Exception {
|
|
|
+ UnifiedOrderEntity entity = order(99001L, 7L, "UO-X", "T-X");
|
|
|
+
|
|
|
+ JsonNode node = GLOBAL_MAPPER.readTree(
|
|
|
+ GLOBAL_MAPPER.writeValueAsString(UnifiedOrderVO.open(entity, 500L)));
|
|
|
+
|
|
|
+ assertFalse(node.has("id"), "开放订单视图不得含内部主键 id,读数: " + node);
|
|
|
+ assertFalse(node.has("tenant_id"), "开放订单视图不得含租户归属,读数: " + node);
|
|
|
+ // 正对照:序列化确实发生了(否则「没有 id」可能只是空对象)
|
|
|
+ assertTrue(node.has("order_no"), "序列化结果缺 order_no,量具无效: " + node);
|
|
|
+ assertEquals(500L, node.get("refundable_amount").asLong());
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void openRefundView_serializedWithGlobalMapper_hasNoInternalId() throws Exception {
|
|
|
+ UnifiedRefundEntity entity = refund(99002L, "UR-X", "UO-X");
|
|
|
+
|
|
|
+ JsonNode node = GLOBAL_MAPPER.readTree(
|
|
|
+ GLOBAL_MAPPER.writeValueAsString(UnifiedRefundVO.open(entity)));
|
|
|
+
|
|
|
+ assertFalse(node.has("id"), "开放退款视图不得含内部主键 id,读数: " + node);
|
|
|
+ // 正对照
|
|
|
+ assertTrue(node.has("refund_no"), "序列化结果缺 refund_no,量具无效: " + node);
|
|
|
+ assertEquals("UR-X", node.get("refund_no").asText());
|
|
|
+ }
|
|
|
+
|
|
|
+ // ────────────────────────────── 认证清单接线 ──────────────────────────────
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 两边都从源码里读:控制器注解拼出的路径集合 vs filter 数组中 unified 子集。
|
|
|
+ * 任何一边漏改都会红,且不需要在测试里维护第三份路径清单。
|
|
|
+ */
|
|
|
+ @Test
|
|
|
+ void fiveMappings_matchOpenapiAccountPathsExactly() throws Exception {
|
|
|
+ String prefix = UnifiedOpenController.class.getAnnotation(RequestMapping.class).value()[0];
|
|
|
+ assertEquals(PREFIX, prefix);
|
|
|
+
|
|
|
+ Set<String> mapped = Arrays.stream(UnifiedOpenController.class.getDeclaredMethods())
|
|
|
+ .filter(m -> m.isAnnotationPresent(PostMapping.class))
|
|
|
+ .map(UnifiedOpenControllerTest::mappedPath)
|
|
|
+ .collect(Collectors.toSet());
|
|
|
+
|
|
|
+ assertEquals(Set.of(
|
|
|
+ PREFIX + "/order/create",
|
|
|
+ PREFIX + "/order/query",
|
|
|
+ PREFIX + "/refund",
|
|
|
+ PREFIX + "/refund/query",
|
|
|
+ PREFIX + "/channel-user/jump-url"),
|
|
|
+ mapped,
|
|
|
+ "控制器映射与 Task 15 契约不符。读数: " + mapped);
|
|
|
+
|
|
|
+ List<String> paths = Arrays.asList(
|
|
|
+ readStringArray(TenantApiKeyAuthFilter.class, "OPENAPI_ACCOUNT_PATHS"));
|
|
|
+ Set<String> filtered = paths.stream()
|
|
|
+ .filter(p -> p.startsWith(PREFIX + "/"))
|
|
|
+ .collect(Collectors.toSet());
|
|
|
+ assertEquals(mapped, filtered,
|
|
|
+ "控制器映射与 OPENAPI_ACCOUNT_PATHS 的 unified 子集必须逐字一致。读数: " + filtered);
|
|
|
+ // 正对照:既有 alipay 端点必须仍在数组中,证明读到的是真数组
|
|
|
+ assertTrue(paths.contains("/payment/openapi/alipay/order/create"),
|
|
|
+ "既有 alipay 端点缺失,说明量具读错了数组。读数: " + paths);
|
|
|
+ }
|
|
|
+
|
|
|
+ // ────────────────────────────── 工具 ──────────────────────────────
|
|
|
+
|
|
|
+ private UnifiedOpenController controller(MockHttpServletRequest request) {
|
|
|
+ return new UnifiedOpenController(orderService, refundService, channelUserService, request);
|
|
|
+ }
|
|
|
+
|
|
|
+ private static MockHttpServletRequest request(Long tenantId, String apiKey) {
|
|
|
+ MockHttpServletRequest request = new MockHttpServletRequest("POST", PREFIX + "/order/create");
|
|
|
+ request.setContextPath("/api/v1");
|
|
|
+ request.setServletPath(PREFIX + "/order/create");
|
|
|
+ request.setRequestURI("/api/v1" + PREFIX + "/order/create");
|
|
|
+ // 容器形状:servletPath ≠ requestURI,属性访问不受影响,但保持与线上一致
|
|
|
+ if (tenantId != null) {
|
|
|
+ request.setAttribute("openapi.tenantId", tenantId);
|
|
|
+ }
|
|
|
+ if (apiKey != null) {
|
|
|
+ request.setAttribute("openapi.apiKey", apiKey);
|
|
|
+ }
|
|
|
+ return request;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static UnifiedOrderCreateCommand createCmd(String outTradeNo) {
|
|
|
+ UnifiedOrderCreateCommand cmd = new UnifiedOrderCreateCommand();
|
|
|
+ cmd.setAmount(100L);
|
|
|
+ cmd.setWayCode("QR_CASHIER");
|
|
|
+ cmd.setSubject("测试订单");
|
|
|
+ cmd.setOutTradeNo(outTradeNo);
|
|
|
+ return cmd;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static UnifiedOrderQueryRequest orderQuery(String orderNo, String outTradeNo) {
|
|
|
+ UnifiedOrderQueryRequest query = new UnifiedOrderQueryRequest();
|
|
|
+ query.setOrderNo(orderNo);
|
|
|
+ query.setOutTradeNo(outTradeNo);
|
|
|
+ return query;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static UnifiedRefundCreateCommand refundCmd() {
|
|
|
+ UnifiedRefundCreateCommand cmd = new UnifiedRefundCreateCommand();
|
|
|
+ cmd.setOrderNo("UO1");
|
|
|
+ cmd.setRefundAmount(100L);
|
|
|
+ cmd.setRefundReason("测试退款");
|
|
|
+ cmd.setOutRefundNo("RF1");
|
|
|
+ return cmd;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static UnifiedRefundQueryRequest refundQuery(String refundNo, String outRefundNo) {
|
|
|
+ UnifiedRefundQueryRequest query = new UnifiedRefundQueryRequest();
|
|
|
+ query.setRefundNo(refundNo);
|
|
|
+ query.setOutRefundNo(outRefundNo);
|
|
|
+ return query;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static UnifiedChannelUserJumpCommand jumpCmd() {
|
|
|
+ UnifiedChannelUserJumpCommand cmd = new UnifiedChannelUserJumpCommand();
|
|
|
+ cmd.setRedirectUrl("https://merchant.example.test/cb");
|
|
|
+ return cmd;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static UnifiedOrderEntity order(Long id, Long tenantId, String orderNo,
|
|
|
+ String outTradeNo) {
|
|
|
+ UnifiedOrderEntity entity = new UnifiedOrderEntity();
|
|
|
+ entity.setId(id);
|
|
|
+ entity.setTenantId(tenantId);
|
|
|
+ entity.setOrderNo(orderNo);
|
|
|
+ entity.setOutTradeNo(outTradeNo);
|
|
|
+ entity.setMchNo("M001");
|
|
|
+ entity.setWayCode("QR_CASHIER");
|
|
|
+ entity.setAmount(1000L);
|
|
|
+ entity.setRefundedAmount(0L);
|
|
|
+ entity.setTradeStatus("2");
|
|
|
+ return entity;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static UnifiedRefundEntity refund(Long id, String refundNo, String orderNo) {
|
|
|
+ UnifiedRefundEntity entity = new UnifiedRefundEntity();
|
|
|
+ entity.setId(id);
|
|
|
+ entity.setRefundNo(refundNo);
|
|
|
+ entity.setOutRefundNo("RF-" + refundNo);
|
|
|
+ entity.setOrderNo(orderNo);
|
|
|
+ entity.setMchNo("M001");
|
|
|
+ entity.setAmount(100L);
|
|
|
+ entity.setState("2");
|
|
|
+ return entity;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static String mappedPath(Method method) {
|
|
|
+ String classPrefix = UnifiedOpenController.class.getAnnotation(RequestMapping.class).value()[0];
|
|
|
+ return classPrefix + method.getAnnotation(PostMapping.class).value()[0];
|
|
|
+ }
|
|
|
+
|
|
|
+ private static String[] readStringArray(Class<?> owner, String fieldName) throws Exception {
|
|
|
+ Field field = owner.getDeclaredField(fieldName);
|
|
|
+ field.setAccessible(true);
|
|
|
+ return (String[]) field.get(null);
|
|
|
+ }
|
|
|
+}
|