|
|
|
|
@ -3,6 +3,7 @@ package com.yau.digitalrmb.payment.interfaces.rest;
|
|
|
|
|
import com.jayway.jsonpath.JsonPath;
|
|
|
|
|
import com.yau.digitalrmb.shared.wallet.WalletPrivateKeyCipher;
|
|
|
|
|
import com.yau.digitalrmb.institutionidentity.application.InstitutionKeyService;
|
|
|
|
|
import com.yau.digitalrmb.institutionidentity.application.InstitutionKeyPairResult;
|
|
|
|
|
import com.yau.digitalrmb.institutionidentity.application.InstitutionKeySubject;
|
|
|
|
|
import com.yau.digitalrmb.institutionidentity.domain.InstitutionIdentityCryptography;
|
|
|
|
|
import com.yau.digitalrmb.institutionidentity.domain.InstitutionSm2KeyPair;
|
|
|
|
|
@ -25,6 +26,7 @@ import org.springframework.test.web.servlet.MockMvc;
|
|
|
|
|
import org.springframework.test.web.servlet.request.RequestPostProcessor;
|
|
|
|
|
|
|
|
|
|
import java.math.BigDecimal;
|
|
|
|
|
import java.util.Map;
|
|
|
|
|
|
|
|
|
|
import static org.assertj.core.api.Assertions.assertThat;
|
|
|
|
|
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.jwt;
|
|
|
|
|
@ -56,6 +58,9 @@ class PaymentControllerTest {
|
|
|
|
|
@Autowired private WalletPrivateKeyCipher walletCipher;
|
|
|
|
|
@Autowired private InstitutionKeyService keyService;
|
|
|
|
|
@Autowired private WalletRuntimeResetService walletRuntimeResetService;
|
|
|
|
|
private InstitutionSm2KeyPair payerWalletKey;
|
|
|
|
|
private InstitutionKeyPairResult bankKey;
|
|
|
|
|
private InstitutionKeyPairResult centralKey;
|
|
|
|
|
|
|
|
|
|
@BeforeEach
|
|
|
|
|
void seedPrerequisites() {
|
|
|
|
|
@ -77,12 +82,12 @@ class PaymentControllerTest {
|
|
|
|
|
"VALUES (?,?,?,?,?,?,?,'ISSUED',?,1,0,CURRENT_TIMESTAMP,CURRENT_TIMESTAMP,'test','test',FALSE)",
|
|
|
|
|
991002L, "BKCHCNBJ00002", PAYEE_USER, 3001L, 2001L, "20260814090000", "BANK_B", "ORG_PAYMENT_B");
|
|
|
|
|
InstitutionKeySubject subject = new InstitutionKeySubject(PAYER_USER, 3001L, 2001L);
|
|
|
|
|
keyService.commercialBankKey(subject, "test");
|
|
|
|
|
keyService.centralBankKey(subject, "test");
|
|
|
|
|
bankKey = keyService.commercialBankKey(subject, "test");
|
|
|
|
|
centralKey = keyService.centralBankKey(subject, "test");
|
|
|
|
|
InstitutionKeySubject payeeSubject = new InstitutionKeySubject(PAYEE_USER, 3001L, 2001L);
|
|
|
|
|
keyService.commercialBankKey(payeeSubject, "test");
|
|
|
|
|
keyService.centralBankKey(payeeSubject, "test");
|
|
|
|
|
InstitutionSm2KeyPair payerWalletKey = cryptography.generateSm2KeyPair();
|
|
|
|
|
payerWalletKey = cryptography.generateSm2KeyPair();
|
|
|
|
|
InstitutionSm2KeyPair payeeWalletKey = cryptography.generateSm2KeyPair();
|
|
|
|
|
WalletOpeningTestData.insertCompletedWallet(jdbc, 991200L, PAYER_USER, 3001L, 2001L,
|
|
|
|
|
PAYER_WALLET, payerWalletKey.getPrivateKey(), payerWalletKey.getPublicKey(),
|
|
|
|
|
@ -214,6 +219,51 @@ class PaymentControllerTest {
|
|
|
|
|
PAYEE_STORAGE_WALLET)).isEqualTo("ACTIVE");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
void rejectsWrongAlgorithmAndWalletPrivateKeyWithoutAdvancingThePaymentAction() throws Exception {
|
|
|
|
|
mockMvc.perform(get("/api/v1/payments/context").with(payer())).andExpect(status().isOk());
|
|
|
|
|
loadPayerWalletKeyFromStorage();
|
|
|
|
|
assertThat(payerWalletKey.getPrivateKey()).isEqualTo(
|
|
|
|
|
"F1E2D3C4B5A6F7E8D9C0B1A2F3E4D5C6B7A8F9E0D1C2B3A4F5E6D7C8B9A0");
|
|
|
|
|
String created = mockMvc.perform(post("/api/v1/payment/attempts").with(payer()))
|
|
|
|
|
.andExpect(status().isOk()).andReturn().getResponse().getContentAsString();
|
|
|
|
|
String attemptId = JsonPath.read(created, "$.data.attemptId");
|
|
|
|
|
executeAction(attemptId, "01", "refresh-payer", payer());
|
|
|
|
|
executeAction(attemptId, "01", "refresh-payee", payer());
|
|
|
|
|
executeAction(attemptId, "02", "generate-timestamp", payer());
|
|
|
|
|
executeAction(attemptId, "02", "concatenate-payment",
|
|
|
|
|
"{\"amount\":200.00,\"note\":\"校验测试\"}", payer());
|
|
|
|
|
|
|
|
|
|
mockMvc.perform(post("/api/v1/payment/attempts/{id}/steps/02/actions/generate-digest", attemptId)
|
|
|
|
|
.with(payer()).contentType(MediaType.APPLICATION_JSON)
|
|
|
|
|
.content("{\"algorithm\":\"SM2-SIGN\"}"))
|
|
|
|
|
.andExpect(status().isBadRequest())
|
|
|
|
|
.andExpect(jsonPath("$.message").value(org.hamcrest.Matchers.containsString("算法选择不正确")));
|
|
|
|
|
assertThat(actionStatus(attemptId, "02", "generate-digest")).isEqualTo("FAILED");
|
|
|
|
|
assertThat(jdbc.queryForMap("SELECT current_step_code,current_action_code " +
|
|
|
|
|
"FROM training_experiment_attempt WHERE id=?", attemptId))
|
|
|
|
|
.containsEntry("current_step_code", "02")
|
|
|
|
|
.containsEntry("current_action_code", "generate-digest");
|
|
|
|
|
|
|
|
|
|
executeAction(attemptId, "02", "generate-digest", payer());
|
|
|
|
|
assertThat(actionStatus(attemptId, "02", "generate-digest")).isEqualTo("COMPLETED");
|
|
|
|
|
mockMvc.perform(post("/api/v1/payment/attempts/{id}/steps/02/actions/wallet-sign", attemptId)
|
|
|
|
|
.with(payer()).contentType(MediaType.APPLICATION_JSON)
|
|
|
|
|
.content("{\"algorithm\":\"SM2-SIGN\",\"privateKey\":\"00\"}"))
|
|
|
|
|
.andExpect(status().isBadRequest())
|
|
|
|
|
.andExpect(jsonPath("$.message").value(org.hamcrest.Matchers.containsString("密钥不匹配")));
|
|
|
|
|
assertThat(actionStatus(attemptId, "02", "wallet-sign")).isEqualTo("FAILED");
|
|
|
|
|
assertThat(jdbc.queryForMap("SELECT current_step_code,current_action_code " +
|
|
|
|
|
"FROM training_experiment_attempt WHERE id=?", attemptId))
|
|
|
|
|
.containsEntry("current_step_code", "02")
|
|
|
|
|
.containsEntry("current_action_code", "wallet-sign");
|
|
|
|
|
assertThat(jdbc.queryForObject("SELECT completion_status FROM stu_module_score_details " +
|
|
|
|
|
"WHERE user_id=? AND serial_number=5", String.class, PAYER_USER)).isEqualTo("2");
|
|
|
|
|
|
|
|
|
|
executeAction(attemptId, "02", "wallet-sign", payer());
|
|
|
|
|
assertThat(actionStatus(attemptId, "02", "wallet-sign")).isEqualTo("COMPLETED");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
void repairsTheLegacyTeachingPayerTypeWithoutResettingItsBalance() throws Exception {
|
|
|
|
|
mockMvc.perform(get("/api/v1/payments/context").with(payer())).andExpect(status().isOk());
|
|
|
|
|
@ -322,6 +372,47 @@ class PaymentControllerTest {
|
|
|
|
|
BigDecimal.class, PAYEE_TEACHING_WALLETS.payerWalletId())).isEqualByComparingTo("10000.00");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
void resetDeletesUncreditedPaymentWithoutReducingPayeeBalance() throws Exception {
|
|
|
|
|
String created = mockMvc.perform(post("/api/v1/payments").with(payer())
|
|
|
|
|
.contentType(MediaType.APPLICATION_JSON)
|
|
|
|
|
.content("{\"payerWalletId\":\"" + PAYER_WALLET + "\",\"payeeWalletId\":\"" +
|
|
|
|
|
PAYEE_WALLET + "\",\"amount\":2000.00,\"note\":\"未入账订单\"}"))
|
|
|
|
|
.andExpect(status().isOk())
|
|
|
|
|
.andExpect(jsonPath("$.data.status").value("REQUEST_PREPARED"))
|
|
|
|
|
.andReturn().getResponse().getContentAsString();
|
|
|
|
|
String paymentId = JsonPath.read(created, "$.data.id");
|
|
|
|
|
|
|
|
|
|
assertThat(jdbc.queryForObject("SELECT balance FROM digital_wallet WHERE wallet_id=?",
|
|
|
|
|
BigDecimal.class, PAYEE_WALLET)).isEqualByComparingTo("0.00");
|
|
|
|
|
|
|
|
|
|
walletRuntimeResetService.reset(new InstitutionKeySubject(PAYER_USER, 3001L, 2001L));
|
|
|
|
|
|
|
|
|
|
assertThat(jdbc.queryForObject("SELECT COUNT(*) FROM payment_order WHERE id=?",
|
|
|
|
|
Integer.class, paymentId)).isZero();
|
|
|
|
|
assertThat(jdbc.queryForObject("SELECT balance FROM digital_wallet WHERE wallet_id=?",
|
|
|
|
|
BigDecimal.class, PAYEE_WALLET)).isEqualByComparingTo("0.00");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
void refreshPayeeRepairsLegacyNegativeBalanceWhenNoPaymentRemains() throws Exception {
|
|
|
|
|
String created = mockMvc.perform(post("/api/v1/payment/attempts").with(payer()))
|
|
|
|
|
.andExpect(status().isOk())
|
|
|
|
|
.andReturn().getResponse().getContentAsString();
|
|
|
|
|
String attemptId = JsonPath.read(created, "$.data.attemptId");
|
|
|
|
|
executeAction(attemptId, "01", "refresh-payer", payer());
|
|
|
|
|
jdbc.update("UPDATE digital_wallet SET balance=-2000.00,frozen_amount=0 WHERE wallet_id=?",
|
|
|
|
|
PAYEE_STORAGE_WALLET);
|
|
|
|
|
|
|
|
|
|
mockMvc.perform(post("/api/v1/payment/attempts/{id}/steps/01/actions/refresh-payee", attemptId)
|
|
|
|
|
.with(payer()).contentType(MediaType.APPLICATION_JSON).content("{}"))
|
|
|
|
|
.andExpect(status().isOk())
|
|
|
|
|
.andExpect(jsonPath("$.data.output.balance").value(0.00))
|
|
|
|
|
.andExpect(jsonPath("$.data.output.availableBalance").value(0.00));
|
|
|
|
|
assertThat(jdbc.queryForObject("SELECT balance FROM digital_wallet WHERE wallet_id=?",
|
|
|
|
|
BigDecimal.class, PAYEE_STORAGE_WALLET)).isEqualByComparingTo("0.00");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
void refreshActionsProjectCompletedModuleThreeWalletsWhenSharedRowsAreMissing() throws Exception {
|
|
|
|
|
clearSharedWalletProjection();
|
|
|
|
|
@ -500,13 +591,56 @@ class PaymentControllerTest {
|
|
|
|
|
|
|
|
|
|
private void executeAction(String attemptId, String step, String action,
|
|
|
|
|
RequestPostProcessor actor) throws Exception {
|
|
|
|
|
executeAction(attemptId, step, action, actionBody(step, action), actor);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void executeAction(String attemptId, String step, String action, String body,
|
|
|
|
|
RequestPostProcessor actor) throws Exception {
|
|
|
|
|
mockMvc.perform(post("/api/v1/payment/attempts/{id}/steps/{step}/actions/{action}",
|
|
|
|
|
attemptId, step, action).with(actor)
|
|
|
|
|
.contentType(MediaType.APPLICATION_JSON).content("{}"))
|
|
|
|
|
.contentType(MediaType.APPLICATION_JSON).content(body))
|
|
|
|
|
.andExpect(status().isOk())
|
|
|
|
|
.andExpect(jsonPath("$.data.actionStatus").value("COMPLETED"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private String actionStatus(String attemptId, String step, String action) {
|
|
|
|
|
return jdbc.queryForObject("SELECT status FROM training_experiment_action " +
|
|
|
|
|
"WHERE attempt_id=? AND step_code=? AND action_code=?",
|
|
|
|
|
String.class, attemptId, step, action);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void loadPayerWalletKeyFromStorage() {
|
|
|
|
|
Map<String, Object> certificate = jdbc.queryForMap(
|
|
|
|
|
"SELECT public_key,encrypted_private_key FROM wallet_certificate " +
|
|
|
|
|
"WHERE wallet_id=? AND status='VALID' AND filing_status='REGISTERED'",
|
|
|
|
|
PAYER_STORAGE_WALLET);
|
|
|
|
|
payerWalletKey = new InstitutionSm2KeyPair(
|
|
|
|
|
walletCipher.decrypt((String) certificate.get("encrypted_private_key")),
|
|
|
|
|
(String) certificate.get("public_key"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private String actionBody(String step, String action) {
|
|
|
|
|
String key = step + ":" + action;
|
|
|
|
|
switch (key) {
|
|
|
|
|
case "02:generate-digest": return "{\"algorithm\":\"SM3\"}";
|
|
|
|
|
case "02:wallet-sign": return "{\"algorithm\":\"SM2-SIGN\",\"privateKey\":\""
|
|
|
|
|
+ payerWalletKey.getPrivateKey() + "\"}";
|
|
|
|
|
case "03:verify-payer-signature": return "{\"algorithm\":\"SM2-VERIFY\",\"publicKey\":\""
|
|
|
|
|
+ payerWalletKey.getPublicKey() + "\"}";
|
|
|
|
|
case "03:generate-compliance-digest": return "{\"algorithm\":\"SM3\"}";
|
|
|
|
|
case "03:bank-sign": return "{\"algorithm\":\"SM2-SIGN\",\"privateKey\":\""
|
|
|
|
|
+ bankKey.getPrivateKey() + "\"}";
|
|
|
|
|
case "04:verify-payer-bank-signature": return "{\"algorithm\":\"SM2-VERIFY\",\"publicKey\":\""
|
|
|
|
|
+ bankKey.getPublicKey() + "\"}";
|
|
|
|
|
case "04:generate-settlement-digest": return "{\"algorithm\":\"SM3\"}";
|
|
|
|
|
case "04:central-bank-sign": return "{\"algorithm\":\"SM2-SIGN\",\"privateKey\":\""
|
|
|
|
|
+ centralKey.getPrivateKey() + "\"}";
|
|
|
|
|
case "05:verify-central-bank-signature": return "{\"algorithm\":\"SM2-VERIFY\",\"publicKey\":\""
|
|
|
|
|
+ centralKey.getPublicKey() + "\"}";
|
|
|
|
|
default: return "{}";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void insertUser(String id, String account, String schoolId, String classId) {
|
|
|
|
|
jdbc.update("INSERT INTO sys_user (user_id,student_id,password,user_name,class_id,class_name,school_id,school_name,role_id,is_deleted,zy_user_id) " +
|
|
|
|
|
"VALUES (?,?,?,?,?,'支付测试班',?,'延安大学',4,0,?)", id, account, "unused", account, classId, schoolId, account);
|
|
|
|
|
@ -563,6 +697,7 @@ class PaymentControllerTest {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void cleanPaymentData() {
|
|
|
|
|
jdbc.update("DELETE FROM stu_module_score_details WHERE user_id=? AND serial_number=5", PAYER_USER);
|
|
|
|
|
jdbc.update("DELETE FROM training_experiment_action WHERE attempt_id IN (SELECT id FROM " +
|
|
|
|
|
"training_experiment_attempt WHERE module_code='PAYMENT' AND user_id IN (?,?))", PAYER_USER, PAYEE_USER);
|
|
|
|
|
jdbc.update("DELETE FROM training_experiment_attempt WHERE module_code='PAYMENT' AND user_id IN (?,?)",
|
|
|
|
|
|