|
@@ -0,0 +1,108 @@
|
|
|
|
|
+package com.malk.minglei.controller;
|
|
|
|
|
+
|
|
|
|
|
+import cn.hutool.core.util.HexUtil;
|
|
|
|
|
+import cn.hutool.crypto.SmUtil;
|
|
|
|
|
+import cn.hutool.crypto.asymmetric.KeyType;
|
|
|
|
|
+import cn.hutool.crypto.asymmetric.SM2;
|
|
|
|
|
+import cn.hutool.crypto.symmetric.SM4;
|
|
|
|
|
+import com.fasterxml.jackson.databind.ObjectMapper;
|
|
|
|
|
+import com.malk.minglei.config.BankingProperties;
|
|
|
|
|
+import com.malk.minglei.service.impl.banking.BankingServiceImpl;
|
|
|
|
|
+import org.junit.jupiter.params.ParameterizedTest;
|
|
|
|
|
+import org.junit.jupiter.params.provider.Arguments;
|
|
|
|
|
+import org.junit.jupiter.params.provider.MethodSource;
|
|
|
|
|
+import org.junit.jupiter.params.provider.ValueSource;
|
|
|
|
|
+import org.mockito.ArgumentCaptor;
|
|
|
|
|
+import org.springframework.http.HttpEntity;
|
|
|
|
|
+import org.springframework.http.HttpMethod;
|
|
|
|
|
+import org.springframework.http.HttpStatus;
|
|
|
|
|
+import org.springframework.http.MediaType;
|
|
|
|
|
+import org.springframework.http.ResponseEntity;
|
|
|
|
|
+import org.springframework.test.web.servlet.setup.MockMvcBuilders;
|
|
|
|
|
+import org.springframework.web.client.RestTemplate;
|
|
|
|
|
+
|
|
|
|
|
+import java.net.URI;
|
|
|
|
|
+import java.nio.charset.StandardCharsets;
|
|
|
|
|
+import java.util.Map;
|
|
|
|
|
+import java.util.stream.Stream;
|
|
|
|
|
+
|
|
|
|
|
+import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
|
|
|
+import static org.junit.jupiter.api.Assertions.assertTrue;
|
|
|
|
|
+import static org.mockito.ArgumentMatchers.any;
|
|
|
|
|
+import static org.mockito.ArgumentMatchers.eq;
|
|
|
|
|
+import static org.mockito.Mockito.mock;
|
|
|
|
|
+import static org.mockito.Mockito.verify;
|
|
|
|
|
+import static org.mockito.Mockito.when;
|
|
|
|
|
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
|
|
|
|
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
|
|
|
|
+
|
|
|
|
|
+class BankingPaymentAccountTypeTest {
|
|
|
|
|
+
|
|
|
|
|
+ @ParameterizedTest
|
|
|
|
|
+ @MethodSource("accountTypes")
|
|
|
|
|
+ void forwardsAccountTypeFromHttpRequestInPlainAndEncryptedPayments(String field, String expected, boolean encrypted) throws Exception {
|
|
|
|
|
+ assertEquals(expected, submitAndReadPayload("/api/banking/payments", field, encrypted).get("toAccountType"));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @ParameterizedTest
|
|
|
|
|
+ @ValueSource(strings = {"/api/v2/payment/commonPay", "/api/banking/payments/submit-and-query-status"})
|
|
|
|
|
+ void forwardsPrivateAccountTypeThroughOtherPaymentEntrypoints(String path) throws Exception {
|
|
|
|
|
+ assertEquals("1", submitAndReadPayload(path, ",\"toAccountType\":\"1\"", false).get("toAccountType"));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private static Stream<Arguments> accountTypes() {
|
|
|
|
|
+ return Stream.of(false, true).flatMap(encrypted -> Stream.of(
|
|
|
|
|
+ Arguments.of(",\"toAccountType\":\"0\"", "0", encrypted),
|
|
|
|
|
+ Arguments.of(",\"toAccountType\":\"1\"", "1", encrypted),
|
|
|
|
|
+ Arguments.of("", "0", encrypted),
|
|
|
|
|
+ Arguments.of(",\"toAccountType\":null", "0", encrypted),
|
|
|
|
|
+ Arguments.of(",\"toAccountType\":\"\"", "0", encrypted),
|
|
|
|
|
+ Arguments.of(",\"toAccountType\":\" \"", "0", encrypted)));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @SuppressWarnings({"rawtypes", "unchecked"})
|
|
|
|
|
+ private Map<String, Object> submitAndReadPayload(String path, String field, boolean encrypted) throws Exception {
|
|
|
|
|
+ ObjectMapper mapper = new ObjectMapper();
|
|
|
|
|
+ RestTemplate restTemplate = mock(RestTemplate.class);
|
|
|
|
|
+ BankingProperties properties = new BankingProperties();
|
|
|
|
|
+ properties.setBaseUrl("https://bank.example");
|
|
|
|
|
+ properties.setAppId("test-app");
|
|
|
|
|
+ properties.setAppSecret("test-secret");
|
|
|
|
|
+ properties.setCustomerCode("test-customer");
|
|
|
|
|
+ properties.setIntraBankName("Test Bank");
|
|
|
|
|
+ properties.setPaymentEncryptionEnabled(encrypted);
|
|
|
|
|
+ SM2 bankKey = encrypted ? SmUtil.sm2() : null;
|
|
|
|
|
+ SM2 enterpriseKey = encrypted ? SmUtil.sm2() : null;
|
|
|
|
|
+ if (encrypted) {
|
|
|
|
|
+ properties.setXencioPublicKey(bankKey.getPublicKeyBase64());
|
|
|
|
|
+ properties.setEnterprisePrivateKey(enterpriseKey.getPrivateKeyBase64());
|
|
|
|
|
+ }
|
|
|
|
|
+ when(restTemplate.exchange(any(URI.class), eq(HttpMethod.GET), any(HttpEntity.class), eq(String.class)))
|
|
|
|
|
+ .thenReturn(new ResponseEntity<String>("{\"code\":\"200\",\"data\":{\"bankBranchList\":["
|
|
|
|
|
+ + "{\"name\":\"Test Bank\",\"code\":\"001\"}]}}", HttpStatus.OK));
|
|
|
|
|
+ when(restTemplate.exchange(eq(URI.create("https://bank.example/api/v2/payment/transferStatus?businessTransNo=PAY-TEST")),
|
|
|
|
|
+ eq(HttpMethod.GET), any(HttpEntity.class), eq(String.class)))
|
|
|
|
|
+ .thenReturn(new ResponseEntity<String>("{\"code\":\"200\",\"data\":{\"transferStatus\":\"X5555\"}}", HttpStatus.OK));
|
|
|
|
|
+ when(restTemplate.exchange(eq("https://bank.example/api/v2/payment/commonPay"), eq(HttpMethod.POST),
|
|
|
|
|
+ any(HttpEntity.class), eq(String.class)))
|
|
|
|
|
+ .thenReturn(new ResponseEntity<String>("{\"code\":\"200\"}", HttpStatus.OK));
|
|
|
|
|
+ BankingServiceImpl service = new BankingServiceImpl(restTemplate, properties, mapper);
|
|
|
|
|
+ String request = "{\"businessTransNo\":\"PAY-TEST\",\"payAmount\":1.00,"
|
|
|
|
|
+ + "\"fromAccountNo\":\"from-account\",\"fromAccountName\":\"from-name\","
|
|
|
|
|
+ + "\"toAccountNo\":\"to-account\",\"toAccountName\":\"to-name\",\"toBankName\":\"Test Bank\"" + field + "}";
|
|
|
|
|
+ MockMvcBuilders.standaloneSetup(new BankingController(service, properties)).build()
|
|
|
|
|
+ .perform(post(path).contentType(MediaType.APPLICATION_JSON).content(request))
|
|
|
|
|
+ .andExpect(status().isOk());
|
|
|
|
|
+ ArgumentCaptor<HttpEntity> entity = ArgumentCaptor.forClass(HttpEntity.class);
|
|
|
|
|
+ verify(restTemplate).exchange(eq("https://bank.example/api/v2/payment/commonPay"), eq(HttpMethod.POST),
|
|
|
|
|
+ entity.capture(), eq(String.class));
|
|
|
|
|
+ Map<String, Object> payload = (Map<String, Object>) entity.getValue().getBody();
|
|
|
|
|
+ if (encrypted) {
|
|
|
|
|
+ String key = bankKey.decryptStr(entity.getValue().getHeaders().getFirst("xencio-key"), KeyType.PrivateKey);
|
|
|
|
|
+ String source = new SM4(key.getBytes(StandardCharsets.UTF_8)).decryptStr((String) payload.get("reqData"));
|
|
|
|
|
+ assertTrue(enterpriseKey.verify(source.getBytes(StandardCharsets.UTF_8), HexUtil.decodeHex((String) payload.get("sign"))));
|
|
|
|
|
+ payload = mapper.readValue(source, Map.class);
|
|
|
|
|
+ }
|
|
|
|
|
+ return payload;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|