package certification import ( "context" "fmt" "strings" "time" "hyapi-server/internal/application/certification/dto/commands" "hyapi-server/internal/application/certification/dto/responses" "hyapi-server/internal/domains/certification/entities" "hyapi-server/internal/domains/certification/enums" "hyapi-server/internal/domains/certification/ports" user_entities "hyapi-server/internal/domains/user/entities" "go.uber.org/zap" ) // ListSignPlatforms 可选签署平台列表 func (s *CertificationApplicationServiceImpl) ListSignPlatforms(ctx context.Context) (*responses.SignPlatformsResponse, error) { _ = ctx items := make([]*responses.SignPlatformItem, 0, 2) for _, p := range s.platformRegistry.List() { platform := p.Platform() item := &responses.SignPlatformItem{ Platform: string(platform), Name: enums.GetSignPlatformName(platform), Available: true, Description: "", } switch platform { case enums.SignPlatformEsign: item.Description = "通过 e签宝 完成企业实名认证与合同签署" case enums.SignPlatformFadada: item.Description = "通过法大大完成企业实名认证与合同签署" } items = append(items, item) } return &responses.SignPlatformsResponse{Platforms: items}, nil } // SelectSignPlatform 用户选择签署平台并生成企业认证链接 func (s *CertificationApplicationServiceImpl) SelectSignPlatform( ctx context.Context, cmd *commands.SelectSignPlatformCommand, ) (*responses.CertificationResponse, error) { if cmd == nil || cmd.UserID == "" { return nil, fmt.Errorf("用户ID不能为空") } platform := enums.SignPlatform(strings.TrimSpace(cmd.SignPlatform)) if !enums.IsValidSignPlatform(platform) { return nil, fmt.Errorf("无效的签署平台: %s", cmd.SignPlatform) } cert, err := s.aggregateService.LoadCertificationByUserID(ctx, cmd.UserID) if err != nil { return nil, fmt.Errorf("加载认证信息失败: %w", err) } record, err := s.enterpriseInfoSubmitRecordRepo.FindLatestByUserID(ctx, cmd.UserID) if err != nil || record == nil { return nil, fmt.Errorf("未找到企业信息提交记录") } if record.ManualReviewStatus != "approved" { return nil, fmt.Errorf("请先等待管理员审核通过后再选择签署平台") } // 已选择且已有认证链接:幂等返回 if cert.SignPlatform == platform && cert.Status == enums.StatusInfoSubmitted && cert.AuthURL != "" { resp := s.convertToResponse(cert) meta, _ := s.AddStatusMetadata(ctx, cert) resp.Metadata = meta return resp, nil } if cert.SignPlatform != "" && cert.SignPlatform != platform { return nil, fmt.Errorf("签署平台已选择为 %s,不可更改", enums.GetSignPlatformName(cert.SignPlatform)) } if cert.Status != enums.StatusInfoPendingReview && !(cert.Status == enums.StatusInfoSubmitted && cert.AuthURL == "") { return nil, fmt.Errorf("当前状态不允许选择签署平台: %s", enums.GetStatusName(cert.Status)) } if err := cert.SetSignPlatform(platform); err != nil { return nil, err } provider, err := s.platformRegistry.Get(platform) if err != nil { return nil, err } authReq := &ports.EnterpriseAuthRequest{ ClientCorpID: record.UnifiedSocialCode, ClientUserID: record.LegalPersonID, CompanyName: record.CompanyName, UnifiedSocialCode: record.UnifiedSocialCode, LegalPersonName: record.LegalPersonName, LegalPersonID: record.LegalPersonID, TransactorName: record.LegalPersonName, TransactorMobile: record.LegalPersonPhone, TransactorID: record.LegalPersonID, } authRes, alreadyVerified, err := s.generateEnterpriseAuthOrDetectVerifiedWithProvider(ctx, provider, authReq) if err != nil { return nil, fmt.Errorf("生成企业认证链接失败: %w", err) } if alreadyVerified { // 已授权/已实名:跳过授权页,直接推进到 enterprise_verified err = s.txManager.ExecuteInTx(ctx, func(txCtx context.Context) error { clientCorpID := strings.TrimSpace(record.UnifiedSocialCode) if cert.Status == enums.StatusInfoPendingReview { if err := cert.ApproveEnterpriseInfoReview("", clientCorpID, cmd.UserID); err != nil { return err } } else if cert.Status == enums.StatusInfoSubmitted { if clientCorpID != "" && cert.AuthFlowID == "" { cert.AuthFlowID = clientCorpID } } else { return fmt.Errorf("当前状态不允许完成已授权企业认证: %s", enums.GetStatusName(cert.Status)) } return s.completeEnterpriseVerification(txCtx, cert, cert.UserID, record.CompanyName, record.LegalPersonName) }) if err != nil { return nil, err } } else { authURL := authRes.AuthShortURL if authURL == "" { authURL = authRes.AuthURL } if err := cert.ApproveEnterpriseInfoReview(authURL, authRes.AuthFlowID, cmd.UserID); err != nil { return nil, err } if err := s.aggregateService.SaveCertification(ctx, cert); err != nil { return nil, fmt.Errorf("保存认证信息失败: %w", err) } } s.logger.Info("用户已选择签署平台", zap.String("user_id", cmd.UserID), zap.String("sign_platform", string(platform)), zap.Bool("already_verified", alreadyVerified)) resp := s.convertToResponse(cert) meta, _ := s.AddStatusMetadata(ctx, cert) resp.Metadata = meta return resp, nil } // HandleFadadaCallback 法大大回调 func (s *CertificationApplicationServiceImpl) HandleFadadaCallback(ctx context.Context, headers map[string]string, body []byte) error { provider, err := s.platformRegistry.Get(enums.SignPlatformFadada) if err != nil { return err } if err := provider.VerifyCallback(ctx, headers, body); err != nil { return fmt.Errorf("法大大回调验签失败: %w", err) } ev, err := provider.ParseCallback(ctx, headers, body) if err != nil { return fmt.Errorf("法大大回调解析失败: %w", err) } s.logger.Info("收到法大大回调", zap.String("event", ev.Event), zap.Bool("auth_passed", ev.AuthPassed), zap.Bool("sign_completed", ev.SignCompleted), zap.String("sign_flow_id", ev.SignFlowID)) // 认证完成主要依赖 ConfirmAuth / details 轮询;签署完成按任务 ID 兜底推进 if ev.SignCompleted && ev.SignFlowID != "" { cert, err := s.queryRepository.FindByEsignFlowID(ctx, ev.SignFlowID) if err == nil && cert != nil && cert.Status == enums.StatusContractApplied { _, _ = s.checkAndUpdateSignStatus(ctx, cert) } } return nil } func (s *CertificationApplicationServiceImpl) resolveProvider(cert *entities.Certification) (ports.SignPlatformProvider, error) { return s.platformRegistry.Get(cert.ResolvedSignPlatform()) } func (s *CertificationApplicationServiceImpl) generateEnterpriseAuthOrDetectVerifiedWithProvider( ctx context.Context, provider ports.SignPlatformProvider, authReq *ports.EnterpriseAuthRequest, ) (*ports.AuthLinkResult, bool, error) { authRes, err := provider.GenerateEnterpriseAuth(ctx, authReq) if err == nil { return authRes, false, nil } // 法大大:企业已对本应用授权(210002)= 授权已完成,直接跳过授权页 if isEnterpriseAlreadyAuthorizedErr(err) { s.logger.Info("第三方返回企业已授权,跳过认证链接并视为认证完成", zap.String("company_name", authReq.CompanyName), zap.String("uscc", authReq.UnifiedSocialCode), zap.String("platform", string(provider.Platform())), zap.Error(err)) return nil, true, nil } // e签宝等:提示已实名时再查一次实名状态确认 if !isEnterpriseAlreadyRealnamedErr(err) && !strings.Contains(strings.ToLower(err.Error()), "identified") { return nil, false, err } ok, qErr := provider.QueryOrgVerified(ctx, &ports.OrgIdentityQuery{ OrgName: authReq.CompanyName, OrgIdentNo: authReq.UnifiedSocialCode, }) if qErr != nil { return nil, false, fmt.Errorf("%v; 且查询实名状态失败: %w", err, qErr) } if ok { s.logger.Info("第三方返回企业已实名,跳过认证链接并视为认证完成", zap.String("company_name", authReq.CompanyName), zap.String("platform", string(provider.Platform()))) return nil, true, nil } return nil, false, err } func (s *CertificationApplicationServiceImpl) appendPlatformSelectMetadata( ctx context.Context, cert *entities.Certification, metadata map[string]interface{}, ) map[string]interface{} { if metadata == nil { metadata = map[string]interface{}{} } record, err := s.enterpriseInfoSubmitRecordRepo.FindLatestByUserID(ctx, cert.UserID) if err != nil || record == nil { return metadata } metadata["manual_review_status"] = record.ManualReviewStatus if record.ManualReviewStatus == "approved" && cert.Status == enums.StatusInfoPendingReview && (cert.SignPlatform == "" || cert.AuthURL == "") { metadata["need_select_platform"] = true platforms := make([]map[string]string, 0, 2) for _, p := range s.platformRegistry.List() { platforms = append(platforms, map[string]string{ "platform": string(p.Platform()), "name": enums.GetSignPlatformName(p.Platform()), }) } metadata["available_platforms"] = platforms metadata["next_action"] = "请选择签署平台以继续企业认证" } if cert.SignPlatform != "" { metadata["sign_platform"] = string(cert.SignPlatform) metadata["sign_platform_name"] = enums.GetSignPlatformName(cert.SignPlatform) } return metadata } // ensureContractFill 签署模板路径补齐协议编号与日期(协议编号生成规则与 e签宝一致) func ensureContractFill(cert *entities.Certification, companyName, uscc, address, repName string) *ports.ContractGenerateRequest { if cert.ContractCode == "" { cert.SetContractCode(user_entities.GenerateContractCode(user_entities.ContractTypeCooperation)) } signDate := time.Now().Format("2006年01月02日") return &ports.ContractGenerateRequest{ AgreementNo: cert.ContractCode, CompanyName: companyName, UnifiedSocialCode: uscc, EnterpriseAddress: address, AuthorizedRepName: repName, SignDate: signDate, } }