944 lines
33 KiB
Go
944 lines
33 KiB
Go
package certification
|
||
|
||
import (
|
||
"context"
|
||
"fmt"
|
||
"io"
|
||
"net/http"
|
||
"time"
|
||
|
||
"tyapi-server/internal/application/certification/dto/commands"
|
||
"tyapi-server/internal/application/certification/dto/queries"
|
||
"tyapi-server/internal/application/certification/dto/responses"
|
||
api_service "tyapi-server/internal/domains/api/services"
|
||
"tyapi-server/internal/domains/certification/entities"
|
||
certification_value_objects "tyapi-server/internal/domains/certification/entities/value_objects"
|
||
"tyapi-server/internal/domains/certification/enums"
|
||
"tyapi-server/internal/domains/certification/repositories"
|
||
"tyapi-server/internal/domains/certification/services"
|
||
finance_service "tyapi-server/internal/domains/finance/services"
|
||
user_entities "tyapi-server/internal/domains/user/entities"
|
||
user_service "tyapi-server/internal/domains/user/services"
|
||
"tyapi-server/internal/infrastructure/external/storage"
|
||
"tyapi-server/internal/shared/database"
|
||
"tyapi-server/internal/shared/esign"
|
||
|
||
"go.uber.org/zap"
|
||
)
|
||
|
||
// CertificationApplicationServiceImpl 认证应用服务实现
|
||
// 负责用例协调,DTO转换,是应用层的核心组件
|
||
type CertificationApplicationServiceImpl struct {
|
||
// 领域服务依赖
|
||
aggregateService services.CertificationAggregateService
|
||
userAggregateService user_service.UserAggregateService
|
||
smsCodeService *user_service.SMSCodeService
|
||
esignClient *esign.Client
|
||
esignConfig *esign.Config
|
||
qiniuStorageService *storage.QiNiuStorageService
|
||
contractAggregateService user_service.ContractAggregateService
|
||
walletAggregateService finance_service.WalletAggregateService
|
||
apiUserAggregateService api_service.ApiUserAggregateService
|
||
enterpriseInfoSubmitRecordService *services.EnterpriseInfoSubmitRecordService
|
||
// 仓储依赖
|
||
queryRepository repositories.CertificationQueryRepository
|
||
enterpriseInfoSubmitRecordRepo repositories.EnterpriseInfoSubmitRecordRepository
|
||
txManager *database.TransactionManager
|
||
|
||
logger *zap.Logger
|
||
}
|
||
|
||
// NewCertificationApplicationService 创建认证应用服务
|
||
func NewCertificationApplicationService(
|
||
aggregateService services.CertificationAggregateService,
|
||
userAggregateService user_service.UserAggregateService,
|
||
queryRepository repositories.CertificationQueryRepository,
|
||
enterpriseInfoSubmitRecordRepo repositories.EnterpriseInfoSubmitRecordRepository,
|
||
smsCodeService *user_service.SMSCodeService,
|
||
esignClient *esign.Client,
|
||
esignConfig *esign.Config,
|
||
qiniuStorageService *storage.QiNiuStorageService,
|
||
contractAggregateService user_service.ContractAggregateService,
|
||
walletAggregateService finance_service.WalletAggregateService,
|
||
apiUserAggregateService api_service.ApiUserAggregateService,
|
||
enterpriseInfoSubmitRecordService *services.EnterpriseInfoSubmitRecordService,
|
||
txManager *database.TransactionManager,
|
||
logger *zap.Logger,
|
||
) CertificationApplicationService {
|
||
return &CertificationApplicationServiceImpl{
|
||
aggregateService: aggregateService,
|
||
userAggregateService: userAggregateService,
|
||
queryRepository: queryRepository,
|
||
enterpriseInfoSubmitRecordRepo: enterpriseInfoSubmitRecordRepo,
|
||
smsCodeService: smsCodeService,
|
||
esignClient: esignClient,
|
||
esignConfig: esignConfig,
|
||
qiniuStorageService: qiniuStorageService,
|
||
contractAggregateService: contractAggregateService,
|
||
walletAggregateService: walletAggregateService,
|
||
apiUserAggregateService: apiUserAggregateService,
|
||
enterpriseInfoSubmitRecordService: enterpriseInfoSubmitRecordService,
|
||
txManager: txManager,
|
||
logger: logger,
|
||
}
|
||
}
|
||
|
||
// ================ 用户操作用例 ================
|
||
|
||
// SubmitEnterpriseInfo 提交企业信息
|
||
func (s *CertificationApplicationServiceImpl) SubmitEnterpriseInfo(
|
||
ctx context.Context,
|
||
cmd *commands.SubmitEnterpriseInfoCommand,
|
||
) (*responses.CertificationResponse, error) {
|
||
s.logger.Info("开始提交企业信息",
|
||
zap.String("user_id", cmd.UserID))
|
||
|
||
// 验证验证码
|
||
if err := s.smsCodeService.VerifyCode(ctx, cmd.LegalPersonPhone, cmd.VerificationCode, user_entities.SMSSceneCertification); err != nil {
|
||
return nil, fmt.Errorf("验证码错误或已过期")
|
||
}
|
||
s.logger.Info("开始处理企业信息提交",
|
||
zap.String("user_id", cmd.UserID))
|
||
// 1. 检查企业信息是否重复(统一社会信用代码,已经认证了的,不能重复提交)
|
||
exists, err := s.userAggregateService.CheckUnifiedSocialCodeExists(ctx, cmd.UnifiedSocialCode, cmd.UserID)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("检查企业信息失败: %s", err.Error())
|
||
}
|
||
if exists {
|
||
return nil, fmt.Errorf("该企业信息已被其他用户使用,请确认企业信息是否正确")
|
||
}
|
||
|
||
// 1.5 插入企业信息提交记录
|
||
record := entities.NewEnterpriseInfoSubmitRecord(
|
||
cmd.UserID,
|
||
cmd.CompanyName,
|
||
cmd.UnifiedSocialCode,
|
||
cmd.LegalPersonName,
|
||
cmd.LegalPersonID,
|
||
cmd.LegalPersonPhone,
|
||
cmd.EnterpriseAddress,
|
||
cmd.EnterpriseEmail,
|
||
)
|
||
enterpriseInfo := &certification_value_objects.EnterpriseInfo{
|
||
CompanyName: cmd.CompanyName,
|
||
UnifiedSocialCode: cmd.UnifiedSocialCode,
|
||
LegalPersonName: cmd.LegalPersonName,
|
||
LegalPersonID: cmd.LegalPersonID,
|
||
LegalPersonPhone: cmd.LegalPersonPhone,
|
||
EnterpriseAddress: cmd.EnterpriseAddress,
|
||
EnterpriseEmail: cmd.EnterpriseEmail,
|
||
}
|
||
err = enterpriseInfo.Validate()
|
||
if err != nil {
|
||
s.logger.Error("企业信息验证失败", zap.Error(err))
|
||
record.MarkAsFailed(err.Error())
|
||
return nil, fmt.Errorf("企业信息验证失败: %s", err.Error())
|
||
}
|
||
err = s.enterpriseInfoSubmitRecordService.ValidateWithWestdex(ctx, enterpriseInfo)
|
||
if err != nil {
|
||
s.logger.Error("企业信息验证失败", zap.Error(err))
|
||
record.MarkAsFailed(err.Error())
|
||
saveErr := s.enterpriseInfoSubmitRecordService.Save(ctx, record)
|
||
if saveErr != nil {
|
||
s.logger.Error("保存企业信息提交记录失败", zap.Error(saveErr))
|
||
}
|
||
return nil, fmt.Errorf("企业信息验证失败, %s", err.Error())
|
||
}
|
||
record.MarkAsVerified()
|
||
err = s.enterpriseInfoSubmitRecordService.Save(ctx, record)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("保存企业信息提交记录失败: %s", err.Error())
|
||
}
|
||
|
||
var response *responses.CertificationResponse
|
||
err = s.txManager.ExecuteInTx(ctx, func(txCtx context.Context) error {
|
||
// 2. 检查用户认证是否存在
|
||
existsCert, err := s.aggregateService.ExistsByUserID(txCtx, cmd.UserID)
|
||
if err != nil {
|
||
return fmt.Errorf("检查用户认证是否存在失败: %s", err.Error())
|
||
}
|
||
if !existsCert {
|
||
// 创建
|
||
_, err := s.aggregateService.CreateCertification(txCtx, cmd.UserID)
|
||
if err != nil {
|
||
return fmt.Errorf("创建认证信息失败: %s", err.Error())
|
||
}
|
||
}
|
||
|
||
// 3. 加载认证聚合根
|
||
cert, err := s.aggregateService.LoadCertificationByUserID(txCtx, cmd.UserID)
|
||
if err != nil {
|
||
return fmt.Errorf("加载认证信息失败: %s", err.Error())
|
||
}
|
||
|
||
// 3. 调用e签宝看是否进行过认证
|
||
respMeta := map[string]interface{}{}
|
||
|
||
identity, err := s.esignClient.QueryOrgIdentityInfo(&esign.QueryOrgIdentityRequest{
|
||
OrgName: cmd.CompanyName,
|
||
})
|
||
if identity != nil && identity.Data.RealnameStatus == 1 {
|
||
// 已提交
|
||
err = cert.SubmitEnterpriseInfo(enterpriseInfo, "", "")
|
||
if err != nil {
|
||
return fmt.Errorf("提交企业认证信息失败: %s", err.Error())
|
||
}
|
||
s.logger.Info("企业认证成功", zap.Any("identity", identity))
|
||
|
||
// 完成企业认证流程
|
||
err = s.completeEnterpriseVerification(txCtx, cert, cmd.UserID, cmd.CompanyName, cmd.LegalPersonName)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
respMeta = map[string]interface{}{
|
||
"enterprise_info": enterpriseInfo,
|
||
"next_action": "企业已认证,可进行后续操作",
|
||
}
|
||
} else {
|
||
if err != nil {
|
||
s.logger.Error("e签宝查询企业认证信息失败或未进行企业认证", zap.Error(err))
|
||
}
|
||
authURL, err := s.esignClient.GenerateEnterpriseAuth(&esign.EnterpriseAuthRequest{
|
||
CompanyName: enterpriseInfo.CompanyName,
|
||
UnifiedSocialCode: enterpriseInfo.UnifiedSocialCode,
|
||
LegalPersonName: enterpriseInfo.LegalPersonName,
|
||
LegalPersonID: enterpriseInfo.LegalPersonID,
|
||
TransactorName: enterpriseInfo.LegalPersonName,
|
||
TransactorMobile: enterpriseInfo.LegalPersonPhone,
|
||
TransactorID: enterpriseInfo.LegalPersonID,
|
||
})
|
||
if err != nil {
|
||
s.logger.Error("生成企业认证链接失败", zap.Error(err))
|
||
return fmt.Errorf("生成企业认证链接失败: %s", err.Error())
|
||
}
|
||
err = cert.SubmitEnterpriseInfo(enterpriseInfo, authURL.AuthShortURL, authURL.AuthFlowID)
|
||
if err != nil {
|
||
return fmt.Errorf("提交企业认证信息失败: %s", err.Error())
|
||
}
|
||
respMeta = map[string]interface{}{
|
||
"enterprise_info": enterpriseInfo,
|
||
"authUrl": authURL.AuthURL,
|
||
"next_action": "请完成企业认证",
|
||
}
|
||
}
|
||
|
||
err = s.aggregateService.SaveCertification(txCtx, cert)
|
||
if err != nil {
|
||
return fmt.Errorf("保存认证信息失败: %s", err.Error())
|
||
}
|
||
// 5. 转换为响应DTO
|
||
response = s.convertToResponse(cert)
|
||
|
||
// 6. 添加工作流结果信息
|
||
if respMeta != nil {
|
||
response.Metadata = respMeta
|
||
}
|
||
|
||
return nil
|
||
})
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
s.logger.Info("企业信息提交成功", zap.String("user_id", cmd.UserID))
|
||
return response, nil
|
||
}
|
||
|
||
// ConfirmAuth 确认认证状态
|
||
func (s *CertificationApplicationServiceImpl) ConfirmAuth(
|
||
ctx context.Context,
|
||
cmd *queries.ConfirmAuthCommand,
|
||
) (*responses.ConfirmAuthResponse, error) {
|
||
s.logger.Info("开始确认状态", zap.String("user_id", cmd.UserID))
|
||
cert, err := s.aggregateService.LoadCertificationByUserID(ctx, cmd.UserID)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("加载认证信息失败: %s", err.Error())
|
||
}
|
||
|
||
// 企业认证
|
||
if cert.Status != enums.StatusInfoSubmitted {
|
||
return nil, fmt.Errorf("认证状态不正确,当前状态: %s", enums.GetStatusName(cert.Status))
|
||
}
|
||
record, err := s.enterpriseInfoSubmitRecordRepo.FindLatestByUserID(ctx, cert.UserID)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("查找企业信息失败: %w", err)
|
||
}
|
||
identity, err := s.esignClient.QueryOrgIdentityInfo(&esign.QueryOrgIdentityRequest{
|
||
OrgName: record.CompanyName,
|
||
})
|
||
if err != nil {
|
||
s.logger.Error("查询企业认证信息失败", zap.Error(err))
|
||
return nil, fmt.Errorf("查询企业认证信息失败: %w", err)
|
||
}
|
||
reason := ""
|
||
if identity != nil && identity.Data.RealnameStatus == 1 {
|
||
err = s.txManager.ExecuteInTx(ctx, func(txCtx context.Context) error {
|
||
err = s.completeEnterpriseVerification(txCtx, cert, cert.UserID, record.CompanyName, record.LegalPersonName)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
reason = "企业认证成功"
|
||
return nil
|
||
})
|
||
if err != nil {
|
||
return nil, fmt.Errorf("完成企业认证失败: %w", err)
|
||
}
|
||
} else {
|
||
reason = "企业未完成"
|
||
}
|
||
return &responses.ConfirmAuthResponse{
|
||
Status: cert.Status,
|
||
Reason: reason,
|
||
}, nil
|
||
}
|
||
|
||
// ConfirmSign 确认签署状态
|
||
func (s *CertificationApplicationServiceImpl) ConfirmSign(
|
||
ctx context.Context,
|
||
cmd *queries.ConfirmSignCommand,
|
||
) (*responses.ConfirmSignResponse, error) {
|
||
cert, err := s.aggregateService.LoadCertificationByUserID(ctx, cmd.UserID)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("加载认证信息失败: %s", err.Error())
|
||
}
|
||
|
||
reason, err := s.checkAndUpdateSignStatus(ctx, cert)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("确认签署状态失败: %w", err)
|
||
}
|
||
|
||
return &responses.ConfirmSignResponse{
|
||
Status: cert.Status,
|
||
Reason: reason,
|
||
}, nil
|
||
}
|
||
|
||
// ApplyContract 申请合同签署
|
||
func (s *CertificationApplicationServiceImpl) ApplyContract(
|
||
ctx context.Context,
|
||
cmd *commands.ApplyContractCommand,
|
||
) (*responses.ContractSignUrlResponse, error) {
|
||
s.logger.Info("开始申请合同签署",
|
||
zap.String("user_id", cmd.UserID))
|
||
|
||
// 1. 验证命令完整性
|
||
if err := s.validateApplyContractCommand(cmd); err != nil {
|
||
return nil, fmt.Errorf("命令验证失败: %s", err.Error())
|
||
}
|
||
|
||
// 2. 加载认证聚合根
|
||
cert, err := s.aggregateService.LoadCertificationByUserID(ctx, cmd.UserID)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("加载认证信息失败: %s", err.Error())
|
||
}
|
||
|
||
// 3. 验证业务前置条件
|
||
if err := s.validateContractApplicationPreconditions(cert, cmd.UserID); err != nil {
|
||
return nil, fmt.Errorf("业务前置条件验证失败: %s", err.Error())
|
||
}
|
||
|
||
// 5. 生成合同和签署链接
|
||
enterpriseInfo, err := s.userAggregateService.GetUserWithEnterpriseInfo(ctx, cmd.UserID)
|
||
if err != nil {
|
||
s.logger.Error("获取企业信息失败", zap.Error(err))
|
||
return nil, fmt.Errorf("获取企业信息失败: %w", err)
|
||
}
|
||
contractInfo, err := s.generateContractAndSignURL(ctx, cert, enterpriseInfo.EnterpriseInfo)
|
||
if err != nil {
|
||
s.logger.Error("生成合同失败", zap.Error(err))
|
||
return nil, fmt.Errorf("生成合同失败: %s", err.Error())
|
||
}
|
||
err = cert.ApplyContract(contractInfo.EsignFlowID, contractInfo.ContractSignURL)
|
||
if err != nil {
|
||
s.logger.Error("合同申请状态转换失败", zap.Error(err))
|
||
return nil, fmt.Errorf("合同申请失败: %s", err.Error())
|
||
}
|
||
|
||
// 7. 保存认证信息
|
||
err = s.aggregateService.SaveCertification(ctx, cert)
|
||
if err != nil {
|
||
s.logger.Error("保存认证信息失败", zap.Error(err))
|
||
return nil, fmt.Errorf("保存认证信息失败: %s", err.Error())
|
||
}
|
||
|
||
// 8. 构建响应
|
||
response := responses.NewContractSignUrlResponse(
|
||
cert.ID,
|
||
contractInfo.ContractSignURL,
|
||
contractInfo.ContractURL,
|
||
"请在规定时间内完成合同签署",
|
||
"合同申请成功",
|
||
)
|
||
|
||
s.logger.Info("合同申请成功", zap.String("user_id", cmd.UserID))
|
||
return response, nil
|
||
}
|
||
|
||
// ================ 查询用例 ================
|
||
|
||
// GetCertification 获取认证详情
|
||
func (s *CertificationApplicationServiceImpl) GetCertification(
|
||
ctx context.Context,
|
||
query *queries.GetCertificationQuery,
|
||
) (*responses.CertificationResponse, error) {
|
||
s.logger.Debug("获取认证详情", zap.String("user_id", query.UserID))
|
||
|
||
// 1. 检查用户认证是否存在
|
||
exists, err := s.aggregateService.ExistsByUserID(ctx, query.UserID)
|
||
if err != nil {
|
||
s.logger.Error("获取认证信息失败", zap.Error(err))
|
||
return nil, fmt.Errorf("获取认证信息失败: %w", err)
|
||
}
|
||
|
||
var cert *entities.Certification
|
||
if !exists {
|
||
// 创建新的认证记录
|
||
cert, err = s.aggregateService.CreateCertification(ctx, query.UserID)
|
||
if err != nil {
|
||
s.logger.Error("创建认证信息失败", zap.Error(err))
|
||
return nil, fmt.Errorf("创建认证信息失败: %w", err)
|
||
}
|
||
} else {
|
||
// 加载现有认证记录
|
||
cert, err = s.aggregateService.LoadCertificationByUserID(ctx, query.UserID)
|
||
if err != nil {
|
||
s.logger.Error("加载认证信息失败", zap.Error(err))
|
||
return nil, fmt.Errorf("加载认证信息失败: %w", err)
|
||
}
|
||
}
|
||
|
||
// 2. 检查是否需要更新合同文件
|
||
if cert.IsContractFileNeedUpdate() {
|
||
err = s.updateContractFile(ctx, cert)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
}
|
||
|
||
if cert.Status == enums.StatusInfoSubmitted {
|
||
err = s.checkAndCompleteEnterpriseVerification(ctx, cert)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
}
|
||
if cert.Status == enums.StatusContractApplied {
|
||
_, err = s.checkAndUpdateSignStatus(ctx, cert)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
}
|
||
// 2. 转换为响应DTO
|
||
response := s.convertToResponse(cert)
|
||
|
||
// 3. 添加状态相关的元数据
|
||
meta := cert.GetDataByStatus()
|
||
if meta != nil {
|
||
response.Metadata = meta
|
||
}
|
||
|
||
s.logger.Info("获取认证详情成功", zap.String("user_id", query.UserID))
|
||
return response, nil
|
||
}
|
||
|
||
// ListCertifications 获取认证列表(管理员)
|
||
func (s *CertificationApplicationServiceImpl) ListCertifications(
|
||
ctx context.Context,
|
||
query *queries.ListCertificationsQuery,
|
||
) (*responses.CertificationListResponse, error) {
|
||
s.logger.Debug("获取认证列表(管理员)")
|
||
|
||
// 1. 转换为领域查询对象
|
||
domainQuery := query.ToDomainQuery()
|
||
|
||
// 2. 执行查询
|
||
certs, total, err := s.queryRepository.List(ctx, domainQuery)
|
||
if err != nil {
|
||
s.logger.Error("查询认证列表失败", zap.Error(err))
|
||
return nil, fmt.Errorf("查询认证列表失败: %w", err)
|
||
}
|
||
|
||
// 3. 转换为响应DTO
|
||
items := make([]*responses.CertificationResponse, len(certs))
|
||
for i, cert := range certs {
|
||
items[i] = s.convertToResponse(cert)
|
||
}
|
||
|
||
// 4. 构建列表响应
|
||
response := responses.NewCertificationListResponse(items, total, query.Page, query.PageSize)
|
||
|
||
return response, nil
|
||
}
|
||
|
||
// ================ e签宝回调处理 ================
|
||
|
||
// HandleEsignCallback 处理e签宝回调
|
||
func (s *CertificationApplicationServiceImpl) HandleEsignCallback(
|
||
ctx context.Context,
|
||
cmd *commands.EsignCallbackCommand,
|
||
) error {
|
||
// if err := esign.VerifySignature(cmd.Data, cmd.Headers, cmd.QueryParams, s.esignConfig.AppSecret); err != nil {
|
||
// return fmt.Errorf("e签宝回调验签失败: %w", err)
|
||
// }
|
||
// 4. 根据回调类型处理业务逻辑
|
||
switch cmd.Data.Action {
|
||
case "AUTH_PASS":
|
||
// 只处理企业认证通过
|
||
if cmd.Data.AuthType == "ORG" {
|
||
err := s.txManager.ExecuteInTx(ctx, func(txCtx context.Context) error {
|
||
|
||
// 1. 根据AuthFlowId加载认证信息
|
||
cert, err := s.aggregateService.LoadCertificationByAuthFlowId(txCtx, cmd.Data.AuthFlowId)
|
||
if err != nil {
|
||
return fmt.Errorf("加载认证信息失败: %s", err.Error())
|
||
}
|
||
if cmd.Data.Organization == nil || cmd.Data.Organization.OrgName == "" {
|
||
return fmt.Errorf("组织信息为空")
|
||
}
|
||
if cert.Status != enums.StatusInfoSubmitted {
|
||
return fmt.Errorf("认证状态不正确")
|
||
}
|
||
// 2. 完成企业认证
|
||
err = cert.CompleteEnterpriseVerification()
|
||
if err != nil {
|
||
return fmt.Errorf("完成企业认证失败: %s", err.Error())
|
||
}
|
||
|
||
record, err := s.enterpriseInfoSubmitRecordRepo.FindLatestByUserID(txCtx, cert.UserID)
|
||
if err != nil {
|
||
s.logger.Error("查找已认证企业信息失败", zap.Error(err))
|
||
return fmt.Errorf("查找已认证企业信息失败: %w", err)
|
||
}
|
||
// 5. 写入用户域
|
||
err = s.userAggregateService.CreateOrUpdateEnterpriseInfo(
|
||
txCtx,
|
||
record.UserID,
|
||
record.CompanyName,
|
||
record.UnifiedSocialCode,
|
||
record.LegalPersonName,
|
||
record.LegalPersonID,
|
||
record.LegalPersonPhone,
|
||
record.EnterpriseAddress,
|
||
record.EnterpriseEmail,
|
||
)
|
||
if err != nil {
|
||
s.logger.Error("同步企业信息到用户域失败", zap.Error(err))
|
||
return fmt.Errorf("同步企业信息到用户域失败: %w", err)
|
||
}
|
||
|
||
// 生成合同
|
||
err = s.generateAndAddContractFile(txCtx, cert, record.CompanyName, record.LegalPersonName, record.UnifiedSocialCode, record.EnterpriseAddress, record.LegalPersonPhone, record.LegalPersonID, record.EnterpriseEmail)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
// 3. 保存认证信息
|
||
err = s.aggregateService.SaveCertification(txCtx, cert)
|
||
if err != nil {
|
||
return fmt.Errorf("保存认证信息失败: %s", err.Error())
|
||
}
|
||
s.logger.Info("完成企业认证", zap.String("certification_id", cert.ID))
|
||
|
||
return nil
|
||
})
|
||
if err != nil {
|
||
s.logger.Error("完成企业认证失败", zap.Error(err))
|
||
return fmt.Errorf("完成企业认证失败: %w", err)
|
||
}
|
||
}
|
||
return nil
|
||
|
||
default:
|
||
s.logger.Info("忽略未知的回调动作", zap.String("action", cmd.Data.Action))
|
||
return nil
|
||
}
|
||
}
|
||
|
||
// ================ 辅助方法 ================
|
||
|
||
// convertToResponse 转换实体为响应DTO
|
||
func (s *CertificationApplicationServiceImpl) convertToResponse(cert *entities.Certification) *responses.CertificationResponse {
|
||
response := &responses.CertificationResponse{
|
||
ID: cert.ID,
|
||
UserID: cert.UserID,
|
||
Status: cert.Status,
|
||
StatusName: enums.GetStatusName(cert.Status),
|
||
Progress: cert.GetProgress(),
|
||
CreatedAt: cert.CreatedAt,
|
||
UpdatedAt: cert.UpdatedAt,
|
||
InfoSubmittedAt: cert.InfoSubmittedAt,
|
||
EnterpriseVerifiedAt: cert.EnterpriseVerifiedAt,
|
||
ContractAppliedAt: cert.ContractAppliedAt,
|
||
ContractSignedAt: cert.ContractSignedAt,
|
||
CompletedAt: cert.CompletedAt,
|
||
IsCompleted: cert.IsCompleted(),
|
||
IsFailed: enums.IsFailureStatus(cert.Status),
|
||
IsUserActionRequired: cert.IsUserActionRequired(),
|
||
NextAction: enums.GetUserActionHint(cert.Status),
|
||
AvailableActions: cert.GetAvailableActions(),
|
||
RetryCount: cert.RetryCount,
|
||
Metadata: make(map[string]interface{}),
|
||
}
|
||
|
||
// 设置企业信息(从认证实体中构建)
|
||
// TODO: 这里需要从企业信息服务或其他地方获取完整的企业信息
|
||
// response.EnterpriseInfo = cert.EnterpriseInfo
|
||
|
||
// 设置合同信息(从认证实体中构建)
|
||
if cert.ContractFileID != "" || cert.EsignFlowID != "" {
|
||
// TODO: 从认证实体字段构建合同信息值对象
|
||
// response.ContractInfo = &value_objects.ContractInfo{...}
|
||
}
|
||
|
||
// 设置失败信息
|
||
if enums.IsFailureStatus(cert.Status) {
|
||
response.FailureReason = cert.FailureReason
|
||
response.FailureReasonName = enums.GetFailureReasonName(cert.FailureReason)
|
||
response.FailureMessage = cert.FailureMessage
|
||
response.CanRetry = enums.IsRetryable(cert.FailureReason)
|
||
}
|
||
|
||
return response
|
||
}
|
||
|
||
// validateApplyContractCommand 验证申请合同命令
|
||
func (s *CertificationApplicationServiceImpl) validateApplyContractCommand(cmd *commands.ApplyContractCommand) error {
|
||
if cmd.UserID == "" {
|
||
return fmt.Errorf("用户ID不能为空")
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// validateContractApplicationPreconditions 验证合同申请前置条件
|
||
func (s *CertificationApplicationServiceImpl) validateContractApplicationPreconditions(cert *entities.Certification, userID string) error {
|
||
if cert.UserID != userID {
|
||
return fmt.Errorf("用户无权限操作此认证申请")
|
||
}
|
||
if cert.Status != enums.StatusEnterpriseVerified {
|
||
return fmt.Errorf("必须先完成企业认证才能申请合同")
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// generateContractAndSignURL 生成合同和签署链接
|
||
func (s *CertificationApplicationServiceImpl) generateContractAndSignURL(ctx context.Context, cert *entities.Certification, enterpriseInfo *user_entities.EnterpriseInfo) (*certification_value_objects.ContractInfo, error) {
|
||
// 发起签署流程
|
||
signFlowID, err := s.esignClient.CreateSignFlow(&esign.CreateSignFlowRequest{
|
||
FileID: cert.ContractFileID,
|
||
SignerAccount: enterpriseInfo.UnifiedSocialCode,
|
||
SignerName: enterpriseInfo.CompanyName,
|
||
TransactorPhone: enterpriseInfo.LegalPersonPhone,
|
||
TransactorName: enterpriseInfo.LegalPersonName,
|
||
TransactorIDCardNum: enterpriseInfo.LegalPersonID,
|
||
})
|
||
if err != nil {
|
||
return nil, fmt.Errorf("生成合同失败: %s", err.Error())
|
||
}
|
||
|
||
_, shortUrl, err := s.esignClient.GetSignURL(signFlowID, enterpriseInfo.LegalPersonPhone, enterpriseInfo.CompanyName)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("获取签署链接失败: %s", err.Error())
|
||
}
|
||
return &certification_value_objects.ContractInfo{
|
||
ContractFileID: cert.ContractFileID,
|
||
EsignFlowID: signFlowID,
|
||
ContractSignURL: shortUrl,
|
||
}, nil
|
||
}
|
||
|
||
// ================ 重构后的公共方法 ================
|
||
|
||
// completeEnterpriseVerification 完成企业认证的公共方法
|
||
func (s *CertificationApplicationServiceImpl) completeEnterpriseVerification(
|
||
ctx context.Context,
|
||
cert *entities.Certification,
|
||
userID string,
|
||
companyName string,
|
||
legalPersonName string,
|
||
) error {
|
||
// 完成企业认证
|
||
err := cert.CompleteEnterpriseVerification()
|
||
if err != nil {
|
||
s.logger.Error("完成企业认证失败", zap.Error(err))
|
||
return fmt.Errorf("完成企业认证失败: %w", err)
|
||
}
|
||
|
||
// 保存企业信息到用户域
|
||
record, err := s.enterpriseInfoSubmitRecordRepo.FindLatestByUserID(ctx, userID)
|
||
if err != nil {
|
||
s.logger.Error("查找企业信息失败", zap.Error(err))
|
||
return fmt.Errorf("查找企业信息失败: %w", err)
|
||
}
|
||
|
||
err = s.userAggregateService.CreateEnterpriseInfo(
|
||
ctx,
|
||
userID,
|
||
record.CompanyName,
|
||
record.UnifiedSocialCode,
|
||
record.LegalPersonName,
|
||
record.LegalPersonID,
|
||
record.LegalPersonPhone,
|
||
record.EnterpriseAddress,
|
||
record.EnterpriseEmail,
|
||
)
|
||
if err != nil {
|
||
s.logger.Error("保存企业信息到用户域失败", zap.Error(err))
|
||
return fmt.Errorf("保存企业信息失败: %s", err.Error())
|
||
} else {
|
||
s.logger.Info("企业信息已保存到用户域", zap.String("user_id", userID))
|
||
}
|
||
|
||
// 生成合同
|
||
err = s.generateAndAddContractFile(ctx, cert, record.CompanyName, record.LegalPersonName, record.UnifiedSocialCode, record.EnterpriseAddress, record.LegalPersonPhone, record.LegalPersonID, record.EnterpriseEmail)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
// 保存认证信息
|
||
err = s.aggregateService.SaveCertification(ctx, cert)
|
||
if err != nil {
|
||
s.logger.Error("保存认证信息失败", zap.Error(err))
|
||
return fmt.Errorf("保存认证信息失败: %w", err)
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// generateAndAddContractFile 生成并添加合同文件的公共方法
|
||
func (s *CertificationApplicationServiceImpl) generateAndAddContractFile(
|
||
ctx context.Context,
|
||
cert *entities.Certification,
|
||
companyName string,
|
||
legalPersonName string,
|
||
unifiedSocialCode string,
|
||
enterpriseAddress string,
|
||
legalPersonPhone string,
|
||
legalPersonID string,
|
||
enterpriseEmail string,
|
||
) error {
|
||
fileComponent := map[string]string{
|
||
"YFCompanyName": companyName,
|
||
"YFCompanyName2": companyName,
|
||
"YFLegalPersonName": legalPersonName,
|
||
"YFLegalPersonName2": legalPersonName,
|
||
"YFUnifiedSocialCode": unifiedSocialCode,
|
||
"YFEnterpriseAddress": enterpriseAddress,
|
||
"YFContactPerson": legalPersonName,
|
||
"YFMobile": legalPersonPhone,
|
||
"YFEmail": enterpriseEmail,
|
||
"SignDate": time.Now().Format("2006年01月02日"),
|
||
"SignDate2": time.Now().Format("2006年01月02日"),
|
||
"SignDate3": time.Now().Format("2006年01月02日"),
|
||
}
|
||
fillTemplateResp, err := s.esignClient.FillTemplate(fileComponent)
|
||
if err != nil {
|
||
s.logger.Error("生成合同失败", zap.Error(err))
|
||
return fmt.Errorf("生成合同失败: %s", err.Error())
|
||
}
|
||
err = cert.AddContractFileID(fillTemplateResp.FileID, fillTemplateResp.FileDownloadUrl)
|
||
if err != nil {
|
||
s.logger.Error("加入合同文件ID链接失败", zap.Error(err))
|
||
return fmt.Errorf("加入合同文件ID链接失败: %s", err.Error())
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// updateContractFile 更新合同文件的公共方法
|
||
func (s *CertificationApplicationServiceImpl) updateContractFile(ctx context.Context, cert *entities.Certification) error {
|
||
// 获取企业信息
|
||
enterpriseInfo, err := s.userAggregateService.GetUserWithEnterpriseInfo(ctx, cert.UserID)
|
||
if err != nil {
|
||
s.logger.Error("获取企业信息失败", zap.Error(err))
|
||
return fmt.Errorf("获取企业信息失败: %w", err)
|
||
}
|
||
|
||
// 生成合同
|
||
err = s.generateAndAddContractFile(ctx, cert, enterpriseInfo.EnterpriseInfo.CompanyName, enterpriseInfo.EnterpriseInfo.LegalPersonName, enterpriseInfo.EnterpriseInfo.UnifiedSocialCode, enterpriseInfo.EnterpriseInfo.EnterpriseAddress, enterpriseInfo.EnterpriseInfo.LegalPersonPhone, enterpriseInfo.EnterpriseInfo.LegalPersonID, enterpriseInfo.EnterpriseInfo.EnterpriseEmail)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
// 更新认证信息
|
||
err = s.aggregateService.SaveCertification(ctx, cert)
|
||
if err != nil {
|
||
s.logger.Error("保存认证信息失败", zap.Error(err))
|
||
return fmt.Errorf("保存认证信息失败: %w", err)
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// checkAndCompleteEnterpriseVerification 检查并完成企业认证的公共方法
|
||
func (s *CertificationApplicationServiceImpl) checkAndCompleteEnterpriseVerification(ctx context.Context, cert *entities.Certification) error {
|
||
record, err := s.enterpriseInfoSubmitRecordRepo.FindLatestByUserID(ctx, cert.UserID)
|
||
if err != nil {
|
||
return fmt.Errorf("查找企业信息失败: %w", err)
|
||
}
|
||
identity, err := s.esignClient.QueryOrgIdentityInfo(&esign.QueryOrgIdentityRequest{
|
||
OrgName: record.CompanyName,
|
||
})
|
||
if err != nil {
|
||
s.logger.Error("查询企业认证信息失败", zap.Error(err))
|
||
}
|
||
if identity != nil && identity.Data.RealnameStatus == 1 {
|
||
err = s.txManager.ExecuteInTx(ctx, func(txCtx context.Context) error {
|
||
return s.completeEnterpriseVerification(txCtx, cert, cert.UserID, record.CompanyName, record.LegalPersonName)
|
||
})
|
||
if err != nil {
|
||
return fmt.Errorf("完成企业认证失败: %w", err)
|
||
}
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// checkAndUpdateSignStatus 检查并更新签署状态的公共方法
|
||
func (s *CertificationApplicationServiceImpl) checkAndUpdateSignStatus(ctx context.Context, cert *entities.Certification) (string, error) {
|
||
var reason string
|
||
err := s.txManager.ExecuteInTx(ctx, func(txCtx context.Context) error {
|
||
if cert.Status != enums.StatusContractApplied {
|
||
return fmt.Errorf("认证状态不正确")
|
||
}
|
||
detail, err := s.esignClient.QuerySignFlowDetail(cert.EsignFlowID)
|
||
if err != nil {
|
||
return fmt.Errorf("查询签署流程详情失败: %s", err.Error())
|
||
}
|
||
if detail.Data.SignFlowStatus == 2 {
|
||
err = cert.SignSuccess()
|
||
if err != nil {
|
||
return fmt.Errorf("合同签署成功失败: %s", err.Error())
|
||
}
|
||
err = cert.CompleteCertification()
|
||
if err != nil {
|
||
return fmt.Errorf("完成认证失败: %s", err.Error())
|
||
}
|
||
// 同步合同信息到用户域
|
||
err = s.handleContractAfterSignComplete(txCtx, cert)
|
||
if err != nil {
|
||
s.logger.Error("同步合同信息到用户域失败", zap.Error(err))
|
||
return fmt.Errorf("同步合同信息失败: %s", err.Error())
|
||
}
|
||
|
||
reason = "合同签署成功"
|
||
} else if detail.Data.SignFlowStatus == 7 {
|
||
err = cert.ContractRejection(detail.Data.SignFlowDescription)
|
||
if err != nil {
|
||
return fmt.Errorf("合同签署失败: %s", err.Error())
|
||
}
|
||
reason = "合同签署拒签"
|
||
} else if detail.Data.SignFlowStatus == 5 {
|
||
err = cert.ContractExpiration()
|
||
if err != nil {
|
||
return fmt.Errorf("合同签署过期失败: %s", err.Error())
|
||
}
|
||
reason = "合同签署过期"
|
||
} else {
|
||
reason = "合同签署中"
|
||
}
|
||
err = s.aggregateService.SaveCertification(ctx, cert)
|
||
if err != nil {
|
||
return fmt.Errorf("保存认证信息失败: %s", err.Error())
|
||
}
|
||
return nil
|
||
})
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
return reason, nil
|
||
}
|
||
|
||
// handleContractAfterSignComplete 处理签署完成后的合同
|
||
func (s *CertificationApplicationServiceImpl) handleContractAfterSignComplete(ctx context.Context, cert *entities.Certification) error {
|
||
// 获取用户的企业信息
|
||
user, err := s.userAggregateService.LoadUser(ctx, cert.UserID)
|
||
if err != nil {
|
||
return fmt.Errorf("加载用户信息失败: %w", err)
|
||
}
|
||
if user.EnterpriseInfo == nil {
|
||
return fmt.Errorf("用户企业信息不存在")
|
||
}
|
||
|
||
// 1. 获取所有已签署合同文件信息
|
||
downloadSignedFileResponse, err := s.esignClient.DownloadSignedFile(cert.EsignFlowID)
|
||
if err != nil {
|
||
return fmt.Errorf("下载已签署文件失败: %s", err.Error())
|
||
}
|
||
files := downloadSignedFileResponse.Data.Files
|
||
if len(files) == 0 {
|
||
return fmt.Errorf("未获取到已签署合同文件")
|
||
}
|
||
|
||
for _, file := range files {
|
||
fileUrl := file.DownloadUrl
|
||
fileName := file.FileName
|
||
fileId := file.FileId
|
||
s.logger.Info("下载已签署文件准备", zap.String("file_url", fileUrl), zap.String("file_name", fileName))
|
||
|
||
// 2. 下载文件内容
|
||
fileBytes, err := s.downloadFileContent(ctx, fileUrl)
|
||
if err != nil {
|
||
s.logger.Error("下载合同文件内容失败", zap.String("file_name", fileName), zap.Error(err))
|
||
continue
|
||
}
|
||
|
||
// 3. 上传到七牛云
|
||
uploadResult, err := s.qiniuStorageService.UploadFile(ctx, fileBytes, fileName)
|
||
if err != nil {
|
||
s.logger.Error("上传合同文件到七牛云失败", zap.String("file_name", fileName), zap.Error(err))
|
||
continue
|
||
}
|
||
qiniuURL := uploadResult.URL
|
||
|
||
s.logger.Info("合同文件已上传七牛云", zap.String("file_name", fileName), zap.String("qiniu_url", qiniuURL))
|
||
|
||
// 4. 保存到合同聚合根
|
||
_, err = s.contractAggregateService.CreateContract(
|
||
ctx,
|
||
user.EnterpriseInfo.ID,
|
||
cert.UserID,
|
||
fileName,
|
||
user_entities.ContractTypeCooperation,
|
||
fileId,
|
||
qiniuURL,
|
||
)
|
||
if err != nil {
|
||
s.logger.Error("保存合同信息到聚合根失败", zap.String("file_name", fileName), zap.Error(err))
|
||
continue
|
||
}
|
||
|
||
s.logger.Info("合同信息已保存到聚合根", zap.String("file_name", fileName), zap.String("qiniu_url", qiniuURL))
|
||
}
|
||
|
||
_, err = s.walletAggregateService.CreateWallet(ctx, cert.UserID)
|
||
if err != nil {
|
||
s.logger.Error("创建钱包失败", zap.String("user_id", cert.UserID), zap.Error(err))
|
||
}
|
||
|
||
// 6. 创建API用户
|
||
err = s.apiUserAggregateService.CreateApiUser(ctx, cert.UserID)
|
||
if err != nil {
|
||
s.logger.Error("创建API用户失败", zap.String("user_id", cert.UserID), zap.Error(err))
|
||
}
|
||
err = s.userAggregateService.CompleteCertification(ctx, cert.UserID)
|
||
if err != nil {
|
||
s.logger.Error("用户域完成认证失败", zap.String("user_id", cert.UserID), zap.Error(err))
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// downloadFileContent 通过URL下载文件内容
|
||
func (s *CertificationApplicationServiceImpl) downloadFileContent(ctx context.Context, fileUrl string) ([]byte, error) {
|
||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, fileUrl, nil)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
resp, err := http.DefaultClient.Do(req)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
defer resp.Body.Close()
|
||
if resp.StatusCode != http.StatusOK {
|
||
return nil, fmt.Errorf("下载失败,状态码: %d", resp.StatusCode)
|
||
}
|
||
return io.ReadAll(resp.Body)
|
||
}
|