From 6fbdac381b97c52a47251bdc25352dc807753be9 Mon Sep 17 00:00:00 2001 From: liangzai <2440983361@qq.com> Date: Mon, 27 Jul 2026 00:18:02 +0800 Subject: [PATCH] f --- .../certification/platform_flow.go | 31 +++++++- .../ports/sign_platform_provider.go | 21 +++++ .../external/esign/esign_provider.go | 7 ++ .../external/fadada/fadada_provider.go | 28 +++++++ internal/shared/fadada/api_types.go | 19 +++++ internal/shared/fadada/client.go | 1 + internal/shared/fadada/corp_service.go | 77 +++++++++++++++++++ internal/shared/fadada/errors.go | 10 ++- internal/shared/fadada/types.go | 29 +++++++ 9 files changed, 217 insertions(+), 6 deletions(-) diff --git a/internal/application/certification/platform_flow.go b/internal/application/certification/platform_flow.go index 6519f11..25c66c7 100644 --- a/internal/application/certification/platform_flow.go +++ b/internal/application/certification/platform_flow.go @@ -188,14 +188,41 @@ func (s *CertificationApplicationServiceImpl) generateEnterpriseAuthOrDetectVeri provider ports.SignPlatformProvider, authReq *ports.EnterpriseAuthRequest, ) (*ports.AuthLinkResult, bool, error) { + // 1) 正式查授权:法大大 POST /corp/get,看 bindingStatus=authorized + clientCorpID := strings.TrimSpace(authReq.ClientCorpID) + if clientCorpID == "" { + clientCorpID = strings.TrimSpace(authReq.UnifiedSocialCode) + } + authStatus, qErr := provider.QueryCorpAuthStatus(ctx, &ports.CorpAuthStatusQuery{ + ClientCorpID: clientCorpID, + CorpIdentNo: strings.TrimSpace(authReq.UnifiedSocialCode), + }) + 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 && 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 } - // 法大大:授权范围重复获取(code=210002/210013)= 已对本应用授权,跳过授权页 + // 3) 兜底:get-auth-url 返回授权范围重复获取(210002/210013) if isEnterpriseAlreadyAuthorizedErr(err) { - s.logger.Info("第三方返回企业已授权,跳过认证链接并视为认证完成", + s.logger.Info("获取授权链接返回重复授权错误码,跳过认证链接并视为认证完成", zap.String("company_name", authReq.CompanyName), zap.String("uscc", authReq.UnifiedSocialCode), zap.String("platform", string(provider.Platform())), diff --git a/internal/domains/certification/ports/sign_platform_provider.go b/internal/domains/certification/ports/sign_platform_provider.go index 50fed34..d961ecf 100644 --- a/internal/domains/certification/ports/sign_platform_provider.go +++ b/internal/domains/certification/ports/sign_platform_provider.go @@ -34,6 +34,25 @@ type OrgIdentityQuery struct { ClientCorpID string } +// CorpAuthStatusQuery 企业授权状态查询(法大大 /corp/get;e签宝可返回未授权) +type CorpAuthStatusQuery struct { + CorpIdentNo string // 统一社会信用代码 + ClientCorpID string // 应用内企业标识 + OpenCorpID string // 法大大 openCorpId +} + +// CorpAuthStatusResult 企业授权状态 +type CorpAuthStatusResult struct { + Supported bool // 当前平台是否支持主动查询授权状态 + Found bool // 本应用下是否已有企业记录 + Authorized bool // bindingStatus=authorized + Identified bool // identStatus=identified + OpenCorpID string + ClientCorpID string + BindingStatus string + IdentStatus string +} + // ContractGenerateRequest 合同模板填单 type ContractGenerateRequest struct { AgreementNo string @@ -131,6 +150,8 @@ type SignPlatformProvider interface { GenerateEnterpriseAuth(ctx context.Context, req *EnterpriseAuthRequest) (*AuthLinkResult, error) QueryOrgVerified(ctx context.Context, req *OrgIdentityQuery) (bool, error) + // QueryCorpAuthStatus 查询企业是否已对本应用完成授权绑定(法大大:/corp/get bindingStatus) + QueryCorpAuthStatus(ctx context.Context, req *CorpAuthStatusQuery) (*CorpAuthStatusResult, error) GenerateContractFile(ctx context.Context, req *ContractGenerateRequest) (*ContractFileResult, error) CreateSignFlow(ctx context.Context, req *SignFlowCreateRequest) (*SignFlowResult, error) diff --git a/internal/infrastructure/external/esign/esign_provider.go b/internal/infrastructure/external/esign/esign_provider.go index c58193d..3d6f8de 100644 --- a/internal/infrastructure/external/esign/esign_provider.go +++ b/internal/infrastructure/external/esign/esign_provider.go @@ -56,6 +56,13 @@ func (p *Provider) QueryOrgVerified(ctx context.Context, req *ports.OrgIdentityQ return identity != nil && identity.Data.RealnameStatus == 1, nil } +func (p *Provider) QueryCorpAuthStatus(ctx context.Context, req *ports.CorpAuthStatusQuery) (*ports.CorpAuthStatusResult, error) { + _ = ctx + _ = req + // e签宝无对等「查询企业授权状态」接口,走取链/已实名兜底逻辑 + return &ports.CorpAuthStatusResult{Supported: false}, nil +} + func (p *Provider) GenerateContractFile(ctx context.Context, req *ports.ContractGenerateRequest) (*ports.ContractFileResult, error) { _ = ctx components := map[string]string{ diff --git a/internal/infrastructure/external/fadada/fadada_provider.go b/internal/infrastructure/external/fadada/fadada_provider.go index f0f1c7c..620bfbd 100644 --- a/internal/infrastructure/external/fadada/fadada_provider.go +++ b/internal/infrastructure/external/fadada/fadada_provider.go @@ -54,6 +54,34 @@ func (p *Provider) QueryOrgVerified(ctx context.Context, req *ports.OrgIdentityQ }) } +func (p *Provider) QueryCorpAuthStatus(ctx context.Context, req *ports.CorpAuthStatusQuery) (*ports.CorpAuthStatusResult, error) { + _ = ctx + if req == nil { + return &ports.CorpAuthStatusResult{Supported: true, Found: false}, nil + } + res, err := p.client.QueryCorpAuth(&sharedfadada.QueryCorpAuthRequest{ + CorpIdentNo: req.CorpIdentNo, + ClientCorpID: req.ClientCorpID, + OpenCorpID: req.OpenCorpID, + }) + if err != nil { + return nil, err + } + if res == nil { + return &ports.CorpAuthStatusResult{Supported: true, Found: false}, nil + } + return &ports.CorpAuthStatusResult{ + Supported: true, + Found: res.Found, + Authorized: res.Authorized, + Identified: res.Identified, + OpenCorpID: res.OpenCorpID, + ClientCorpID: res.ClientCorpID, + BindingStatus: res.BindingStatus, + IdentStatus: res.IdentStatus, + }, nil +} + // GenerateContractFile 法大大走签署任务模板(/sign-task/create-with-template), // 此处返回空结果,实际填单由 CreateSignFlow 通过 /sign-task/field/fill-values 完成。 func (p *Provider) GenerateContractFile(ctx context.Context, req *ports.ContractGenerateRequest) (*ports.ContractFileResult, error) { diff --git a/internal/shared/fadada/api_types.go b/internal/shared/fadada/api_types.go index d6e4938..8cc47e7 100644 --- a/internal/shared/fadada/api_types.go +++ b/internal/shared/fadada/api_types.go @@ -55,6 +55,25 @@ type getCorpAuthURLData struct { AuthURL string `json:"authUrl"` } +// ---- /corp/get 查询企业授权状态 ---- + +type getCorpReq struct { + CorpIdentNo string `json:"corpIdentNo,omitempty"` + ClientCorpID string `json:"clientCorpId,omitempty"` + OpenCorpID string `json:"openCorpId,omitempty"` +} + +type getCorpData struct { + ClientCorpID string `json:"clientCorpId"` + ClientCorpName string `json:"clientCorpName,omitempty"` + OpenCorpID string `json:"openCorpId"` + EntityType string `json:"entityType,omitempty"` + BindingStatus string `json:"bindingStatus"` + AuthScope []string `json:"authScope,omitempty"` + IdentStatus string `json:"identStatus,omitempty"` + AvailableStatus string `json:"availableStatus,omitempty"` +} + // ---- /corp/get-identified-status ---- type getIdentifiedStatusReq struct { diff --git a/internal/shared/fadada/client.go b/internal/shared/fadada/client.go index 6b008f2..c2b6bda 100644 --- a/internal/shared/fadada/client.go +++ b/internal/shared/fadada/client.go @@ -6,6 +6,7 @@ import "sync" const ( pathGetAccessToken = "/service/get-access-token" pathGetCorpAuthURL = "/corp/get-auth-url" + pathGetCorp = "/corp/get" pathGetCorpIdentifiedStatus = "/corp/get-identified-status" pathFillDocTemplateValues = "/doc-template/fill-values" pathCreateSignTaskTemplate = "/sign-task/create-with-template" diff --git a/internal/shared/fadada/corp_service.go b/internal/shared/fadada/corp_service.go index bf579ea..c776ac5 100644 --- a/internal/shared/fadada/corp_service.go +++ b/internal/shared/fadada/corp_service.go @@ -95,6 +95,83 @@ func (c *Client) GenerateEnterpriseAuth(req *EnterpriseAuthRequest) (*Enterprise }, nil } +// QueryCorpAuth 查询企业授权状态(POST /corp/get) +// 文档:https://dev.fadada.com/api-doc/X2JXRJIYRE/ELN3ZLQWR6PLXGCE/5-1 +// 以 bindingStatus=authorized 判断是否已完成帐号授权绑定(比 get-auth-url 的 210002/210013 更准确)。 +func (c *Client) QueryCorpAuth(req *QueryCorpAuthRequest) (*QueryCorpAuthResult, error) { + if req == nil { + return nil, fmt.Errorf("查询请求不能为空") + } + corpIdentNo := strings.TrimSpace(req.CorpIdentNo) + clientCorpID := strings.TrimSpace(req.ClientCorpID) + openCorpID := strings.TrimSpace(req.OpenCorpID) + filled := 0 + if corpIdentNo != "" { + filled++ + } + if clientCorpID != "" { + filled++ + } + if openCorpID != "" { + filled++ + } + if filled == 0 { + return nil, fmt.Errorf("corpIdentNo、clientCorpId、openCorpId 不能同时为空") + } + if filled > 1 { + // 文档要求三选一;优先 openCorpId > clientCorpId > corpIdentNo + if openCorpID != "" { + corpIdentNo, clientCorpID = "", "" + } else if clientCorpID != "" { + corpIdentNo = "" + } + } + + accessToken, err := c.ensureAccessToken() + if err != nil { + return nil, err + } + + res, err := c.http.PostBiz(pathGetCorp, accessToken, &getCorpReq{ + CorpIdentNo: corpIdentNo, + ClientCorpID: clientCorpID, + OpenCorpID: openCorpID, + }) + if err != nil { + return nil, err + } + if res.Code != successCode { + // 企业尚未在本应用下建档/授权时,视为未找到,不算业务异常 + if isCorpNotFoundCode(res.Code) { + return &QueryCorpAuthResult{Found: false}, nil + } + return nil, NewAPIError("查询法大大企业授权状态失败", res.Code, res.Msg, res.RequestID) + } + + var data getCorpData + if err := decodeData(res.Data, &data); err != nil { + return nil, fmt.Errorf("解析企业授权状态失败: %w", err) + } + + binding := strings.TrimSpace(data.BindingStatus) + ident := strings.TrimSpace(data.IdentStatus) + return &QueryCorpAuthResult{ + Found: true, + Authorized: binding == BindingStatusAuthorized, + Identified: ident == IdentStatusIdentified, + ClientCorpID: strings.TrimSpace(data.ClientCorpID), + OpenCorpID: strings.TrimSpace(data.OpenCorpID), + BindingStatus: binding, + IdentStatus: ident, + AvailableStatus: strings.TrimSpace(data.AvailableStatus), + AuthScopes: append([]string{}, data.AuthScope...), + }, nil +} + +func isCorpNotFoundCode(code string) bool { + return strings.TrimSpace(code) == CodeCorpNotFound +} + // QueryOrgVerified 查询企业是否已在法大大完成实名(POST /corp/get-identified-status) func (c *Client) QueryOrgVerified(req *QueryOrgIdentityRequest) (bool, error) { result, err := c.QueryOrgIdentity(req) diff --git a/internal/shared/fadada/errors.go b/internal/shared/fadada/errors.go index 4e03e82..2dc79b8 100644 --- a/internal/shared/fadada/errors.go +++ b/internal/shared/fadada/errors.go @@ -21,10 +21,11 @@ const ( CodeParamEmpty = "100012" // 请求参数为空 CodeBusinessIDNotFound = "212007" // 业务场景标识不存在 - // 授权范围重复获取:主体已对本应用授权,无须再获取授权链接 - // 个人用户帐号管理 / 企业用户帐号管理 均有说明 + // get-auth-url 返回「授权范围重复获取」时可用作兜底(非正式查授权接口) + // 正式查授权请用 POST /corp/get 的 bindingStatus CodeAuthScopeDuplicate = "210002" // 授权范围重复获取 CodeAuthScopeDuplicateCorp = "210013" // 授权范围重复获取(无须重复获取授权申请链接) + CodeCorpNotFound = "210032" // 企业用户不存在 ) // APIError 法大大业务错误(按 code 判断,不依赖 msg) @@ -56,8 +57,9 @@ func NewAPIError(op, code, msg, requestID string) *APIError { } } -// IsAuthAlreadyGranted 是否表示「已授权 / 授权范围重复获取」,应跳过再次获取授权链接。 -// 官方文档:应按返回码 code 判断,msg 可能调整。 +// IsAuthAlreadyGranted 是否为 get-auth-url「授权范围重复获取」错误码(210002/210013)。 +// 注意:正式判断是否已授权应调用 POST /corp/get(bindingStatus=authorized); +// 本函数仅作取链失败时的兜底。 func IsAuthAlreadyGranted(err error) bool { if err == nil { return false diff --git a/internal/shared/fadada/types.go b/internal/shared/fadada/types.go index 8c9605d..d3cf185 100644 --- a/internal/shared/fadada/types.go +++ b/internal/shared/fadada/types.go @@ -41,6 +41,35 @@ type QueryOrgIdentityResult struct { CorpIdentNo string `json:"corpIdentNo"` } +// 企业授权/实名状态(/corp/get) +const ( + BindingStatusUnauthorized = "unauthorized" // 未授权 + BindingStatusAuthorized = "authorized" // 已授权 + IdentStatusUnidentified = "unidentified" // 未认证 + IdentStatusIdentified = "identified" // 已认证且有效 +) + +// QueryCorpAuthRequest 查询企业授权状态(POST /corp/get) +// corpIdentNo、clientCorpId、openCorpId 三选一,不能同时为空。 +type QueryCorpAuthRequest struct { + CorpIdentNo string `json:"corpIdentNo,omitempty"` + ClientCorpID string `json:"clientCorpId,omitempty"` + OpenCorpID string `json:"openCorpId,omitempty"` +} + +// QueryCorpAuthResult 企业授权状态查询结果 +type QueryCorpAuthResult struct { + Found bool `json:"found"` // 本应用下是否已有该企业记录 + Authorized bool `json:"authorized"` + Identified bool `json:"identified"` + ClientCorpID string `json:"clientCorpId"` + OpenCorpID string `json:"openCorpId"` + BindingStatus string `json:"bindingStatus"` + IdentStatus string `json:"identStatus"` + AvailableStatus string `json:"availableStatus"` + AuthScopes []string `json:"authScopes"` +} + // ContractFillRequest 合作协议模板填单请求(对齐 e签宝 generateAndAddContractFile 入参) type ContractFillRequest struct { AgreementNo string `json:"agreementNo"` // 协议编号