This commit is contained in:
2026-07-27 12:37:29 +08:00
parent dc9a98e893
commit 9429e4f6e0
20 changed files with 1066 additions and 61 deletions

View File

@@ -27,6 +27,7 @@ import (
"hyapi-server/internal/infrastructure/external/storage"
"hyapi-server/internal/shared/database"
"hyapi-server/internal/shared/esign"
"hyapi-server/internal/shared/fadada"
sharedOCR "hyapi-server/internal/shared/ocr"
"go.uber.org/zap"
@@ -51,6 +52,7 @@ type CertificationApplicationServiceImpl struct {
// 仓储依赖
queryRepository repositories.CertificationQueryRepository
enterpriseInfoSubmitRecordRepo repositories.EnterpriseInfoSubmitRecordRepository
fadadaCorpAuthRecordRepo repositories.FadadaCorpAuthRecordRepository
txManager *database.TransactionManager
wechatWorkService *notification.WeChatWorkService
@@ -64,6 +66,7 @@ func NewCertificationApplicationService(
userAggregateService user_service.UserAggregateService,
queryRepository repositories.CertificationQueryRepository,
enterpriseInfoSubmitRecordRepo repositories.EnterpriseInfoSubmitRecordRepository,
fadadaCorpAuthRecordRepo repositories.FadadaCorpAuthRecordRepository,
smsCodeService *user_service.SMSCodeService,
esignClient *esign.Client,
esignConfig *esign.Config,
@@ -87,6 +90,7 @@ func NewCertificationApplicationService(
userAggregateService: userAggregateService,
queryRepository: queryRepository,
enterpriseInfoSubmitRecordRepo: enterpriseInfoSubmitRecordRepo,
fadadaCorpAuthRecordRepo: fadadaCorpAuthRecordRepo,
smsCodeService: smsCodeService,
esignClient: esignClient,
esignConfig: esignConfig,
@@ -160,6 +164,9 @@ func (s *CertificationApplicationServiceImpl) SubmitEnterpriseInfo(
}
}
// 企业名半角括号统一为全角,避免与工商登记不一致
cmd.CompanyName = normalizeCompanyNameParentheses(cmd.CompanyName)
// 1.5 插入企业信息提交记录(包含扩展字段)
record := entities.NewEnterpriseInfoSubmitRecord(
cmd.UserID,
@@ -445,7 +452,7 @@ func (s *CertificationApplicationServiceImpl) ConfirmAuth(
s.logger.Info("确认状态-步骤2-获取最近提交记录成功",
zap.String("user_id", cmd.UserID),
zap.String("record_id", record.ID))
s.logger.Info("确认状态-步骤3-开始查询三方实名状态",
s.logger.Info("确认状态-步骤3-开始查询三方实名/授权状态",
zap.String("user_id", cmd.UserID),
zap.String("company_name", record.CompanyName),
zap.String("sign_platform", string(cert.ResolvedSignPlatform())))
@@ -453,6 +460,19 @@ func (s *CertificationApplicationServiceImpl) ConfirmAuth(
if err != nil {
return nil, err
}
// 法大大ConfirmAuth 时再查一次授权状态并落库 openCorpId
authorizedByCorpGet := false
if cert.ResolvedSignPlatform() == enums.SignPlatformFadada {
authQuery := s.resolveCorpAuthQueryForGet(ctx, cert.UserID, record.UnifiedSocialCode, record.UnifiedSocialCode)
if authStatus, qErr := provider.QueryCorpAuthStatus(ctx, authQuery); qErr != nil {
s.logger.Warn("确认认证时查询企业授权状态失败", zap.Error(qErr), zap.String("user_id", cmd.UserID))
} else if authStatus != nil && authStatus.Supported {
s.persistFadadaCorpAuthStatus(ctx, cert, record, authStatus, "corp_get_confirm_auth")
authorizedByCorpGet = authStatus.Authorized
}
}
verified, err := provider.QueryOrgVerified(ctx, &ports.OrgIdentityQuery{
OrgName: record.CompanyName,
OrgIdentNo: record.UnifiedSocialCode,
@@ -461,6 +481,9 @@ func (s *CertificationApplicationServiceImpl) ConfirmAuth(
s.logger.Error("查询企业认证信息失败", zap.Error(err))
return nil, fmt.Errorf("查询企业认证信息失败: %w", err)
}
if authorizedByCorpGet {
verified = true
}
reason := ""
if verified {
s.logger.Info("确认状态-步骤3-三方实名状态已完成,准备事务内推进认证",
@@ -547,9 +570,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 {
@@ -625,9 +653,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 {
@@ -1349,16 +1382,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)
}
// validateApplyContractCommand 验证申请合同命令
@@ -1380,6 +1408,44 @@ func (s *CertificationApplicationServiceImpl) validateContractApplicationPrecond
return nil
}
// pickAuthorizedRepName 合同模板「客户授权代表」: 优先企业提交记录中的授权代表, 否则为法定代表人
func pickAuthorizedRepName(record *entities.EnterpriseInfoSubmitRecord, legalPersonName string) string {
if record != nil && strings.TrimSpace(record.AuthorizedRepName) != "" {
return strings.TrimSpace(record.AuthorizedRepName)
}
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
}
// generateContractAndSignURL 生成合同和签署链接
func (s *CertificationApplicationServiceImpl) generateContractAndSignURL(ctx context.Context, cert *entities.Certification, enterpriseInfo *user_entities.EnterpriseInfo) (*certification_value_objects.ContractInfo, error) {
provider, err := s.resolveProvider(cert)
@@ -1395,14 +1461,25 @@ 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: "海宇数据-合作协议-" + enterpriseInfo.CompanyName,
FileID: cert.ContractFileID,
PartyAOpenCorpID: s.partyAOpenCorpIDFromStore(ctx, cert.UserID, enterpriseInfo.UnifiedSocialCode),
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),
}
@@ -1450,6 +1527,7 @@ func (s *CertificationApplicationServiceImpl) completeEnterpriseVerification(
zap.String("user_id", userID),
zap.String("record_id", record.ID))
// 新用户创建企业信息重新认证更新已有企业信息CreateOrUpdateEnterpriseInfo 内部分流)
err = s.userAggregateService.CreateOrUpdateEnterpriseInfo(
ctx,
userID,
@@ -1464,7 +1542,7 @@ func (s *CertificationApplicationServiceImpl) completeEnterpriseVerification(
s.logger.Error("保存企业信息到用户域失败", zap.Error(err))
return fmt.Errorf("保存企业信息失败: %s", err.Error())
}
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))
@@ -1512,14 +1590,16 @@ func (s *CertificationApplicationServiceImpl) createSignTaskAfterEnterpriseVerif
}
repName := pickAuthorizedRepName(record, record.LegalPersonName)
address := record.EnterpriseAddress
transactorName, transactorMobile, transactorID := pickTransactorFromRecord(record)
req := &ports.SignFlowCreateRequest{
Subject: "海宇数据-合作协议-" + record.CompanyName,
FileID: cert.ContractFileID,
PartyAOpenCorpID: s.partyAOpenCorpIDFromStore(ctx, cert.UserID, record.UnifiedSocialCode),
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, // 预创建不取甲方链接,避免消耗单次 EmbedURL
@@ -1534,14 +1614,6 @@ func (s *CertificationApplicationServiceImpl) createSignTaskAfterEnterpriseVerif
return nil
}
// pickAuthorizedRepName 合同模板「客户授权代表」: 优先企业提交记录中的授权代表, 否则为法定代表人
func pickAuthorizedRepName(record *entities.EnterpriseInfoSubmitRecord, legalPersonName string) string {
if record != nil && strings.TrimSpace(record.AuthorizedRepName) != "" {
return strings.TrimSpace(record.AuthorizedRepName)
}
return legalPersonName
}
// generateAndAddContractFile 生成并添加合同文件的公共方法
func (s *CertificationApplicationServiceImpl) generateAndAddContractFile(
ctx context.Context,