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 } transactorName, transactorMobile, transactorID := pickTransactorFromRecord(record) clientCorpID := strings.TrimSpace(record.UnifiedSocialCode) authReq := &ports.EnterpriseAuthRequest{ ClientCorpID: clientCorpID, ClientUserID: transactorID, CompanyName: record.CompanyName, UnifiedSocialCode: record.UnifiedSocialCode, LegalPersonName: record.LegalPersonName, LegalPersonID: record.LegalPersonID, TransactorName: transactorName, TransactorMobile: transactorMobile, TransactorID: transactorID, } // 选平台时先落库种子数据(clientCorpId/USCC),供后续 /corp/get 与签署复用 s.ensureFadadaCorpAuthSeed(ctx, cert, record, clientCorpID) authRes, alreadyVerified, err := s.generateEnterpriseAuthOrDetectVerifiedWithProvider(ctx, provider, authReq, cert, record) 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), zap.String("open_corp_id", ev.OpenCorpID), zap.String("client_corp_id", ev.ClientCorpID)) // 授权成功:落库 openCorpId / clientCorpId if ev.AuthPassed && (strings.TrimSpace(ev.OpenCorpID) != "" || strings.TrimSpace(ev.ClientCorpID) != "") { rec := entities.NewFadadaCorpAuthRecord("", "", s.fadadaAppID(), ev.OrgName, "", ev.ClientCorpID) rec.ApplyAuthStatus(ev.OpenCorpID, ev.ClientCorpID, "authorized", "", "", nil, "callback_auth_passed") if s.fadadaCorpAuthRecordRepo != nil { if err := s.fadadaCorpAuthRecordRepo.UpsertByBizKey(ctx, rec); err != nil { s.logger.Warn("回调落库法大大授权信息失败", zap.Error(err)) } } } // 认证完成主要依赖 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, cert *entities.Certification, record *entities.EnterpriseInfoSubmitRecord, ) (*ports.AuthLinkResult, bool, error) { userID := "" if cert != nil { userID = cert.UserID } // 1) 正式查授权:POST /corp/get(三选一:库 openCorpId > USCC corpIdentNo > 库 clientCorpId) authQuery := s.resolveCorpAuthQueryForGet(ctx, userID, authReq.UnifiedSocialCode, authReq.ClientCorpID) s.logger.Info("查询企业授权状态入参", zap.String("company_name", authReq.CompanyName), zap.String("open_corp_id", authQuery.OpenCorpID), zap.String("corp_ident_no", authQuery.CorpIdentNo), zap.String("client_corp_id", authQuery.ClientCorpID), zap.String("platform", string(provider.Platform()))) authStatus, qErr := provider.QueryCorpAuthStatus(ctx, authQuery) if qErr != nil { s.logger.Warn("查询企业授权状态失败,将继续尝试获取授权链接", zap.String("company_name", authReq.CompanyName), zap.String("uscc", authReq.UnifiedSocialCode), zap.String("platform", string(provider.Platform())), zap.Error(qErr)) } else if authStatus != nil && authStatus.Supported { s.persistFadadaCorpAuthStatus(ctx, cert, record, authStatus, "corp_get_select_platform") if authStatus.Authorized { s.logger.Info("查询企业授权状态为已授权,跳过认证链接并视为认证完成", zap.String("company_name", authReq.CompanyName), zap.String("uscc", authReq.UnifiedSocialCode), zap.String("platform", string(provider.Platform())), zap.String("binding_status", authStatus.BindingStatus), zap.String("open_corp_id", authStatus.OpenCorpID), zap.String("ident_status", authStatus.IdentStatus)) return nil, true, nil } } // 2) 未授权或不支持查询:走获取授权链接 authRes, err := provider.GenerateEnterpriseAuth(ctx, authReq) if err == nil { return authRes, false, nil } // 3) 兜底:get-auth-url 返回授权范围重复获取(210002/210013) 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)) // 再查一次 /corp/get,尽量把 openCorpId 落库 if authStatus2, qErr2 := provider.QueryCorpAuthStatus(ctx, authQuery); qErr2 == nil && authStatus2 != nil { s.persistFadadaCorpAuthStatus(ctx, cert, record, authStatus2, "corp_get_after_duplicate_auth") } 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, } }