331 lines
13 KiB
Go
331 lines
13 KiB
Go
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,
|
||
}
|
||
}
|