You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
323 lines
17 KiB
Java
323 lines
17 KiB
Java
package com.yau.digitalrmb.walletopening.application;
|
|
|
|
import com.yau.digitalrmb.institutionidentity.application.InstitutionKeyService;
|
|
import com.yau.digitalrmb.institutionidentity.application.InstitutionKeySubject;
|
|
import com.yau.digitalrmb.institutionidentity.infrastructure.SmInstitutionIdentityCryptography;
|
|
import com.yau.digitalrmb.shared.api.ErrorCode;
|
|
import com.yau.digitalrmb.shared.exception.BusinessException;
|
|
import com.yau.digitalrmb.walletopening.domain.CentralWalletActivation;
|
|
import com.yau.digitalrmb.walletopening.domain.CentralWalletActivationRepository;
|
|
import com.yau.digitalrmb.walletopening.domain.CentralWalletRegistration;
|
|
import com.yau.digitalrmb.walletopening.domain.CentralWalletRegistrationRepository;
|
|
import com.yau.digitalrmb.walletopening.domain.SmartContractGeneration;
|
|
import com.yau.digitalrmb.walletopening.domain.SmartContractGenerationRepository;
|
|
import com.yau.digitalrmb.walletopening.domain.WalletApplication;
|
|
import com.yau.digitalrmb.walletopening.domain.WalletApplicationRepository;
|
|
import com.yau.digitalrmb.walletopening.domain.WalletIdentifierGeneration;
|
|
import com.yau.digitalrmb.walletopening.domain.WalletIdentifierGenerationRepository;
|
|
import org.springframework.stereotype.Service;
|
|
import org.springframework.transaction.annotation.Transactional;
|
|
|
|
import javax.annotation.Resource;
|
|
import java.time.LocalDateTime;
|
|
import java.time.format.DateTimeFormatter;
|
|
|
|
@Service
|
|
public class CentralWalletActivationService {
|
|
private static final DateTimeFormatter TIMESTAMP_FORMAT = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");
|
|
private static final DateTimeFormatter DATE_FORMAT = DateTimeFormatter.ofPattern("yyyyMMdd");
|
|
|
|
@Resource
|
|
private CentralWalletActivationRepository activationRepository;
|
|
@Resource
|
|
private SmartContractGenerationRepository contractRepository;
|
|
@Resource
|
|
private WalletApplicationRepository applicationRepository;
|
|
@Resource
|
|
private WalletIdentifierGenerationRepository identifierRepository;
|
|
@Resource
|
|
private CentralWalletRegistrationRepository centralRegistrationRepository;
|
|
@Resource
|
|
private InstitutionKeyService institutionKeyService;
|
|
@Resource
|
|
private SmInstitutionIdentityCryptography cryptography;
|
|
@Resource
|
|
private WalletOpeningScoreService scoreService;
|
|
|
|
@Transactional(readOnly = true)
|
|
public CentralWalletActivationPageResult getPage(InstitutionKeySubject subject) {
|
|
CentralWalletActivation a = activationRepository
|
|
.findLatest(subject.getUserId(), subject.getSchoolId(), subject.getClassId()).orElse(null);
|
|
return new CentralWalletActivationPageResult(a != null ? a : CentralWalletActivation.create(
|
|
subject.getUserId(), subject.getSchoolId(), subject.getClassId()));
|
|
}
|
|
|
|
@Transactional
|
|
public CentralWalletActivationPageResult receive(InstitutionKeySubject subject) {
|
|
CentralWalletActivation a = getOrCreate(subject);
|
|
if (a.isReceived()) {
|
|
return new CentralWalletActivationPageResult(a);
|
|
}
|
|
SmartContractGeneration c = contractRepository
|
|
.findLatest(subject.getUserId(), subject.getSchoolId(), subject.getClassId()).orElse(null);
|
|
if (c == null || !c.isSent()) {
|
|
scoreService.recordError(subject.getUserId());
|
|
throw new BusinessException(ErrorCode.VALIDATION_ERROR, "尚未收到商业银行发送的开通确认报文");
|
|
}
|
|
String receiveTime = LocalDateTime.now().format(TIMESTAMP_FORMAT);
|
|
a.receive(c.getConfirmId(), c.getWalletId(), c.getOrgCode(), c.getContractId(),
|
|
c.getActivateDigest(), c.getActivateSignature(), c.getActivateTimestamp(), c.getConfirmJson());
|
|
activationRepository.update(a);
|
|
return new CentralWalletActivationPageResult(a);
|
|
}
|
|
|
|
@Transactional
|
|
public CentralWalletActivationPageResult verifySignature(InstitutionKeySubject subject, String publicKeyInput, String algorithm) {
|
|
if (!"SM2".equalsIgnoreCase(algorithm != null ? algorithm.trim() : "")) {
|
|
scoreService.recordError(subject.getUserId());
|
|
throw new BusinessException(ErrorCode.VALIDATION_ERROR, "算法选择有误,错误数+1");
|
|
}
|
|
CentralWalletActivation a = getOrCreate(subject);
|
|
if (!a.isReceived()) {
|
|
scoreService.recordError(subject.getUserId());
|
|
throw new BusinessException(ErrorCode.VALIDATION_ERROR, "请先接收开通确认报文");
|
|
}
|
|
String storedPublicKey = institutionKeyService.commercialBankSecondPublicKeyOrNull(subject);
|
|
if (storedPublicKey == null) {
|
|
scoreService.recordError(subject.getUserId());
|
|
throw new BusinessException(ErrorCode.VALIDATION_ERROR, "商业银行第二公钥不存在,请先在模块一生成机构密钥");
|
|
}
|
|
boolean keyMatches = storedPublicKey.equalsIgnoreCase(publicKeyInput.trim());
|
|
if (!keyMatches) {
|
|
a.verifySignature(publicKeyInput, false);
|
|
activationRepository.update(a);
|
|
scoreService.recordError(subject.getUserId());
|
|
throw new BusinessException(ErrorCode.VALIDATION_ERROR, "选择的公钥与商业银行第二公钥不匹配");
|
|
}
|
|
boolean verified = cryptography.verify(storedPublicKey, a.getDigest(), a.getSignature());
|
|
a.verifySignature(publicKeyInput.trim(), verified);
|
|
if (!verified) {
|
|
activationRepository.update(a);
|
|
scoreService.recordError(subject.getUserId());
|
|
throw new BusinessException(ErrorCode.VALIDATION_ERROR, "签名验证失败");
|
|
}
|
|
activationRepository.update(a);
|
|
return new CentralWalletActivationPageResult(a);
|
|
}
|
|
|
|
@Transactional
|
|
public CentralWalletActivationPageResult recalculateDigest(InstitutionKeySubject subject, String algorithm) {
|
|
if (!"SM3".equalsIgnoreCase(algorithm != null ? algorithm.trim() : "")) {
|
|
scoreService.recordError(subject.getUserId());
|
|
throw new BusinessException(ErrorCode.VALIDATION_ERROR, "算法选择有误,错误数+1");
|
|
}
|
|
CentralWalletActivation a = getOrCreate(subject);
|
|
if (a.getSignVerifyResult() == null || !a.getSignVerifyResult().startsWith("通过")) {
|
|
scoreService.recordError(subject.getUserId());
|
|
throw new BusinessException(ErrorCode.VALIDATION_ERROR, "请先完成签名验证");
|
|
}
|
|
String plaintext = "ACTIVATE|" + a.getWalletId() + "|" + a.getOrgCode() + "|"
|
|
+ a.getContractId() + "|" + a.getActivateTime();
|
|
String newDigest = cryptography.sm3(plaintext);
|
|
a.recalculateDigest(newDigest);
|
|
activationRepository.update(a);
|
|
return new CentralWalletActivationPageResult(a);
|
|
}
|
|
|
|
@Transactional
|
|
public CentralWalletActivationPageResult matchDigest(InstitutionKeySubject subject) {
|
|
CentralWalletActivation a = getOrCreate(subject);
|
|
if (a.getRecalculatedDigest() == null) {
|
|
scoreService.recordError(subject.getUserId());
|
|
throw new BusinessException(ErrorCode.VALIDATION_ERROR, "请先计算新摘要值H2");
|
|
}
|
|
boolean matches = a.getRecalculatedDigest().equalsIgnoreCase(a.getDigest());
|
|
a.matchDigest(matches);
|
|
if (!matches) {
|
|
activationRepository.update(a);
|
|
scoreService.recordError(subject.getUserId());
|
|
throw new BusinessException(ErrorCode.VALIDATION_ERROR, "摘要不匹配,报文可能被篡改");
|
|
}
|
|
activationRepository.update(a);
|
|
return new CentralWalletActivationPageResult(a);
|
|
}
|
|
|
|
@Transactional
|
|
public CentralWalletActivationPageResult activateWallet(InstitutionKeySubject subject) {
|
|
CentralWalletActivation a = getOrCreate(subject);
|
|
if (a.getDigestMatchResult() == null || !a.getDigestMatchResult().startsWith("通过")) {
|
|
scoreService.recordError(subject.getUserId());
|
|
throw new BusinessException(ErrorCode.VALIDATION_ERROR, "请先完成摘要匹配校验");
|
|
}
|
|
a.activateWallet();
|
|
activationRepository.update(a);
|
|
scoreService.markStepCompleted(subject.getUserId(), 7);
|
|
return new CentralWalletActivationPageResult(a);
|
|
}
|
|
|
|
@Transactional
|
|
public String verifyFinalSignature(InstitutionKeySubject subject, String publicKeyInput, String algorithm) {
|
|
if (!"SM2".equalsIgnoreCase(algorithm != null ? algorithm.trim() : "")) {
|
|
scoreService.recordError(subject.getUserId());
|
|
throw new BusinessException(ErrorCode.VALIDATION_ERROR, "算法选择有误,错误数+1");
|
|
}
|
|
CentralWalletActivation a = getOrCreate(subject);
|
|
if (!a.isReceived()) {
|
|
scoreService.recordError(subject.getUserId());
|
|
throw new BusinessException(ErrorCode.VALIDATION_ERROR, "请先完成步骤四开通确认报文发送");
|
|
}
|
|
if (a.getStep5SignVerifyResult() != null && a.getStep5SignVerifyResult().startsWith("通过")) {
|
|
return "通过";
|
|
}
|
|
String storedPublicKey = institutionKeyService.commercialBankSecondPublicKeyOrNull(subject);
|
|
if (storedPublicKey == null) {
|
|
scoreService.recordError(subject.getUserId());
|
|
throw new BusinessException(ErrorCode.VALIDATION_ERROR, "商业银行第二公钥不存在,请先在模块一生成机构密钥");
|
|
}
|
|
boolean keyMatches = storedPublicKey.equalsIgnoreCase(publicKeyInput.trim());
|
|
if (!keyMatches) {
|
|
a.verifyFinalSignature(publicKeyInput, false);
|
|
activationRepository.update(a);
|
|
scoreService.recordError(subject.getUserId());
|
|
throw new BusinessException(ErrorCode.VALIDATION_ERROR, "选择的公钥与商业银行第二公钥不匹配");
|
|
}
|
|
boolean verified = cryptography.verify(storedPublicKey, a.getDigest(), a.getSignature());
|
|
a.verifyFinalSignature(publicKeyInput.trim(), verified);
|
|
if (!verified) {
|
|
activationRepository.update(a);
|
|
scoreService.recordError(subject.getUserId());
|
|
throw new BusinessException(ErrorCode.VALIDATION_ERROR, "签名验证失败");
|
|
}
|
|
activationRepository.update(a);
|
|
return "通过";
|
|
}
|
|
|
|
@Transactional
|
|
public String finalSign(InstitutionKeySubject subject, String algorithm,
|
|
String digestValue, String privateKeyInput) {
|
|
if (!"SM2".equalsIgnoreCase(algorithm != null ? algorithm.trim() : "")) {
|
|
scoreService.recordError(subject.getUserId());
|
|
throw new BusinessException(ErrorCode.VALIDATION_ERROR, "算法选择有误,错误数+1");
|
|
}
|
|
CentralWalletActivation a = getOrCreate(subject);
|
|
if (a.getStep5SignVerifyResult() == null || !a.getStep5SignVerifyResult().startsWith("通过")) {
|
|
scoreService.recordError(subject.getUserId());
|
|
throw new BusinessException(ErrorCode.VALIDATION_ERROR, "请先完成SM2签名验证");
|
|
}
|
|
// 支持重复点击:按最新摘要重新确权签名
|
|
if (!a.getDigest().equalsIgnoreCase(digestValue != null ? digestValue.trim() : "")) {
|
|
scoreService.recordError(subject.getUserId());
|
|
throw new BusinessException(ErrorCode.VALIDATION_ERROR, "摘要值与开通确认报文不一致");
|
|
}
|
|
String storedPrivateKey = institutionKeyService.centralBankFirstPrivateKeyOrNull(subject);
|
|
if (storedPrivateKey == null) {
|
|
scoreService.recordError(subject.getUserId());
|
|
throw new BusinessException(ErrorCode.VALIDATION_ERROR, "央行第一私钥不存在,请先生成央行密钥对");
|
|
}
|
|
if (!storedPrivateKey.equalsIgnoreCase(privateKeyInput.trim())) {
|
|
scoreService.recordError(subject.getUserId());
|
|
throw new BusinessException(ErrorCode.VALIDATION_ERROR, "签名密钥错误,请使用正确的央行第一私钥");
|
|
}
|
|
String sig = cryptography.sign(storedPrivateKey, a.getDigest());
|
|
String cbFinalSignature = "CB_FINAL_WALLET_" + sig;
|
|
a.finalSign(cbFinalSignature);
|
|
activationRepository.update(a);
|
|
return cbFinalSignature;
|
|
}
|
|
|
|
@Transactional
|
|
public String updateWalletRegisterStatus(InstitutionKeySubject subject) {
|
|
CentralWalletActivation a = getOrCreate(subject);
|
|
if (a.getCbFinalSignature() == null) {
|
|
scoreService.recordError(subject.getUserId());
|
|
throw new BusinessException(ErrorCode.VALIDATION_ERROR, "请先完成央行最终确权签名");
|
|
}
|
|
// 支持重复点击:重复更新钱包登记状态
|
|
a.updateWalletRegisterStatus();
|
|
activationRepository.update(a);
|
|
return "成功";
|
|
}
|
|
|
|
@Transactional
|
|
public FinalConfirmOutputResult outputFinalConfirm(InstitutionKeySubject subject) {
|
|
CentralWalletActivation a = getOrCreate(subject);
|
|
if (a.getWalletRegisterStatus() == null) {
|
|
scoreService.recordError(subject.getUserId());
|
|
throw new BusinessException(ErrorCode.VALIDATION_ERROR, "请先更新钱包登记状态");
|
|
}
|
|
// 支持重复点击:按最新确权签名重新输出最终确认报文
|
|
String date = LocalDateTime.now().format(DATE_FORMAT);
|
|
String finalConfirmId = "FINAL_CONFIRM_" + date + "_001";
|
|
String finalTime = LocalDateTime.now().format(TIMESTAMP_FORMAT);
|
|
String json = buildFinalConfirmJson(finalConfirmId, a.getWalletId(),
|
|
a.getCbFinalSignature(), finalTime);
|
|
a.outputFinalConfirm(finalConfirmId, json, finalTime);
|
|
activationRepository.update(a);
|
|
return new FinalConfirmOutputResult(
|
|
finalConfirmId, a.getWalletId(), "AVAILABLE",
|
|
a.getCbFinalSignature(), finalTime);
|
|
}
|
|
|
|
@Transactional
|
|
public String sendFinal(InstitutionKeySubject subject) {
|
|
CentralWalletActivation a = getOrCreate(subject);
|
|
if (a.getFinalConfirmJson() == null) {
|
|
scoreService.recordError(subject.getUserId());
|
|
throw new BusinessException(ErrorCode.VALIDATION_ERROR, "请先输出最终确权通知");
|
|
}
|
|
a.sendFinal();
|
|
activationRepository.update(a);
|
|
scoreService.markStepCompleted(subject.getUserId(), 5);
|
|
return "成功";
|
|
}
|
|
|
|
@Transactional(readOnly = true)
|
|
public UserFinalResult getUserFinalResult(InstitutionKeySubject subject) {
|
|
CentralWalletActivation a = activationRepository
|
|
.findLatest(subject.getUserId(), subject.getSchoolId(), subject.getClassId()).orElse(null);
|
|
if (a == null || !a.isFinalSent()) {
|
|
return null;
|
|
}
|
|
WalletApplication app = applicationRepository
|
|
.findLatest(subject.getUserId(), subject.getSchoolId(), subject.getClassId()).orElse(null);
|
|
WalletIdentifierGeneration ident = identifierRepository
|
|
.findLatest(subject.getUserId(), subject.getSchoolId(), subject.getClassId()).orElse(null);
|
|
SmartContractGeneration contract = contractRepository
|
|
.findLatest(subject.getUserId(), subject.getSchoolId(), subject.getClassId()).orElse(null);
|
|
CentralWalletRegistration centralReg = centralRegistrationRepository
|
|
.findLatest(subject.getUserId(), subject.getSchoolId(), subject.getClassId()).orElse(null);
|
|
return UserFinalResult.build(a, app, ident, contract, centralReg);
|
|
}
|
|
|
|
private CentralWalletActivation getOrCreate(InstitutionKeySubject subject) {
|
|
CentralWalletActivation a = activationRepository
|
|
.findLatest(subject.getUserId(), subject.getSchoolId(), subject.getClassId())
|
|
.orElse(null);
|
|
if (a == null) {
|
|
a = activationRepository.save(
|
|
CentralWalletActivation.create(subject.getUserId(), subject.getSchoolId(), subject.getClassId()));
|
|
}
|
|
if (!a.isReceived()) {
|
|
SmartContractGeneration c = contractRepository
|
|
.findLatest(subject.getUserId(), subject.getSchoolId(), subject.getClassId()).orElse(null);
|
|
if (c != null && c.isSent()) {
|
|
a.receive(c.getConfirmId(), c.getWalletId(), c.getOrgCode(), c.getContractId(),
|
|
c.getActivateDigest(), c.getActivateSignature(), c.getActivateTimestamp(), c.getConfirmJson());
|
|
activationRepository.update(a);
|
|
}
|
|
}
|
|
return a;
|
|
}
|
|
|
|
private String buildFinalConfirmJson(String finalConfirmId, String walletId,
|
|
String cbFinalSignature, String finalTime) {
|
|
return "{\n"
|
|
+ " \"finalConfirmId\": \"" + finalConfirmId + "\",\n"
|
|
+ " \"walletId\": \"" + walletId + "\",\n"
|
|
+ " \"status\": \"AVAILABLE\",\n"
|
|
+ " \"cbFinalSignature\": \"" + cbFinalSignature + "\",\n"
|
|
+ " \"finalTime\": \"" + finalTime + "\"\n"
|
|
+ "}";
|
|
}
|
|
}
|