This commit is contained in:
2026-07-27 00:03:07 +08:00
parent 7244df5ae1
commit c3b0de6ac5
7 changed files with 281 additions and 48 deletions

View File

@@ -2,7 +2,10 @@ package certification
import (
"errors"
"fmt"
"testing"
"tyapi-server/internal/shared/fadada"
)
func TestIsEnterpriseAlreadyAuthorizedErr(t *testing.T) {
@@ -12,18 +15,38 @@ func TestIsEnterpriseAlreadyAuthorizedErr(t *testing.T) {
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"),
want: true,
},
{
name: "exist authorize wording",
err: errors.New("当前应用已存在授权"),
name: "legacy string 210013",
err: errors.New("获取法大大企业认证链接失败: code=210013 msg=授权范围重复获取 requestId=xyz"),
want: true,
},
{
name: "other error",
err: errors.New("获取法大大企业认证链接失败: code=100011 msg=参数错误"),
name: "wrapped structured 210002",
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,
},
{

View File

@@ -30,6 +30,7 @@ import (
"tyapi-server/internal/infrastructure/external/storage"
"tyapi-server/internal/shared/database"
"tyapi-server/internal/shared/esign"
"tyapi-server/internal/shared/fadada"
sharedOCR "tyapi-server/internal/shared/ocr"
"github.com/shopspring/decimal"
@@ -171,6 +172,9 @@ func (s *CertificationApplicationServiceImpl) SubmitEnterpriseInfo(
}
}
// 企业名半角括号统一为全角,避免与工商登记不一致
cmd.CompanyName = normalizeCompanyNameParentheses(cmd.CompanyName)
// 1.5 插入企业信息提交记录(包含扩展字段)
record := entities.NewEnterpriseInfoSubmitRecord(
cmd.UserID,
@@ -557,9 +561,14 @@ func (s *CertificationApplicationServiceImpl) ApplyContract(
return nil, err
}
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{
SignFlowID: flowID,
TransactorMobile: ei.LegalPersonPhone,
TransactorMobile: transactorMobile,
PartyAName: ei.CompanyName,
})
if err != nil {
@@ -635,9 +644,14 @@ func (s *CertificationApplicationServiceImpl) RefreshContractSignURL(
return nil, fmt.Errorf("获取企业信息失败: %w", err)
}
ei := enterpriseInfo.EnterpriseInfo
submitRec, _ := s.enterpriseInfoSubmitRecordRepo.FindLatestByUserID(ctx, userID)
_, transactorMobile, _ := pickTransactorFromRecord(submitRec)
if transactorMobile == "" {
transactorMobile = ei.LegalPersonPhone
}
urlRes, err := provider.GetActorSignURL(ctx, &ports.GetActorSignURLRequest{
SignFlowID: cert.EsignFlowID,
TransactorMobile: ei.LegalPersonPhone,
TransactorMobile: transactorMobile,
PartyAName: ei.CompanyName,
})
if err != nil {
@@ -753,9 +767,24 @@ func (s *CertificationApplicationServiceImpl) GetCertification(
}
if cert.Status == enums.StatusInfoSubmitted {
err = s.checkAndCompleteEnterpriseVerification(ctx, cert)
if err != nil {
return nil, err
if autoErr := s.checkAndCompleteEnterpriseVerification(ctx, cert); autoErr != nil {
// 自动推进失败时仍返回当前认证详情,避免前端整页报错无法展示步骤
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 {
@@ -1407,16 +1436,11 @@ func isEnterpriseAlreadyRealnamedErr(err error) bool {
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 {
if err == nil {
return false
}
msg := err.Error()
return strings.Contains(msg, "210002") ||
strings.Contains(msg, "该企业已授权") ||
strings.Contains(msg, "无需重复操作") ||
strings.Contains(msg, "已存在授权")
return fadada.IsAuthAlreadyGranted(err)
}
func firstNonEmptyStr(values ...string) string {
@@ -1465,6 +1489,36 @@ func pickAuthorizedRepName(record *entities.EnterpriseInfoSubmitRecord, legalPer
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 未同步导致合同控件无值)
func pickEnterpriseString(primary string, record *entities.EnterpriseInfoSubmitRecord, fromRecord func(*entities.EnterpriseInfoSubmitRecord) string) string {
if strings.TrimSpace(primary) != "" {
@@ -1491,14 +1545,24 @@ func (s *CertificationApplicationServiceImpl) generateContractAndSignURL(ctx con
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{
Subject: s.contractSubjectPrefix(cert) + "-" + enterpriseInfo.CompanyName,
FileID: cert.ContractFileID,
PartyAName: enterpriseInfo.CompanyName,
PartyAUSCC: enterpriseInfo.UnifiedSocialCode,
TransactorName: enterpriseInfo.LegalPersonName,
TransactorMobile: enterpriseInfo.LegalPersonPhone,
TransactorID: enterpriseInfo.LegalPersonID,
TransactorName: transactorName,
TransactorMobile: transactorMobile,
TransactorID: transactorID,
TransReferenceID: cert.ID,
Fill: ensureContractFill(cert, enterpriseInfo.CompanyName, enterpriseInfo.UnifiedSocialCode, address, repName),
}
@@ -1545,7 +1609,8 @@ func (s *CertificationApplicationServiceImpl) completeEnterpriseVerification(
zap.String("user_id", userID),
zap.String("record_id", record.ID))
err = s.userAggregateService.CreateEnterpriseInfo(
// 新用户创建企业信息重新认证更新已有企业信息CreateOrUpdateEnterpriseInfo 内部分流)
err = s.userAggregateService.CreateOrUpdateEnterpriseInfo(
ctx,
userID,
record.CompanyName,
@@ -1558,9 +1623,8 @@ func (s *CertificationApplicationServiceImpl) completeEnterpriseVerification(
if err != nil {
s.logger.Error("保存企业信息到用户域失败", zap.Error(err))
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))
if err != nil {
@@ -1605,14 +1669,15 @@ func (s *CertificationApplicationServiceImpl) createSignTaskAfterEnterpriseVerif
}
repName := pickAuthorizedRepName(record, record.LegalPersonName)
address := record.EnterpriseAddress
transactorName, transactorMobile, transactorID := pickTransactorFromRecord(record)
req := &ports.SignFlowCreateRequest{
Subject: s.contractSubjectPrefix(cert) + "-" + record.CompanyName,
FileID: cert.ContractFileID,
PartyAName: record.CompanyName,
PartyAUSCC: record.UnifiedSocialCode,
TransactorName: record.LegalPersonName,
TransactorMobile: record.LegalPersonPhone,
TransactorID: record.LegalPersonID,
TransactorName: transactorName,
TransactorMobile: transactorMobile,
TransactorID: transactorID,
TransReferenceID: cert.ID,
Fill: ensureContractFill(cert, record.CompanyName, record.UnifiedSocialCode, address, repName),
SkipActorURL: true,

View File

@@ -88,16 +88,17 @@ func (s *CertificationApplicationServiceImpl) SelectSignPlatform(
return nil, err
}
transactorName, transactorMobile, transactorID := pickTransactorFromRecord(record)
authReq := &ports.EnterpriseAuthRequest{
ClientCorpID: record.UnifiedSocialCode,
ClientUserID: record.LegalPersonID,
ClientUserID: transactorID,
CompanyName: record.CompanyName,
UnifiedSocialCode: record.UnifiedSocialCode,
LegalPersonName: record.LegalPersonName,
LegalPersonID: record.LegalPersonID,
TransactorName: record.LegalPersonName,
TransactorMobile: record.LegalPersonPhone,
TransactorID: record.LegalPersonID,
TransactorName: transactorName,
TransactorMobile: transactorMobile,
TransactorID: transactorID,
}
authRes, alreadyVerified, err := s.generateEnterpriseAuthOrDetectVerifiedWithProvider(ctx, provider, authReq)
@@ -192,7 +193,7 @@ func (s *CertificationApplicationServiceImpl) generateEnterpriseAuthOrDetectVeri
return authRes, false, nil
}
// 法大大:企业已对本应用授权210002= 授权已完成,直接跳过授权页
// 法大大:授权范围重复获取code=210002/210013= 已对本应用授权,跳过授权页
if isEnterpriseAlreadyAuthorizedErr(err) {
s.logger.Info("第三方返回企业已授权,跳过认证链接并视为认证完成",
zap.String("company_name", authReq.CompanyName),