f
This commit is contained in:
@@ -2,7 +2,10 @@ package certification
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"tyapi-server/internal/shared/fadada"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestIsEnterpriseAlreadyAuthorizedErr(t *testing.T) {
|
func TestIsEnterpriseAlreadyAuthorizedErr(t *testing.T) {
|
||||||
@@ -12,18 +15,38 @@ func TestIsEnterpriseAlreadyAuthorizedErr(t *testing.T) {
|
|||||||
want bool
|
want bool
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "fadada 210002",
|
name: "structured 210002",
|
||||||
|
err: fadada.NewAPIError("获取法大大企业认证链接失败", fadada.CodeAuthScopeDuplicate, "授权范围重复获取", "abc"),
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "structured 210013",
|
||||||
|
err: fadada.NewAPIError("获取法大大企业认证链接失败", fadada.CodeAuthScopeDuplicateCorp, "授权范围重复获取", "def"),
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "legacy string 210002",
|
||||||
err: errors.New("获取法大大企业认证链接失败: code=210002 msg=该企业已授权,无需重复操作 requestId=abc"),
|
err: errors.New("获取法大大企业认证链接失败: code=210002 msg=该企业已授权,无需重复操作 requestId=abc"),
|
||||||
want: true,
|
want: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "exist authorize wording",
|
name: "legacy string 210013",
|
||||||
err: errors.New("当前应用已存在授权"),
|
err: errors.New("获取法大大企业认证链接失败: code=210013 msg=授权范围重复获取 requestId=xyz"),
|
||||||
want: true,
|
want: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "other error",
|
name: "wrapped structured 210002",
|
||||||
err: errors.New("获取法大大企业认证链接失败: code=100011 msg=参数错误"),
|
err: fmt.Errorf("生成企业认证链接失败: %w", fadada.NewAPIError("获取法大大企业认证链接失败", fadada.CodeAuthScopeDuplicate, "授权范围重复获取", "rid")),
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "msg only should not match",
|
||||||
|
err: errors.New("当前应用已存在授权"),
|
||||||
|
want: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "other error 100011",
|
||||||
|
err: fadada.NewAPIError("获取法大大企业认证链接失败", fadada.CodeParamIllegal, "请求参数非法", "rid"),
|
||||||
want: false,
|
want: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ import (
|
|||||||
"tyapi-server/internal/infrastructure/external/storage"
|
"tyapi-server/internal/infrastructure/external/storage"
|
||||||
"tyapi-server/internal/shared/database"
|
"tyapi-server/internal/shared/database"
|
||||||
"tyapi-server/internal/shared/esign"
|
"tyapi-server/internal/shared/esign"
|
||||||
|
"tyapi-server/internal/shared/fadada"
|
||||||
sharedOCR "tyapi-server/internal/shared/ocr"
|
sharedOCR "tyapi-server/internal/shared/ocr"
|
||||||
|
|
||||||
"github.com/shopspring/decimal"
|
"github.com/shopspring/decimal"
|
||||||
@@ -171,6 +172,9 @@ func (s *CertificationApplicationServiceImpl) SubmitEnterpriseInfo(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 企业名半角括号统一为全角,避免与工商登记不一致
|
||||||
|
cmd.CompanyName = normalizeCompanyNameParentheses(cmd.CompanyName)
|
||||||
|
|
||||||
// 1.5 插入企业信息提交记录(包含扩展字段)
|
// 1.5 插入企业信息提交记录(包含扩展字段)
|
||||||
record := entities.NewEnterpriseInfoSubmitRecord(
|
record := entities.NewEnterpriseInfoSubmitRecord(
|
||||||
cmd.UserID,
|
cmd.UserID,
|
||||||
@@ -557,9 +561,14 @@ func (s *CertificationApplicationServiceImpl) ApplyContract(
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
ei := enterpriseInfo.EnterpriseInfo
|
ei := enterpriseInfo.EnterpriseInfo
|
||||||
|
submitRec, _ := s.enterpriseInfoSubmitRecordRepo.FindLatestByUserID(ctx, cmd.UserID)
|
||||||
|
_, transactorMobile, _ := pickTransactorFromRecord(submitRec)
|
||||||
|
if transactorMobile == "" {
|
||||||
|
transactorMobile = ei.LegalPersonPhone
|
||||||
|
}
|
||||||
urlRes, err := provider.GetActorSignURL(ctx, &ports.GetActorSignURLRequest{
|
urlRes, err := provider.GetActorSignURL(ctx, &ports.GetActorSignURLRequest{
|
||||||
SignFlowID: flowID,
|
SignFlowID: flowID,
|
||||||
TransactorMobile: ei.LegalPersonPhone,
|
TransactorMobile: transactorMobile,
|
||||||
PartyAName: ei.CompanyName,
|
PartyAName: ei.CompanyName,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -635,9 +644,14 @@ func (s *CertificationApplicationServiceImpl) RefreshContractSignURL(
|
|||||||
return nil, fmt.Errorf("获取企业信息失败: %w", err)
|
return nil, fmt.Errorf("获取企业信息失败: %w", err)
|
||||||
}
|
}
|
||||||
ei := enterpriseInfo.EnterpriseInfo
|
ei := enterpriseInfo.EnterpriseInfo
|
||||||
|
submitRec, _ := s.enterpriseInfoSubmitRecordRepo.FindLatestByUserID(ctx, userID)
|
||||||
|
_, transactorMobile, _ := pickTransactorFromRecord(submitRec)
|
||||||
|
if transactorMobile == "" {
|
||||||
|
transactorMobile = ei.LegalPersonPhone
|
||||||
|
}
|
||||||
urlRes, err := provider.GetActorSignURL(ctx, &ports.GetActorSignURLRequest{
|
urlRes, err := provider.GetActorSignURL(ctx, &ports.GetActorSignURLRequest{
|
||||||
SignFlowID: cert.EsignFlowID,
|
SignFlowID: cert.EsignFlowID,
|
||||||
TransactorMobile: ei.LegalPersonPhone,
|
TransactorMobile: transactorMobile,
|
||||||
PartyAName: ei.CompanyName,
|
PartyAName: ei.CompanyName,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -753,9 +767,24 @@ func (s *CertificationApplicationServiceImpl) GetCertification(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if cert.Status == enums.StatusInfoSubmitted {
|
if cert.Status == enums.StatusInfoSubmitted {
|
||||||
err = s.checkAndCompleteEnterpriseVerification(ctx, cert)
|
if autoErr := s.checkAndCompleteEnterpriseVerification(ctx, cert); autoErr != nil {
|
||||||
if err != nil {
|
// 自动推进失败时仍返回当前认证详情,避免前端整页报错无法展示步骤
|
||||||
return nil, err
|
s.logger.Error("获取详情时自动完成企业认证失败,返回当前状态",
|
||||||
|
zap.String("user_id", query.UserID),
|
||||||
|
zap.String("cert_status", string(cert.Status)),
|
||||||
|
zap.Error(autoErr))
|
||||||
|
fresh, loadErr := s.aggregateService.LoadCertificationByUserID(ctx, query.UserID)
|
||||||
|
if loadErr != nil {
|
||||||
|
return nil, fmt.Errorf("加载认证信息失败: %w", loadErr)
|
||||||
|
}
|
||||||
|
cert = fresh
|
||||||
|
response := s.convertToResponse(cert)
|
||||||
|
meta, metaErr := s.AddStatusMetadata(ctx, cert)
|
||||||
|
if metaErr == nil && meta != nil {
|
||||||
|
response.Metadata = meta
|
||||||
|
response.Metadata["auto_complete_error"] = autoErr.Error()
|
||||||
|
}
|
||||||
|
return response, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if cert.Status == enums.StatusContractApplied {
|
if cert.Status == enums.StatusContractApplied {
|
||||||
@@ -1407,16 +1436,11 @@ func isEnterpriseAlreadyRealnamedErr(err error) bool {
|
|||||||
return strings.Contains(msg, "企业用户已实名") || strings.Contains(msg, "已实名")
|
return strings.Contains(msg, "企业用户已实名") || strings.Contains(msg, "已实名")
|
||||||
}
|
}
|
||||||
|
|
||||||
// isEnterpriseAlreadyAuthorizedErr 法大大等平台:企业已对本应用授权,无需再获取授权链接(如 code=210002)
|
// isEnterpriseAlreadyAuthorizedErr 法大大:授权范围重复获取,无需再获取授权链接。
|
||||||
|
// 官方错误码说明要求按 code 判断(210002 / 210013),勿依赖 msg 文案。
|
||||||
|
// 文档:https://dev.fadada.com/api-doc/JOQQIJXLFL/H4F0HSKMHQ3HPIIM/5-1
|
||||||
func isEnterpriseAlreadyAuthorizedErr(err error) bool {
|
func isEnterpriseAlreadyAuthorizedErr(err error) bool {
|
||||||
if err == nil {
|
return fadada.IsAuthAlreadyGranted(err)
|
||||||
return false
|
|
||||||
}
|
|
||||||
msg := err.Error()
|
|
||||||
return strings.Contains(msg, "210002") ||
|
|
||||||
strings.Contains(msg, "该企业已授权") ||
|
|
||||||
strings.Contains(msg, "无需重复操作") ||
|
|
||||||
strings.Contains(msg, "已存在授权")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func firstNonEmptyStr(values ...string) string {
|
func firstNonEmptyStr(values ...string) string {
|
||||||
@@ -1465,6 +1489,36 @@ func pickAuthorizedRepName(record *entities.EnterpriseInfoSubmitRecord, legalPer
|
|||||||
return legalPersonName
|
return legalPersonName
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// normalizeCompanyNameParentheses 将企业名中的半角括号 () 转为全角 ()
|
||||||
|
func normalizeCompanyNameParentheses(name string) string {
|
||||||
|
name = strings.TrimSpace(name)
|
||||||
|
if name == "" {
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
replacer := strings.NewReplacer("(", "(", ")", ")")
|
||||||
|
return replacer.Replace(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// pickTransactorFromRecord 法大大经办人:优先授权代表,缺省回退法定代表人
|
||||||
|
func pickTransactorFromRecord(record *entities.EnterpriseInfoSubmitRecord) (name, mobile, idNo string) {
|
||||||
|
if record == nil {
|
||||||
|
return "", "", ""
|
||||||
|
}
|
||||||
|
name = strings.TrimSpace(record.AuthorizedRepName)
|
||||||
|
mobile = strings.TrimSpace(record.AuthorizedRepPhone)
|
||||||
|
idNo = strings.TrimSpace(record.AuthorizedRepID)
|
||||||
|
if name == "" {
|
||||||
|
name = strings.TrimSpace(record.LegalPersonName)
|
||||||
|
}
|
||||||
|
if mobile == "" {
|
||||||
|
mobile = strings.TrimSpace(record.LegalPersonPhone)
|
||||||
|
}
|
||||||
|
if idNo == "" {
|
||||||
|
idNo = strings.TrimSpace(record.LegalPersonID)
|
||||||
|
}
|
||||||
|
return name, mobile, idNo
|
||||||
|
}
|
||||||
|
|
||||||
// pickEnterpriseString 优先用户域企业表字段,为空则用最近一次认证提交记录(避免 enterprise_infos 未同步导致合同控件无值)
|
// pickEnterpriseString 优先用户域企业表字段,为空则用最近一次认证提交记录(避免 enterprise_infos 未同步导致合同控件无值)
|
||||||
func pickEnterpriseString(primary string, record *entities.EnterpriseInfoSubmitRecord, fromRecord func(*entities.EnterpriseInfoSubmitRecord) string) string {
|
func pickEnterpriseString(primary string, record *entities.EnterpriseInfoSubmitRecord, fromRecord func(*entities.EnterpriseInfoSubmitRecord) string) string {
|
||||||
if strings.TrimSpace(primary) != "" {
|
if strings.TrimSpace(primary) != "" {
|
||||||
@@ -1491,14 +1545,24 @@ func (s *CertificationApplicationServiceImpl) generateContractAndSignURL(ctx con
|
|||||||
address = record.EnterpriseAddress
|
address = record.EnterpriseAddress
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
transactorName, transactorMobile, transactorID := pickTransactorFromRecord(record)
|
||||||
|
if transactorName == "" {
|
||||||
|
transactorName = enterpriseInfo.LegalPersonName
|
||||||
|
}
|
||||||
|
if transactorMobile == "" {
|
||||||
|
transactorMobile = enterpriseInfo.LegalPersonPhone
|
||||||
|
}
|
||||||
|
if transactorID == "" {
|
||||||
|
transactorID = enterpriseInfo.LegalPersonID
|
||||||
|
}
|
||||||
req := &ports.SignFlowCreateRequest{
|
req := &ports.SignFlowCreateRequest{
|
||||||
Subject: s.contractSubjectPrefix(cert) + "-" + enterpriseInfo.CompanyName,
|
Subject: s.contractSubjectPrefix(cert) + "-" + enterpriseInfo.CompanyName,
|
||||||
FileID: cert.ContractFileID,
|
FileID: cert.ContractFileID,
|
||||||
PartyAName: enterpriseInfo.CompanyName,
|
PartyAName: enterpriseInfo.CompanyName,
|
||||||
PartyAUSCC: enterpriseInfo.UnifiedSocialCode,
|
PartyAUSCC: enterpriseInfo.UnifiedSocialCode,
|
||||||
TransactorName: enterpriseInfo.LegalPersonName,
|
TransactorName: transactorName,
|
||||||
TransactorMobile: enterpriseInfo.LegalPersonPhone,
|
TransactorMobile: transactorMobile,
|
||||||
TransactorID: enterpriseInfo.LegalPersonID,
|
TransactorID: transactorID,
|
||||||
TransReferenceID: cert.ID,
|
TransReferenceID: cert.ID,
|
||||||
Fill: ensureContractFill(cert, enterpriseInfo.CompanyName, enterpriseInfo.UnifiedSocialCode, address, repName),
|
Fill: ensureContractFill(cert, enterpriseInfo.CompanyName, enterpriseInfo.UnifiedSocialCode, address, repName),
|
||||||
}
|
}
|
||||||
@@ -1545,7 +1609,8 @@ func (s *CertificationApplicationServiceImpl) completeEnterpriseVerification(
|
|||||||
zap.String("user_id", userID),
|
zap.String("user_id", userID),
|
||||||
zap.String("record_id", record.ID))
|
zap.String("record_id", record.ID))
|
||||||
|
|
||||||
err = s.userAggregateService.CreateEnterpriseInfo(
|
// 新用户:创建企业信息;重新认证:更新已有企业信息(CreateOrUpdateEnterpriseInfo 内部分流)
|
||||||
|
err = s.userAggregateService.CreateOrUpdateEnterpriseInfo(
|
||||||
ctx,
|
ctx,
|
||||||
userID,
|
userID,
|
||||||
record.CompanyName,
|
record.CompanyName,
|
||||||
@@ -1558,9 +1623,8 @@ func (s *CertificationApplicationServiceImpl) completeEnterpriseVerification(
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
s.logger.Error("保存企业信息到用户域失败", zap.Error(err))
|
s.logger.Error("保存企业信息到用户域失败", zap.Error(err))
|
||||||
return fmt.Errorf("保存企业信息失败: %s", err.Error())
|
return fmt.Errorf("保存企业信息失败: %s", err.Error())
|
||||||
} else {
|
|
||||||
s.logger.Info("企业信息已保存到用户域", zap.String("user_id", userID))
|
|
||||||
}
|
}
|
||||||
|
s.logger.Info("企业信息已写入用户域(新用户创建/重新认证更新)", zap.String("user_id", userID))
|
||||||
|
|
||||||
err = s.generateAndAddContractFile(ctx, cert, record.CompanyName, record.UnifiedSocialCode, record.EnterpriseAddress, pickAuthorizedRepName(record, record.LegalPersonName))
|
err = s.generateAndAddContractFile(ctx, cert, record.CompanyName, record.UnifiedSocialCode, record.EnterpriseAddress, pickAuthorizedRepName(record, record.LegalPersonName))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1605,14 +1669,15 @@ func (s *CertificationApplicationServiceImpl) createSignTaskAfterEnterpriseVerif
|
|||||||
}
|
}
|
||||||
repName := pickAuthorizedRepName(record, record.LegalPersonName)
|
repName := pickAuthorizedRepName(record, record.LegalPersonName)
|
||||||
address := record.EnterpriseAddress
|
address := record.EnterpriseAddress
|
||||||
|
transactorName, transactorMobile, transactorID := pickTransactorFromRecord(record)
|
||||||
req := &ports.SignFlowCreateRequest{
|
req := &ports.SignFlowCreateRequest{
|
||||||
Subject: s.contractSubjectPrefix(cert) + "-" + record.CompanyName,
|
Subject: s.contractSubjectPrefix(cert) + "-" + record.CompanyName,
|
||||||
FileID: cert.ContractFileID,
|
FileID: cert.ContractFileID,
|
||||||
PartyAName: record.CompanyName,
|
PartyAName: record.CompanyName,
|
||||||
PartyAUSCC: record.UnifiedSocialCode,
|
PartyAUSCC: record.UnifiedSocialCode,
|
||||||
TransactorName: record.LegalPersonName,
|
TransactorName: transactorName,
|
||||||
TransactorMobile: record.LegalPersonPhone,
|
TransactorMobile: transactorMobile,
|
||||||
TransactorID: record.LegalPersonID,
|
TransactorID: transactorID,
|
||||||
TransReferenceID: cert.ID,
|
TransReferenceID: cert.ID,
|
||||||
Fill: ensureContractFill(cert, record.CompanyName, record.UnifiedSocialCode, address, repName),
|
Fill: ensureContractFill(cert, record.CompanyName, record.UnifiedSocialCode, address, repName),
|
||||||
SkipActorURL: true,
|
SkipActorURL: true,
|
||||||
|
|||||||
@@ -88,16 +88,17 @@ func (s *CertificationApplicationServiceImpl) SelectSignPlatform(
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
transactorName, transactorMobile, transactorID := pickTransactorFromRecord(record)
|
||||||
authReq := &ports.EnterpriseAuthRequest{
|
authReq := &ports.EnterpriseAuthRequest{
|
||||||
ClientCorpID: record.UnifiedSocialCode,
|
ClientCorpID: record.UnifiedSocialCode,
|
||||||
ClientUserID: record.LegalPersonID,
|
ClientUserID: transactorID,
|
||||||
CompanyName: record.CompanyName,
|
CompanyName: record.CompanyName,
|
||||||
UnifiedSocialCode: record.UnifiedSocialCode,
|
UnifiedSocialCode: record.UnifiedSocialCode,
|
||||||
LegalPersonName: record.LegalPersonName,
|
LegalPersonName: record.LegalPersonName,
|
||||||
LegalPersonID: record.LegalPersonID,
|
LegalPersonID: record.LegalPersonID,
|
||||||
TransactorName: record.LegalPersonName,
|
TransactorName: transactorName,
|
||||||
TransactorMobile: record.LegalPersonPhone,
|
TransactorMobile: transactorMobile,
|
||||||
TransactorID: record.LegalPersonID,
|
TransactorID: transactorID,
|
||||||
}
|
}
|
||||||
|
|
||||||
authRes, alreadyVerified, err := s.generateEnterpriseAuthOrDetectVerifiedWithProvider(ctx, provider, authReq)
|
authRes, alreadyVerified, err := s.generateEnterpriseAuthOrDetectVerifiedWithProvider(ctx, provider, authReq)
|
||||||
@@ -192,7 +193,7 @@ func (s *CertificationApplicationServiceImpl) generateEnterpriseAuthOrDetectVeri
|
|||||||
return authRes, false, nil
|
return authRes, false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// 法大大:企业已对本应用授权(210002)= 授权已完成,直接跳过授权页
|
// 法大大:授权范围重复获取(code=210002/210013)= 已对本应用授权,跳过授权页
|
||||||
if isEnterpriseAlreadyAuthorizedErr(err) {
|
if isEnterpriseAlreadyAuthorizedErr(err) {
|
||||||
s.logger.Info("第三方返回企业已授权,跳过认证链接并视为认证完成",
|
s.logger.Info("第三方返回企业已授权,跳过认证链接并视为认证完成",
|
||||||
zap.String("company_name", authReq.CompanyName),
|
zap.String("company_name", authReq.CompanyName),
|
||||||
|
|||||||
@@ -467,7 +467,9 @@ func (s *UserAggregateServiceImpl) CheckUnifiedSocialCodeExists(ctx context.Cont
|
|||||||
return exists, nil
|
return exists, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateOrUpdateEnterpriseInfo 认证域专用:写入/覆盖企业信息
|
// CreateOrUpdateEnterpriseInfo 认证域专用:按两条链路写入企业信息
|
||||||
|
// - 新用户:尚无企业信息 → 创建
|
||||||
|
// - 重新认证:已有企业信息 → 覆盖更新
|
||||||
func (s *UserAggregateServiceImpl) CreateOrUpdateEnterpriseInfo(
|
func (s *UserAggregateServiceImpl) CreateOrUpdateEnterpriseInfo(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
userID, companyName, unifiedSocialCode, legalPersonName, legalPersonID, legalPersonPhone, enterpriseAddress string,
|
userID, companyName, unifiedSocialCode, legalPersonName, legalPersonID, legalPersonPhone, enterpriseAddress string,
|
||||||
@@ -476,19 +478,44 @@ func (s *UserAggregateServiceImpl) CreateOrUpdateEnterpriseInfo(
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("用户不存在: %w", err)
|
return fmt.Errorf("用户不存在: %w", err)
|
||||||
}
|
}
|
||||||
if user.EnterpriseInfo == nil {
|
|
||||||
enterpriseInfo, err := entities.NewEnterpriseInfo(userID, companyName, unifiedSocialCode, legalPersonName, legalPersonID, legalPersonPhone, enterpriseAddress)
|
// 统一校验 USCC(排除本人),新用户创建与重新认证更新均适用
|
||||||
if err != nil {
|
exists, err := s.CheckUnifiedSocialCodeExists(ctx, unifiedSocialCode, userID)
|
||||||
return err
|
if err != nil {
|
||||||
}
|
return fmt.Errorf("检查统一社会信用代码失败: %w", err)
|
||||||
user.EnterpriseInfo = enterpriseInfo
|
|
||||||
} else {
|
|
||||||
err := user.EnterpriseInfo.UpdateEnterpriseInfo(companyName, unifiedSocialCode, legalPersonName, legalPersonID, legalPersonPhone, enterpriseAddress)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return s.SaveUser(ctx, user)
|
if exists {
|
||||||
|
return fmt.Errorf("统一社会信用代码已被其他用户使用")
|
||||||
|
}
|
||||||
|
|
||||||
|
if user.HasEnterpriseInfo() {
|
||||||
|
s.logger.Info("重新认证链路:更新已有企业信息",
|
||||||
|
zap.String("user_id", userID),
|
||||||
|
zap.String("unified_social_code", unifiedSocialCode),
|
||||||
|
zap.String("enterprise_info_id", user.EnterpriseInfo.ID),
|
||||||
|
)
|
||||||
|
if err := user.UpdateEnterpriseInfo(companyName, unifiedSocialCode, legalPersonName, legalPersonID, legalPersonPhone, enterpriseAddress); err != nil {
|
||||||
|
return fmt.Errorf("更新企业信息失败: %w", err)
|
||||||
|
}
|
||||||
|
if err := s.SaveUser(ctx, user); err != nil {
|
||||||
|
return fmt.Errorf("保存企业信息失败: %w", err)
|
||||||
|
}
|
||||||
|
s.logger.Info("重新认证链路:企业信息更新成功", zap.String("user_id", userID))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
s.logger.Info("新用户认证链路:创建企业信息",
|
||||||
|
zap.String("user_id", userID),
|
||||||
|
zap.String("unified_social_code", unifiedSocialCode),
|
||||||
|
)
|
||||||
|
if err := user.CreateEnterpriseInfo(companyName, unifiedSocialCode, legalPersonName, legalPersonID, legalPersonPhone, enterpriseAddress); err != nil {
|
||||||
|
return fmt.Errorf("创建企业信息失败: %w", err)
|
||||||
|
}
|
||||||
|
if err := s.SaveUser(ctx, user); err != nil {
|
||||||
|
return fmt.Errorf("保存企业信息失败: %w", err)
|
||||||
|
}
|
||||||
|
s.logger.Info("新用户认证链路:企业信息创建成功", zap.String("user_id", userID))
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// CompleteCertification 完成认证
|
// CompleteCertification 完成认证
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ func (c *Client) GenerateEnterpriseAuth(req *EnterpriseAuthRequest) (*Enterprise
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if res.Code != successCode {
|
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
|
var data getCorpAuthURLData
|
||||||
@@ -128,7 +128,7 @@ func (c *Client) QueryOrgIdentity(req *QueryOrgIdentityRequest) (*QueryOrgIdenti
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if res.Code != successCode {
|
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
|
var data getIdentifiedStatusData
|
||||||
|
|||||||
89
internal/shared/fadada/errors.go
Normal file
89
internal/shared/fadada/errors.go
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
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" // 业务场景标识不存在
|
||||||
|
|
||||||
|
// 授权范围重复获取:主体已对本应用授权,无须再获取授权链接
|
||||||
|
// 个人用户帐号管理 / 企业用户帐号管理 均有说明
|
||||||
|
CodeAuthScopeDuplicate = "210002" // 授权范围重复获取
|
||||||
|
CodeAuthScopeDuplicateCorp = "210013" // 授权范围重复获取(无须重复获取授权申请链接)
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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 是否表示「已授权 / 授权范围重复获取」,应跳过再次获取授权链接。
|
||||||
|
// 官方文档:应按返回码 code 判断,msg 可能调整。
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user