This commit is contained in:
2026-07-27 00:18:02 +08:00
parent c3b0de6ac5
commit 6fbdac381b
9 changed files with 217 additions and 6 deletions

View File

@@ -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())),

View File

@@ -34,6 +34,25 @@ type OrgIdentityQuery struct {
ClientCorpID string
}
// CorpAuthStatusQuery 企业授权状态查询(法大大 /corp/gete签宝可返回未授权
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)

View File

@@ -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{

View File

@@ -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) {

View File

@@ -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 {

View File

@@ -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"

View File

@@ -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)

View File

@@ -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/getbindingStatus=authorized
// 本函数仅作取链失败时的兜底。
func IsAuthAlreadyGranted(err error) bool {
if err == nil {
return false

View File

@@ -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"` // 协议编号