feat: align payment request and compliance actions
parent
47ff07c956
commit
754cb2620b
@ -0,0 +1,38 @@
|
||||
package com.yau.digitalrmb.payment.application.service;
|
||||
|
||||
import com.yau.digitalrmb.payment.domain.model.PaymentActor;
|
||||
import com.yau.digitalrmb.shared.api.ErrorCode;
|
||||
import com.yau.digitalrmb.shared.exception.BusinessException;
|
||||
import com.yau.digitalrmb.training.attempt.application.AttemptCancellationHandler;
|
||||
import com.yau.digitalrmb.training.attempt.domain.ExperimentAttempt;
|
||||
import com.yau.digitalrmb.training.attempt.domain.ExperimentModule;
|
||||
import com.yau.digitalrmb.training.attempt.domain.ExperimentSubject;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
@Component
|
||||
public class PaymentAttemptCancellationHandler implements AttemptCancellationHandler {
|
||||
private final PaymentApplicationService payments;
|
||||
|
||||
public PaymentAttemptCancellationHandler(PaymentApplicationService payments) {
|
||||
this.payments = payments;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExperimentModule module() {
|
||||
return ExperimentModule.PAYMENT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancel(ExperimentAttempt attempt, ExperimentSubject subject) {
|
||||
if (attempt.getBusinessId() == null) return;
|
||||
try {
|
||||
payments.cancel(UUID.fromString(attempt.getBusinessId()),
|
||||
new PaymentActor(subject.getUserId(), subject.getUserId(),
|
||||
subject.getSchoolId(), subject.getClassId()));
|
||||
} catch (IllegalArgumentException exception) {
|
||||
throw new BusinessException(ErrorCode.INTERNAL_ERROR, "支付实验绑定的业务标识无效");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,237 @@
|
||||
package com.yau.digitalrmb.payment.application.service;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.yau.digitalrmb.payment.application.command.CreatePaymentCommand;
|
||||
import com.yau.digitalrmb.payment.application.query.PaymentOrderView;
|
||||
import com.yau.digitalrmb.payment.domain.model.PaymentActor;
|
||||
import com.yau.digitalrmb.payment.domain.model.PaymentContext;
|
||||
import com.yau.digitalrmb.payment.interfaces.dto.PaymentActionRequest;
|
||||
import com.yau.digitalrmb.shared.api.ErrorCode;
|
||||
import com.yau.digitalrmb.shared.exception.BusinessException;
|
||||
import com.yau.digitalrmb.training.attempt.application.ActionOutcome;
|
||||
import com.yau.digitalrmb.training.attempt.application.ExperimentAttemptService;
|
||||
import com.yau.digitalrmb.training.attempt.domain.ActionStatus;
|
||||
import com.yau.digitalrmb.training.attempt.domain.ExperimentSubject;
|
||||
import com.yau.digitalrmb.training.attempt.interfaces.dto.ExperimentActionView;
|
||||
import com.yau.digitalrmb.training.attempt.interfaces.dto.ExperimentAttemptView;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.time.Instant;
|
||||
import java.time.ZoneOffset;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
@Service
|
||||
public class PaymentTrainingActionService {
|
||||
private static final DateTimeFormatter TIMESTAMP = DateTimeFormatter.ofPattern("yyyyMMddHHmmss")
|
||||
.withZone(ZoneOffset.UTC);
|
||||
private static final List<String> ACTIONS = Arrays.asList(
|
||||
"01:refresh-payer", "01:refresh-payee",
|
||||
"02:generate-timestamp", "02:concatenate-payment", "02:generate-digest",
|
||||
"02:wallet-sign", "02:package-payment", "02:send-payment",
|
||||
"03:verify-payer-signature", "03:compliance-check", "03:generate-compliance-report",
|
||||
"03:generate-compliance-digest", "03:bank-sign", "03:package-bank-request",
|
||||
"03:send-to-central-bank");
|
||||
|
||||
private final ExperimentAttemptService attempts;
|
||||
private final PaymentApplicationService payments;
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
public PaymentTrainingActionService(ExperimentAttemptService attempts,
|
||||
PaymentApplicationService payments,
|
||||
ObjectMapper objectMapper) {
|
||||
this.attempts = attempts;
|
||||
this.payments = payments;
|
||||
this.objectMapper = objectMapper;
|
||||
}
|
||||
|
||||
public ExperimentActionView<?> execute(UUID attemptId, String stepCode, String actionCode,
|
||||
PaymentActionRequest request, ExperimentSubject subject,
|
||||
PaymentActor actor) {
|
||||
String key = stepCode + ":" + actionCode;
|
||||
int index = ACTIONS.indexOf(key);
|
||||
if (index < 0) throw validation("不支持的支付实验动作:" + key);
|
||||
PaymentActionRequest input = request == null ? new PaymentActionRequest() : request;
|
||||
ExperimentAttemptView attempt = attempts.detail(attemptId, subject);
|
||||
requirePrevious(attempt, index);
|
||||
UUID paymentId = businessId(attempt.getBusinessId());
|
||||
if (index > ACTIONS.indexOf("02:generate-digest") && paymentId == null) {
|
||||
throw validation("请先生成支付摘要");
|
||||
}
|
||||
String[] next = ACTIONS.get(Math.min(index + 1, ACTIONS.size() - 1)).split(":", 2);
|
||||
final UUID boundPaymentId = paymentId;
|
||||
return attempts.execute(attemptId, subject, stepCode, actionCode,
|
||||
fingerprint(key, input, subject), Object.class,
|
||||
() -> run(key, input, attempt, boundPaymentId, actor), next[0], next[1]);
|
||||
}
|
||||
|
||||
private ActionOutcome<Object> run(String key, PaymentActionRequest request,
|
||||
ExperimentAttemptView attempt, UUID paymentId,
|
||||
PaymentActor actor) {
|
||||
switch (key) {
|
||||
case "01:refresh-payer":
|
||||
return completed(payments.payer(actor));
|
||||
case "01:refresh-payee":
|
||||
return completed(payments.payee(required(request.getPayeeWalletId(), "收款钱包标识")));
|
||||
case "02:generate-timestamp":
|
||||
return completed(value("timestamp", TIMESTAMP.format(Instant.now())));
|
||||
case "02:concatenate-payment":
|
||||
return completed(paymentSource(request, attempt, actor));
|
||||
case "02:generate-digest": {
|
||||
Map<String, Object> source = prior(attempt, "02", "concatenate-payment");
|
||||
PaymentOrderView created = payments.create(new CreatePaymentCommand(
|
||||
text(source, "payerWalletId"), text(source, "payeeWalletId"),
|
||||
decimal(source, "amount"), text(source, "note"), text(source, "timestamp")), actor);
|
||||
return ActionOutcome.completed(created, created.getId());
|
||||
}
|
||||
case "02:wallet-sign":
|
||||
return completed(payments.sign(paymentId, actor));
|
||||
case "02:package-payment":
|
||||
case "02:send-payment":
|
||||
case "03:package-bank-request":
|
||||
case "03:send-to-central-bank":
|
||||
return completed(payments.get(paymentId, actor));
|
||||
case "03:verify-payer-signature":
|
||||
return completed(payments.verifyPayerSignature(paymentId, actor));
|
||||
case "03:compliance-check":
|
||||
return completed(payments.lockComplianceResources(paymentId, actor));
|
||||
case "03:generate-compliance-report":
|
||||
return completed(payments.generateComplianceReport(paymentId, actor));
|
||||
case "03:generate-compliance-digest":
|
||||
return completed(payments.generateComplianceDigest(paymentId, actor));
|
||||
case "03:bank-sign":
|
||||
return completed(payments.signCompliance(paymentId, actor));
|
||||
default:
|
||||
throw validation("不支持的支付实验动作:" + key);
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, Object> paymentSource(PaymentActionRequest request,
|
||||
ExperimentAttemptView attempt,
|
||||
PaymentActor actor) {
|
||||
String payeeWalletId = required(request.getPayeeWalletId(), "收款钱包标识");
|
||||
PaymentContext context = payments.context(payeeWalletId, actor);
|
||||
if (request.getPayerWalletId() != null
|
||||
&& !context.getPayer().getWalletId().equals(request.getPayerWalletId().trim())) {
|
||||
throw validation("付款钱包不属于当前用户");
|
||||
}
|
||||
BigDecimal amount = money(request.getAmount());
|
||||
String note = request.getNote() == null ? "" : request.getNote().trim();
|
||||
if (note.length() > 256) throw validation("支付备注最多256个字符");
|
||||
String timestamp = text(prior(attempt, "02", "generate-timestamp"), "timestamp");
|
||||
Map<String, Object> output = new LinkedHashMap<String, Object>();
|
||||
output.put("payerWalletId", context.getPayer().getWalletId());
|
||||
output.put("payeeWalletId", context.getPayee().getWalletId());
|
||||
output.put("amount", amount.toPlainString());
|
||||
output.put("note", note);
|
||||
output.put("timestamp", timestamp);
|
||||
output.put("sourceText", "PAY|" + context.getPayer().getWalletId() + "|"
|
||||
+ context.getPayee().getWalletId() + "|" + amount.toPlainString() + "|"
|
||||
+ note + "|" + timestamp);
|
||||
return output;
|
||||
}
|
||||
|
||||
private void requirePrevious(ExperimentAttemptView attempt, int index) {
|
||||
if (index == 0) return;
|
||||
String[] previous = ACTIONS.get(index - 1).split(":", 2);
|
||||
for (ExperimentActionView<?> action : attempt.getActions()) {
|
||||
if (previous[0].equals(action.getStepCode()) && previous[1].equals(action.getActionCode())
|
||||
&& action.getActionStatus() == ActionStatus.COMPLETED) return;
|
||||
}
|
||||
throw validation("请先完成动作:" + ACTIONS.get(index - 1));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private Map<String, Object> prior(ExperimentAttemptView attempt, String step, String actionCode) {
|
||||
for (ExperimentActionView<?> action : attempt.getActions()) {
|
||||
if (step.equals(action.getStepCode()) && actionCode.equals(action.getActionCode())
|
||||
&& action.getActionStatus() == ActionStatus.COMPLETED
|
||||
&& action.getOutput() instanceof Map) {
|
||||
return (Map<String, Object>) action.getOutput();
|
||||
}
|
||||
}
|
||||
throw validation("缺少前置动作结果:" + step + ":" + actionCode);
|
||||
}
|
||||
|
||||
private Map<String, Object> value(String name, Object value) {
|
||||
Map<String, Object> output = new LinkedHashMap<String, Object>();
|
||||
output.put(name, value);
|
||||
return output;
|
||||
}
|
||||
|
||||
private ActionOutcome<Object> completed(Object output) {
|
||||
return ActionOutcome.completed(output);
|
||||
}
|
||||
|
||||
private UUID businessId(String value) {
|
||||
if (value == null || value.trim().isEmpty()) return null;
|
||||
try {
|
||||
return UUID.fromString(value);
|
||||
} catch (IllegalArgumentException exception) {
|
||||
throw new BusinessException(ErrorCode.INTERNAL_ERROR, "支付实验绑定的业务标识无效");
|
||||
}
|
||||
}
|
||||
|
||||
private String text(Map<String, Object> values, String name) {
|
||||
Object value = values.get(name);
|
||||
if (value == null) throw validation("缺少派生参数:" + name);
|
||||
return value.toString();
|
||||
}
|
||||
|
||||
private BigDecimal decimal(Map<String, Object> values, String name) {
|
||||
try {
|
||||
return new BigDecimal(text(values, name));
|
||||
} catch (NumberFormatException exception) {
|
||||
throw validation("派生金额无效:" + name);
|
||||
}
|
||||
}
|
||||
|
||||
private BigDecimal money(BigDecimal value) {
|
||||
if (value == null) throw validation("支付金额不能为空");
|
||||
try {
|
||||
BigDecimal normalized = value.setScale(2, RoundingMode.UNNECESSARY);
|
||||
if (normalized.signum() <= 0) throw validation("支付金额必须大于0");
|
||||
return normalized;
|
||||
} catch (ArithmeticException exception) {
|
||||
throw validation("支付金额最多保留两位小数");
|
||||
}
|
||||
}
|
||||
|
||||
private String required(String value, String name) {
|
||||
if (value == null || value.trim().isEmpty()) throw validation(name + "不能为空");
|
||||
return value.trim();
|
||||
}
|
||||
|
||||
private String fingerprint(String key, PaymentActionRequest request, ExperimentSubject subject) {
|
||||
Map<String, Object> values = new LinkedHashMap<String, Object>();
|
||||
values.put("action", key);
|
||||
values.put("payerWalletId", request.getPayerWalletId());
|
||||
values.put("payeeWalletId", request.getPayeeWalletId());
|
||||
values.put("amount", request.getAmount());
|
||||
values.put("note", request.getNote());
|
||||
values.put("userId", subject.getUserId());
|
||||
try {
|
||||
byte[] json = objectMapper.writeValueAsString(values).getBytes(StandardCharsets.UTF_8);
|
||||
byte[] digest = MessageDigest.getInstance("SHA-256").digest(json);
|
||||
StringBuilder result = new StringBuilder();
|
||||
for (byte value : digest) result.append(String.format("%02x", value & 0xff));
|
||||
return result.toString();
|
||||
} catch (JsonProcessingException | NoSuchAlgorithmException exception) {
|
||||
throw new BusinessException(ErrorCode.INTERNAL_ERROR, "无法生成动作幂等指纹");
|
||||
}
|
||||
}
|
||||
|
||||
private BusinessException validation(String message) {
|
||||
return new BusinessException(ErrorCode.VALIDATION_ERROR, message);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,19 @@
|
||||
package com.yau.digitalrmb.payment.interfaces.dto;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
public class PaymentActionRequest {
|
||||
private String payerWalletId;
|
||||
private String payeeWalletId;
|
||||
private BigDecimal amount;
|
||||
private String note;
|
||||
|
||||
public String getPayerWalletId() { return payerWalletId; }
|
||||
public void setPayerWalletId(String payerWalletId) { this.payerWalletId = payerWalletId; }
|
||||
public String getPayeeWalletId() { return payeeWalletId; }
|
||||
public void setPayeeWalletId(String payeeWalletId) { this.payeeWalletId = payeeWalletId; }
|
||||
public BigDecimal getAmount() { return amount; }
|
||||
public void setAmount(BigDecimal amount) { this.amount = amount; }
|
||||
public String getNote() { return note; }
|
||||
public void setNote(String note) { this.note = note; }
|
||||
}
|
||||
@ -0,0 +1,110 @@
|
||||
package com.yau.digitalrmb.payment.interfaces.rest;
|
||||
|
||||
import com.yau.digitalrmb.payment.application.service.PaymentTrainingActionService;
|
||||
import com.yau.digitalrmb.payment.domain.model.PaymentActor;
|
||||
import com.yau.digitalrmb.payment.interfaces.dto.PaymentActionRequest;
|
||||
import com.yau.digitalrmb.security.application.CurrentUser;
|
||||
import com.yau.digitalrmb.security.application.CurrentUserService;
|
||||
import com.yau.digitalrmb.shared.api.ApiResponse;
|
||||
import com.yau.digitalrmb.shared.api.ErrorCode;
|
||||
import com.yau.digitalrmb.shared.exception.BusinessException;
|
||||
import com.yau.digitalrmb.shared.web.TraceIdFilter;
|
||||
import com.yau.digitalrmb.training.attempt.application.ExperimentAttemptService;
|
||||
import com.yau.digitalrmb.training.attempt.domain.ExperimentModule;
|
||||
import com.yau.digitalrmb.training.attempt.domain.ExperimentSubject;
|
||||
import com.yau.digitalrmb.training.attempt.interfaces.dto.ExperimentActionView;
|
||||
import com.yau.digitalrmb.training.attempt.interfaces.dto.ExperimentAttemptView;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import org.slf4j.MDC;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/payment/attempts")
|
||||
@Tag(name = "数字货币支付实验动作", description = "模块五可恢复实验及前端独立按钮动作")
|
||||
public class PaymentTrainingAttemptController {
|
||||
private final ExperimentAttemptService attempts;
|
||||
private final PaymentTrainingActionService actions;
|
||||
private final CurrentUserService users;
|
||||
|
||||
public PaymentTrainingAttemptController(ExperimentAttemptService attempts,
|
||||
PaymentTrainingActionService actions,
|
||||
CurrentUserService users) {
|
||||
this.attempts = attempts;
|
||||
this.actions = actions;
|
||||
this.users = users;
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
@Operation(summary = "开始新的支付实验")
|
||||
public ApiResponse<ExperimentAttemptView> create() {
|
||||
return ok(attempts.create(ExperimentModule.PAYMENT, subject(users.getCurrentUser())));
|
||||
}
|
||||
|
||||
@GetMapping("/current")
|
||||
@Operation(summary = "恢复当前支付实验")
|
||||
public ApiResponse<ExperimentAttemptView> current() {
|
||||
return ok(attempts.current(ExperimentModule.PAYMENT, subject(users.getCurrentUser())));
|
||||
}
|
||||
|
||||
@GetMapping("/{attemptId}")
|
||||
@Operation(summary = "查询支付实验详情和已保存动作")
|
||||
public ApiResponse<ExperimentAttemptView> detail(@PathVariable UUID attemptId) {
|
||||
return ok(attempts.detail(attemptId, subject(users.getCurrentUser())));
|
||||
}
|
||||
|
||||
@PostMapping("/{attemptId}/cancel")
|
||||
@Operation(summary = "取消支付实验")
|
||||
public ApiResponse<ExperimentAttemptView> cancel(@PathVariable UUID attemptId) {
|
||||
return ok(attempts.cancel(attemptId, subject(users.getCurrentUser())));
|
||||
}
|
||||
|
||||
@PostMapping("/{attemptId}/steps/{stepCode}/actions/{actionCode}")
|
||||
@Operation(summary = "执行一个支付实验按钮动作")
|
||||
public ApiResponse<ExperimentActionView<?>> execute(@PathVariable UUID attemptId,
|
||||
@PathVariable String stepCode,
|
||||
@PathVariable String actionCode,
|
||||
@RequestBody(required = false) PaymentActionRequest request) {
|
||||
CurrentUser user = users.getCurrentUser();
|
||||
return ok(actions.execute(attemptId, stepCode, actionCode,
|
||||
request == null ? new PaymentActionRequest() : request, subject(user), actor(user)));
|
||||
}
|
||||
|
||||
private ExperimentSubject subject(CurrentUser user) {
|
||||
return new ExperimentSubject(required(user.getUserId(), "用户 ID"),
|
||||
numeric(user.getSchoolId(), "学校 ID"), numeric(user.getClassId(), "班级 ID"), "");
|
||||
}
|
||||
|
||||
private PaymentActor actor(CurrentUser user) {
|
||||
return new PaymentActor(required(user.getUserId(), "用户 ID"), required(user.getName(), "用户姓名"),
|
||||
numeric(user.getSchoolId(), "学校 ID"), numeric(user.getClassId(), "班级 ID"));
|
||||
}
|
||||
|
||||
private long numeric(String value, String name) {
|
||||
try {
|
||||
long result = Long.parseLong(required(value, name));
|
||||
if (result <= 0) throw new NumberFormatException(name);
|
||||
return result;
|
||||
} catch (NumberFormatException exception) {
|
||||
throw new BusinessException(ErrorCode.UNAUTHORIZED, "登录凭据中的" + name + "无效");
|
||||
}
|
||||
}
|
||||
|
||||
private String required(String value, String name) {
|
||||
if (value == null || value.trim().isEmpty()) {
|
||||
throw new BusinessException(ErrorCode.UNAUTHORIZED, "登录凭据缺少" + name);
|
||||
}
|
||||
return value.trim();
|
||||
}
|
||||
|
||||
private <T> ApiResponse<T> ok(T data) {
|
||||
return ApiResponse.success(data, MDC.get(TraceIdFilter.MDC_KEY));
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,84 @@
|
||||
package com.yau.digitalrmb.payment.interfaces.rest;
|
||||
|
||||
import com.jayway.jsonpath.JsonPath;
|
||||
import com.yau.digitalrmb.payment.application.service.PaymentTrainingActionService;
|
||||
import com.yau.digitalrmb.security.application.CurrentUser;
|
||||
import com.yau.digitalrmb.security.application.CurrentUserService;
|
||||
import com.yau.digitalrmb.training.attempt.domain.ActionStatus;
|
||||
import com.yau.digitalrmb.training.attempt.domain.AttemptStatus;
|
||||
import com.yau.digitalrmb.training.attempt.domain.ExperimentModule;
|
||||
import com.yau.digitalrmb.training.attempt.interfaces.dto.ExperimentActionView;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.test.context.ActiveProfiles;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.Collections;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.jwt;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
@SpringBootTest
|
||||
@AutoConfigureMockMvc
|
||||
@ActiveProfiles("test")
|
||||
class PaymentTrainingAttemptControllerTest {
|
||||
private static final String USER_ID = "00000000-0000-0000-0000-000000000489";
|
||||
|
||||
@Autowired private MockMvc mockMvc;
|
||||
@MockBean private PaymentTrainingActionService actions;
|
||||
@MockBean private CurrentUserService users;
|
||||
|
||||
@Test
|
||||
void exposesEveryPaymentActionThroughComplianceProcessing() throws Exception {
|
||||
when(users.getCurrentUser()).thenReturn(user());
|
||||
String created = mockMvc.perform(post("/api/v1/payment/attempts").with(jwtForUser()))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.data.moduleCode").value("PAYMENT"))
|
||||
.andReturn().getResponse().getContentAsString();
|
||||
UUID attemptId = UUID.fromString(JsonPath.read(created, "$.data.attemptId"));
|
||||
when(actions.execute(any(UUID.class), anyString(), anyString(), any(), any(), any()))
|
||||
.thenAnswer(invocation -> new ExperimentActionView<Object>(invocation.getArgument(0),
|
||||
ExperimentModule.PAYMENT, invocation.getArgument(1), invocation.getArgument(2),
|
||||
ActionStatus.COMPLETED, AttemptStatus.IN_PROGRESS, invocation.getArgument(1),
|
||||
invocation.getArgument(2), Instant.now(), Collections.singletonMap("safe", true)));
|
||||
|
||||
String[][] actionCodes = {
|
||||
{"01", "refresh-payer"}, {"01", "refresh-payee"},
|
||||
{"02", "generate-timestamp"}, {"02", "concatenate-payment"},
|
||||
{"02", "generate-digest"}, {"02", "wallet-sign"},
|
||||
{"02", "package-payment"}, {"02", "send-payment"},
|
||||
{"03", "verify-payer-signature"}, {"03", "compliance-check"},
|
||||
{"03", "generate-compliance-report"}, {"03", "generate-compliance-digest"},
|
||||
{"03", "bank-sign"}, {"03", "package-bank-request"},
|
||||
{"03", "send-to-central-bank"}
|
||||
};
|
||||
for (String[] action : actionCodes) {
|
||||
mockMvc.perform(post("/api/v1/payment/attempts/{id}/steps/{step}/actions/{action}",
|
||||
attemptId, action[0], action[1]).with(jwtForUser())
|
||||
.contentType(MediaType.APPLICATION_JSON).content("{}"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.data.stepCode").value(action[0]))
|
||||
.andExpect(jsonPath("$.data.actionCode").value(action[1]));
|
||||
}
|
||||
}
|
||||
|
||||
private CurrentUser user() {
|
||||
return new CurrentUser("3001", "延安大学", null, null, null, null, 4L,
|
||||
USER_ID, "payment", "支付用户", "2001", "测试班", "payment");
|
||||
}
|
||||
|
||||
private org.springframework.test.web.servlet.request.RequestPostProcessor jwtForUser() {
|
||||
return jwt().jwt(token -> token.subject(USER_ID).claim("userId", USER_ID));
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue