f
This commit is contained in:
@@ -49,6 +49,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 {
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -74,7 +74,7 @@ func (c *Client) GenerateEnterpriseAuth(req *EnterpriseAuthRequest) (*Enterprise
|
||||
return nil, err
|
||||
}
|
||||
if res.Code != successCode {
|
||||
return nil, fmt.Errorf("获取法大大企业认证链接失败: code=%s msg=%s requestId=%s", res.Code, res.Msg, res.RequestID)
|
||||
return nil, NewAPIError("获取法大大企业认证链接失败", res.Code, res.Msg, res.RequestID)
|
||||
}
|
||||
|
||||
var data getCorpAuthURLData
|
||||
@@ -92,6 +92,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 > corpIdentNo > clientCorpId
|
||||
if openCorpID != "" {
|
||||
corpIdentNo, clientCorpID = "", ""
|
||||
} else if corpIdentNo != "" {
|
||||
clientCorpID = ""
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
@@ -125,7 +202,7 @@ func (c *Client) QueryOrgIdentity(req *QueryOrgIdentityRequest) (*QueryOrgIdenti
|
||||
return nil, err
|
||||
}
|
||||
if res.Code != successCode {
|
||||
return nil, fmt.Errorf("查询法大大企业实名状态失败: code=%s msg=%s requestId=%s", res.Code, res.Msg, res.RequestID)
|
||||
return nil, NewAPIError("查询法大大企业实名状态失败", res.Code, res.Msg, res.RequestID)
|
||||
}
|
||||
|
||||
var data getIdentifiedStatusData
|
||||
|
||||
91
internal/shared/fadada/errors.go
Normal file
91
internal/shared/fadada/errors.go
Normal file
@@ -0,0 +1,91 @@
|
||||
package fadada
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// 法大大 FASC OpenAPI 错误码(以官方「错误码说明」文档 code 为准,勿依赖 msg 文案)。
|
||||
// 文档:https://dev.fadada.com/api-doc/JOQQIJXLFL/H4F0HSKMHQ3HPIIM/5-1
|
||||
const (
|
||||
CodeSuccess = "100000" // 请求成功
|
||||
|
||||
// 公共
|
||||
CodeUnknownException = "100001" // 不可预知异常
|
||||
CodeAccessTokenInvalid = "100002" // 访问凭证失效
|
||||
CodeSignInvalid = "100003" // 接口签名错误
|
||||
CodeTooFrequent = "100004" // 请求频繁
|
||||
CodeParamIllegal = "100011" // 请求参数非法
|
||||
CodeParamEmpty = "100012" // 请求参数为空
|
||||
CodeBusinessIDNotFound = "212007" // 业务场景标识不存在
|
||||
|
||||
// get-auth-url 返回「授权范围重复获取」时可用作兜底(非正式查授权接口)
|
||||
// 正式查授权请用 POST /corp/get 的 bindingStatus
|
||||
CodeAuthScopeDuplicate = "210002" // 授权范围重复获取
|
||||
CodeAuthScopeDuplicateCorp = "210013" // 授权范围重复获取(无须重复获取授权申请链接)
|
||||
CodeCorpNotFound = "210032" // 企业用户不存在
|
||||
)
|
||||
|
||||
// APIError 法大大业务错误(按 code 判断,不依赖 msg)
|
||||
type APIError struct {
|
||||
Op string // 调用场景说明
|
||||
Code string
|
||||
Msg string
|
||||
RequestID string
|
||||
}
|
||||
|
||||
func (e *APIError) Error() string {
|
||||
if e == nil {
|
||||
return ""
|
||||
}
|
||||
op := strings.TrimSpace(e.Op)
|
||||
if op == "" {
|
||||
op = "法大大接口调用失败"
|
||||
}
|
||||
return fmt.Sprintf("%s: code=%s msg=%s requestId=%s", op, e.Code, e.Msg, e.RequestID)
|
||||
}
|
||||
|
||||
// NewAPIError 构造结构化业务错误
|
||||
func NewAPIError(op, code, msg, requestID string) *APIError {
|
||||
return &APIError{
|
||||
Op: strings.TrimSpace(op),
|
||||
Code: strings.TrimSpace(code),
|
||||
Msg: strings.TrimSpace(msg),
|
||||
RequestID: strings.TrimSpace(requestID),
|
||||
}
|
||||
}
|
||||
|
||||
// IsAuthAlreadyGranted 是否为 get-auth-url「授权范围重复获取」错误码(210002/210013)。
|
||||
// 注意:正式判断是否已授权应调用 POST /corp/get(bindingStatus=authorized);
|
||||
// 本函数仅作取链失败时的兜底。
|
||||
func IsAuthAlreadyGranted(err error) bool {
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
var apiErr *APIError
|
||||
if errors.As(err, &apiErr) && apiErr != nil {
|
||||
return isAuthAlreadyGrantedCode(apiErr.Code)
|
||||
}
|
||||
return isAuthAlreadyGrantedCode(extractFadadaCode(err.Error()))
|
||||
}
|
||||
|
||||
func isAuthAlreadyGrantedCode(code string) bool {
|
||||
switch strings.TrimSpace(code) {
|
||||
case CodeAuthScopeDuplicate, CodeAuthScopeDuplicateCorp:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
var fadadaCodeRe = regexp.MustCompile(`(?i)(?:^|[^\d])code\s*=\s*(\d{6})(?:[^\d]|$)`)
|
||||
|
||||
func extractFadadaCode(msg string) string {
|
||||
m := fadadaCodeRe.FindStringSubmatch(msg)
|
||||
if len(m) < 2 {
|
||||
return ""
|
||||
}
|
||||
return m[1]
|
||||
}
|
||||
28
internal/shared/fadada/errors_test.go
Normal file
28
internal/shared/fadada/errors_test.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package fadada
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestIsAuthAlreadyGranted(t *testing.T) {
|
||||
if !IsAuthAlreadyGranted(NewAPIError("op", CodeAuthScopeDuplicate, "授权范围重复获取", "1")) {
|
||||
t.Fatal("210002 should match")
|
||||
}
|
||||
if !IsAuthAlreadyGranted(NewAPIError("op", CodeAuthScopeDuplicateCorp, "授权范围重复获取", "2")) {
|
||||
t.Fatal("210013 should match")
|
||||
}
|
||||
if IsAuthAlreadyGranted(NewAPIError("op", CodeParamIllegal, "请求参数非法", "3")) {
|
||||
t.Fatal("100011 should not match")
|
||||
}
|
||||
if !IsAuthAlreadyGranted(fmt.Errorf("wrap: %w", NewAPIError("op", CodeAuthScopeDuplicate, "x", "4"))) {
|
||||
t.Fatal("wrapped 210002 should match")
|
||||
}
|
||||
if !IsAuthAlreadyGranted(errors.New("fail: code=210013 msg=授权范围重复获取 requestId=x")) {
|
||||
t.Fatal("legacy string 210013 should match")
|
||||
}
|
||||
if IsAuthAlreadyGranted(errors.New("无需重复操作")) {
|
||||
t.Fatal("msg-only must not match per official docs")
|
||||
}
|
||||
}
|
||||
@@ -38,6 +38,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"` // 协议编号
|
||||
|
||||
Reference in New Issue
Block a user