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" + "}"; } }