f
This commit is contained in:
@@ -222,6 +222,7 @@ func (a *Application) autoMigrate(db *gorm.DB) error {
|
|||||||
// 认证域
|
// 认证域
|
||||||
&certEntities.Certification{},
|
&certEntities.Certification{},
|
||||||
&certEntities.EnterpriseInfoSubmitRecord{},
|
&certEntities.EnterpriseInfoSubmitRecord{},
|
||||||
|
&certEntities.FadadaCorpAuthRecord{},
|
||||||
&certEntities.EsignContractGenerateRecord{},
|
&certEntities.EsignContractGenerateRecord{},
|
||||||
&certEntities.EsignContractSignRecord{},
|
&certEntities.EsignContractSignRecord{},
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,10 @@ package certification
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"hyapi-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,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import (
|
|||||||
"hyapi-server/internal/infrastructure/external/storage"
|
"hyapi-server/internal/infrastructure/external/storage"
|
||||||
"hyapi-server/internal/shared/database"
|
"hyapi-server/internal/shared/database"
|
||||||
"hyapi-server/internal/shared/esign"
|
"hyapi-server/internal/shared/esign"
|
||||||
|
"hyapi-server/internal/shared/fadada"
|
||||||
sharedOCR "hyapi-server/internal/shared/ocr"
|
sharedOCR "hyapi-server/internal/shared/ocr"
|
||||||
|
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
@@ -51,6 +52,7 @@ type CertificationApplicationServiceImpl struct {
|
|||||||
// 仓储依赖
|
// 仓储依赖
|
||||||
queryRepository repositories.CertificationQueryRepository
|
queryRepository repositories.CertificationQueryRepository
|
||||||
enterpriseInfoSubmitRecordRepo repositories.EnterpriseInfoSubmitRecordRepository
|
enterpriseInfoSubmitRecordRepo repositories.EnterpriseInfoSubmitRecordRepository
|
||||||
|
fadadaCorpAuthRecordRepo repositories.FadadaCorpAuthRecordRepository
|
||||||
txManager *database.TransactionManager
|
txManager *database.TransactionManager
|
||||||
|
|
||||||
wechatWorkService *notification.WeChatWorkService
|
wechatWorkService *notification.WeChatWorkService
|
||||||
@@ -64,6 +66,7 @@ func NewCertificationApplicationService(
|
|||||||
userAggregateService user_service.UserAggregateService,
|
userAggregateService user_service.UserAggregateService,
|
||||||
queryRepository repositories.CertificationQueryRepository,
|
queryRepository repositories.CertificationQueryRepository,
|
||||||
enterpriseInfoSubmitRecordRepo repositories.EnterpriseInfoSubmitRecordRepository,
|
enterpriseInfoSubmitRecordRepo repositories.EnterpriseInfoSubmitRecordRepository,
|
||||||
|
fadadaCorpAuthRecordRepo repositories.FadadaCorpAuthRecordRepository,
|
||||||
smsCodeService *user_service.SMSCodeService,
|
smsCodeService *user_service.SMSCodeService,
|
||||||
esignClient *esign.Client,
|
esignClient *esign.Client,
|
||||||
esignConfig *esign.Config,
|
esignConfig *esign.Config,
|
||||||
@@ -87,6 +90,7 @@ func NewCertificationApplicationService(
|
|||||||
userAggregateService: userAggregateService,
|
userAggregateService: userAggregateService,
|
||||||
queryRepository: queryRepository,
|
queryRepository: queryRepository,
|
||||||
enterpriseInfoSubmitRecordRepo: enterpriseInfoSubmitRecordRepo,
|
enterpriseInfoSubmitRecordRepo: enterpriseInfoSubmitRecordRepo,
|
||||||
|
fadadaCorpAuthRecordRepo: fadadaCorpAuthRecordRepo,
|
||||||
smsCodeService: smsCodeService,
|
smsCodeService: smsCodeService,
|
||||||
esignClient: esignClient,
|
esignClient: esignClient,
|
||||||
esignConfig: esignConfig,
|
esignConfig: esignConfig,
|
||||||
@@ -160,6 +164,9 @@ func (s *CertificationApplicationServiceImpl) SubmitEnterpriseInfo(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 企业名半角括号统一为全角,避免与工商登记不一致
|
||||||
|
cmd.CompanyName = normalizeCompanyNameParentheses(cmd.CompanyName)
|
||||||
|
|
||||||
// 1.5 插入企业信息提交记录(包含扩展字段)
|
// 1.5 插入企业信息提交记录(包含扩展字段)
|
||||||
record := entities.NewEnterpriseInfoSubmitRecord(
|
record := entities.NewEnterpriseInfoSubmitRecord(
|
||||||
cmd.UserID,
|
cmd.UserID,
|
||||||
@@ -445,7 +452,7 @@ func (s *CertificationApplicationServiceImpl) ConfirmAuth(
|
|||||||
s.logger.Info("确认状态-步骤2-获取最近提交记录成功",
|
s.logger.Info("确认状态-步骤2-获取最近提交记录成功",
|
||||||
zap.String("user_id", cmd.UserID),
|
zap.String("user_id", cmd.UserID),
|
||||||
zap.String("record_id", record.ID))
|
zap.String("record_id", record.ID))
|
||||||
s.logger.Info("确认状态-步骤3-开始查询三方实名状态",
|
s.logger.Info("确认状态-步骤3-开始查询三方实名/授权状态",
|
||||||
zap.String("user_id", cmd.UserID),
|
zap.String("user_id", cmd.UserID),
|
||||||
zap.String("company_name", record.CompanyName),
|
zap.String("company_name", record.CompanyName),
|
||||||
zap.String("sign_platform", string(cert.ResolvedSignPlatform())))
|
zap.String("sign_platform", string(cert.ResolvedSignPlatform())))
|
||||||
@@ -453,6 +460,19 @@ func (s *CertificationApplicationServiceImpl) ConfirmAuth(
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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{
|
verified, err := provider.QueryOrgVerified(ctx, &ports.OrgIdentityQuery{
|
||||||
OrgName: record.CompanyName,
|
OrgName: record.CompanyName,
|
||||||
OrgIdentNo: record.UnifiedSocialCode,
|
OrgIdentNo: record.UnifiedSocialCode,
|
||||||
@@ -461,6 +481,9 @@ func (s *CertificationApplicationServiceImpl) ConfirmAuth(
|
|||||||
s.logger.Error("查询企业认证信息失败", zap.Error(err))
|
s.logger.Error("查询企业认证信息失败", zap.Error(err))
|
||||||
return nil, fmt.Errorf("查询企业认证信息失败: %w", err)
|
return nil, fmt.Errorf("查询企业认证信息失败: %w", err)
|
||||||
}
|
}
|
||||||
|
if authorizedByCorpGet {
|
||||||
|
verified = true
|
||||||
|
}
|
||||||
reason := ""
|
reason := ""
|
||||||
if verified {
|
if verified {
|
||||||
s.logger.Info("确认状态-步骤3-三方实名状态已完成,准备事务内推进认证",
|
s.logger.Info("确认状态-步骤3-三方实名状态已完成,准备事务内推进认证",
|
||||||
@@ -547,9 +570,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 {
|
||||||
@@ -625,9 +653,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 {
|
||||||
@@ -1349,16 +1382,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, "已存在授权")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// validateApplyContractCommand 验证申请合同命令
|
// validateApplyContractCommand 验证申请合同命令
|
||||||
@@ -1380,6 +1408,44 @@ func (s *CertificationApplicationServiceImpl) validateContractApplicationPrecond
|
|||||||
return nil
|
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 生成合同和签署链接
|
// generateContractAndSignURL 生成合同和签署链接
|
||||||
func (s *CertificationApplicationServiceImpl) generateContractAndSignURL(ctx context.Context, cert *entities.Certification, enterpriseInfo *user_entities.EnterpriseInfo) (*certification_value_objects.ContractInfo, error) {
|
func (s *CertificationApplicationServiceImpl) generateContractAndSignURL(ctx context.Context, cert *entities.Certification, enterpriseInfo *user_entities.EnterpriseInfo) (*certification_value_objects.ContractInfo, error) {
|
||||||
provider, err := s.resolveProvider(cert)
|
provider, err := s.resolveProvider(cert)
|
||||||
@@ -1395,14 +1461,25 @@ 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: "海宇数据-合作协议-" + enterpriseInfo.CompanyName,
|
Subject: "海宇数据-合作协议-" + enterpriseInfo.CompanyName,
|
||||||
FileID: cert.ContractFileID,
|
FileID: cert.ContractFileID,
|
||||||
|
PartyAOpenCorpID: s.partyAOpenCorpIDFromStore(ctx, cert.UserID, enterpriseInfo.UnifiedSocialCode),
|
||||||
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),
|
||||||
}
|
}
|
||||||
@@ -1450,6 +1527,7 @@ 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))
|
||||||
|
|
||||||
|
// 新用户:创建企业信息;重新认证:更新已有企业信息(CreateOrUpdateEnterpriseInfo 内部分流)
|
||||||
err = s.userAggregateService.CreateOrUpdateEnterpriseInfo(
|
err = s.userAggregateService.CreateOrUpdateEnterpriseInfo(
|
||||||
ctx,
|
ctx,
|
||||||
userID,
|
userID,
|
||||||
@@ -1464,7 +1542,7 @@ func (s *CertificationApplicationServiceImpl) completeEnterpriseVerification(
|
|||||||
s.logger.Error("保存企业信息到用户域失败", zap.Error(err))
|
s.logger.Error("保存企业信息到用户域失败", zap.Error(err))
|
||||||
return fmt.Errorf("保存企业信息失败: %s", err.Error())
|
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))
|
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)
|
repName := pickAuthorizedRepName(record, record.LegalPersonName)
|
||||||
address := record.EnterpriseAddress
|
address := record.EnterpriseAddress
|
||||||
|
transactorName, transactorMobile, transactorID := pickTransactorFromRecord(record)
|
||||||
req := &ports.SignFlowCreateRequest{
|
req := &ports.SignFlowCreateRequest{
|
||||||
Subject: "海宇数据-合作协议-" + record.CompanyName,
|
Subject: "海宇数据-合作协议-" + record.CompanyName,
|
||||||
FileID: cert.ContractFileID,
|
FileID: cert.ContractFileID,
|
||||||
|
PartyAOpenCorpID: s.partyAOpenCorpIDFromStore(ctx, cert.UserID, record.UnifiedSocialCode),
|
||||||
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, // 预创建不取甲方链接,避免消耗单次 EmbedURL
|
SkipActorURL: true, // 预创建不取甲方链接,避免消耗单次 EmbedURL
|
||||||
@@ -1534,14 +1614,6 @@ func (s *CertificationApplicationServiceImpl) createSignTaskAfterEnterpriseVerif
|
|||||||
return nil
|
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 生成并添加合同文件的公共方法
|
// generateAndAddContractFile 生成并添加合同文件的公共方法
|
||||||
func (s *CertificationApplicationServiceImpl) generateAndAddContractFile(
|
func (s *CertificationApplicationServiceImpl) generateAndAddContractFile(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
|
|||||||
151
internal/application/certification/fadada_corp_auth.go
Normal file
151
internal/application/certification/fadada_corp_auth.go
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
package certification
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"hyapi-server/internal/domains/certification/entities"
|
||||||
|
"hyapi-server/internal/domains/certification/ports"
|
||||||
|
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
// resolveCorpAuthQueryForGet 构造 /corp/get 入参(三选一)。
|
||||||
|
// 优先级:库中 openCorpId > corpIdentNo(USCC) > 库中 clientCorpId
|
||||||
|
func (s *CertificationApplicationServiceImpl) resolveCorpAuthQueryForGet(
|
||||||
|
ctx context.Context,
|
||||||
|
userID, uscc, fallbackClientCorpID string,
|
||||||
|
) *ports.CorpAuthStatusQuery {
|
||||||
|
uscc = strings.TrimSpace(uscc)
|
||||||
|
fallbackClientCorpID = strings.TrimSpace(fallbackClientCorpID)
|
||||||
|
rec := s.loadFadadaCorpAuthRecord(ctx, userID, uscc, fallbackClientCorpID)
|
||||||
|
|
||||||
|
q := &ports.CorpAuthStatusQuery{}
|
||||||
|
if rec != nil && strings.TrimSpace(rec.OpenCorpID) != "" {
|
||||||
|
q.OpenCorpID = strings.TrimSpace(rec.OpenCorpID)
|
||||||
|
return q
|
||||||
|
}
|
||||||
|
if uscc != "" {
|
||||||
|
q.CorpIdentNo = uscc
|
||||||
|
return q
|
||||||
|
}
|
||||||
|
if rec != nil && strings.TrimSpace(rec.ClientCorpID) != "" {
|
||||||
|
q.ClientCorpID = strings.TrimSpace(rec.ClientCorpID)
|
||||||
|
return q
|
||||||
|
}
|
||||||
|
if fallbackClientCorpID != "" {
|
||||||
|
q.ClientCorpID = fallbackClientCorpID
|
||||||
|
}
|
||||||
|
return q
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *CertificationApplicationServiceImpl) loadFadadaCorpAuthRecord(
|
||||||
|
ctx context.Context,
|
||||||
|
userID, uscc, clientCorpID string,
|
||||||
|
) *entities.FadadaCorpAuthRecord {
|
||||||
|
if s.fadadaCorpAuthRecordRepo == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if userID != "" {
|
||||||
|
if rec, err := s.fadadaCorpAuthRecordRepo.FindLatestByUserID(ctx, userID); err == nil && rec != nil {
|
||||||
|
return rec
|
||||||
|
} else if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
s.logger.Warn("按用户查询法大大授权记录失败", zap.String("user_id", userID), zap.Error(err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if uscc != "" {
|
||||||
|
if rec, err := s.fadadaCorpAuthRecordRepo.FindByUnifiedSocialCode(ctx, uscc); err == nil && rec != nil {
|
||||||
|
return rec
|
||||||
|
} else if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
s.logger.Warn("按信用代码查询法大大授权记录失败", zap.String("uscc", uscc), zap.Error(err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if clientCorpID != "" {
|
||||||
|
if rec, err := s.fadadaCorpAuthRecordRepo.FindByClientCorpID(ctx, clientCorpID); err == nil && rec != nil {
|
||||||
|
return rec
|
||||||
|
} else if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
s.logger.Warn("按clientCorpId查询法大大授权记录失败", zap.String("client_corp_id", clientCorpID), zap.Error(err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *CertificationApplicationServiceImpl) fadadaAppID() string {
|
||||||
|
if s.config != nil {
|
||||||
|
return strings.TrimSpace(s.config.Fadada.AppID)
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *CertificationApplicationServiceImpl) ensureFadadaCorpAuthSeed(
|
||||||
|
ctx context.Context,
|
||||||
|
cert *entities.Certification,
|
||||||
|
record *entities.EnterpriseInfoSubmitRecord,
|
||||||
|
clientCorpID string,
|
||||||
|
) {
|
||||||
|
if s.fadadaCorpAuthRecordRepo == nil || cert == nil || record == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
seed := entities.NewFadadaCorpAuthRecord(
|
||||||
|
cert.UserID,
|
||||||
|
cert.ID,
|
||||||
|
s.fadadaAppID(),
|
||||||
|
record.CompanyName,
|
||||||
|
record.UnifiedSocialCode,
|
||||||
|
clientCorpID,
|
||||||
|
)
|
||||||
|
seed.Source = "select_sign_platform"
|
||||||
|
if err := s.fadadaCorpAuthRecordRepo.UpsertByBizKey(ctx, seed); err != nil {
|
||||||
|
s.logger.Warn("初始化法大大授权记录失败", zap.Error(err), zap.String("user_id", cert.UserID))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *CertificationApplicationServiceImpl) persistFadadaCorpAuthStatus(
|
||||||
|
ctx context.Context,
|
||||||
|
cert *entities.Certification,
|
||||||
|
record *entities.EnterpriseInfoSubmitRecord,
|
||||||
|
status *ports.CorpAuthStatusResult,
|
||||||
|
source string,
|
||||||
|
) {
|
||||||
|
if s.fadadaCorpAuthRecordRepo == nil || status == nil || !status.Found {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
userID, certID, company, uscc := "", "", "", ""
|
||||||
|
if cert != nil {
|
||||||
|
userID = cert.UserID
|
||||||
|
certID = cert.ID
|
||||||
|
}
|
||||||
|
if record != nil {
|
||||||
|
company = record.CompanyName
|
||||||
|
uscc = record.UnifiedSocialCode
|
||||||
|
if userID == "" {
|
||||||
|
userID = record.UserID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
rec := entities.NewFadadaCorpAuthRecord(userID, certID, s.fadadaAppID(), company, uscc, status.ClientCorpID)
|
||||||
|
rec.ApplyAuthStatus(
|
||||||
|
status.OpenCorpID,
|
||||||
|
status.ClientCorpID,
|
||||||
|
status.BindingStatus,
|
||||||
|
status.IdentStatus,
|
||||||
|
"",
|
||||||
|
nil,
|
||||||
|
source,
|
||||||
|
)
|
||||||
|
if err := s.fadadaCorpAuthRecordRepo.UpsertByBizKey(ctx, rec); err != nil {
|
||||||
|
s.logger.Warn("保存法大大授权状态失败", zap.Error(err), zap.String("user_id", userID), zap.String("source", source))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *CertificationApplicationServiceImpl) partyAOpenCorpIDFromStore(
|
||||||
|
ctx context.Context,
|
||||||
|
userID, uscc string,
|
||||||
|
) string {
|
||||||
|
rec := s.loadFadadaCorpAuthRecord(ctx, userID, uscc, uscc)
|
||||||
|
if rec == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return strings.TrimSpace(rec.OpenCorpID)
|
||||||
|
}
|
||||||
@@ -88,19 +88,23 @@ func (s *CertificationApplicationServiceImpl) SelectSignPlatform(
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
transactorName, transactorMobile, transactorID := pickTransactorFromRecord(record)
|
||||||
|
clientCorpID := strings.TrimSpace(record.UnifiedSocialCode)
|
||||||
authReq := &ports.EnterpriseAuthRequest{
|
authReq := &ports.EnterpriseAuthRequest{
|
||||||
ClientCorpID: record.UnifiedSocialCode,
|
ClientCorpID: clientCorpID,
|
||||||
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,
|
||||||
}
|
}
|
||||||
|
// 选平台时先落库种子数据(clientCorpId/USCC),供后续 /corp/get 与签署复用
|
||||||
|
s.ensureFadadaCorpAuthSeed(ctx, cert, record, clientCorpID)
|
||||||
|
|
||||||
authRes, alreadyVerified, err := s.generateEnterpriseAuthOrDetectVerifiedWithProvider(ctx, provider, authReq)
|
authRes, alreadyVerified, err := s.generateEnterpriseAuthOrDetectVerifiedWithProvider(ctx, provider, authReq, cert, record)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("生成企业认证链接失败: %w", err)
|
return nil, fmt.Errorf("生成企业认证链接失败: %w", err)
|
||||||
}
|
}
|
||||||
@@ -166,7 +170,20 @@ func (s *CertificationApplicationServiceImpl) HandleFadadaCallback(ctx context.C
|
|||||||
zap.String("event", ev.Event),
|
zap.String("event", ev.Event),
|
||||||
zap.Bool("auth_passed", ev.AuthPassed),
|
zap.Bool("auth_passed", ev.AuthPassed),
|
||||||
zap.Bool("sign_completed", ev.SignCompleted),
|
zap.Bool("sign_completed", ev.SignCompleted),
|
||||||
zap.String("sign_flow_id", ev.SignFlowID))
|
zap.String("sign_flow_id", ev.SignFlowID),
|
||||||
|
zap.String("open_corp_id", ev.OpenCorpID),
|
||||||
|
zap.String("client_corp_id", ev.ClientCorpID))
|
||||||
|
|
||||||
|
// 授权成功:落库 openCorpId / clientCorpId
|
||||||
|
if ev.AuthPassed && (strings.TrimSpace(ev.OpenCorpID) != "" || strings.TrimSpace(ev.ClientCorpID) != "") {
|
||||||
|
rec := entities.NewFadadaCorpAuthRecord("", "", s.fadadaAppID(), ev.OrgName, "", ev.ClientCorpID)
|
||||||
|
rec.ApplyAuthStatus(ev.OpenCorpID, ev.ClientCorpID, "authorized", "", "", nil, "callback_auth_passed")
|
||||||
|
if s.fadadaCorpAuthRecordRepo != nil {
|
||||||
|
if err := s.fadadaCorpAuthRecordRepo.UpsertByBizKey(ctx, rec); err != nil {
|
||||||
|
s.logger.Warn("回调落库法大大授权信息失败", zap.Error(err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 认证完成主要依赖 ConfirmAuth / details 轮询;签署完成按任务 ID 兜底推进
|
// 认证完成主要依赖 ConfirmAuth / details 轮询;签署完成按任务 ID 兜底推进
|
||||||
if ev.SignCompleted && ev.SignFlowID != "" {
|
if ev.SignCompleted && ev.SignFlowID != "" {
|
||||||
@@ -186,19 +203,59 @@ func (s *CertificationApplicationServiceImpl) generateEnterpriseAuthOrDetectVeri
|
|||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
provider ports.SignPlatformProvider,
|
provider ports.SignPlatformProvider,
|
||||||
authReq *ports.EnterpriseAuthRequest,
|
authReq *ports.EnterpriseAuthRequest,
|
||||||
|
cert *entities.Certification,
|
||||||
|
record *entities.EnterpriseInfoSubmitRecord,
|
||||||
) (*ports.AuthLinkResult, bool, error) {
|
) (*ports.AuthLinkResult, bool, error) {
|
||||||
|
userID := ""
|
||||||
|
if cert != nil {
|
||||||
|
userID = cert.UserID
|
||||||
|
}
|
||||||
|
// 1) 正式查授权:POST /corp/get(三选一:库 openCorpId > USCC corpIdentNo > 库 clientCorpId)
|
||||||
|
authQuery := s.resolveCorpAuthQueryForGet(ctx, userID, authReq.UnifiedSocialCode, authReq.ClientCorpID)
|
||||||
|
s.logger.Info("查询企业授权状态入参",
|
||||||
|
zap.String("company_name", authReq.CompanyName),
|
||||||
|
zap.String("open_corp_id", authQuery.OpenCorpID),
|
||||||
|
zap.String("corp_ident_no", authQuery.CorpIdentNo),
|
||||||
|
zap.String("client_corp_id", authQuery.ClientCorpID),
|
||||||
|
zap.String("platform", string(provider.Platform())))
|
||||||
|
authStatus, qErr := provider.QueryCorpAuthStatus(ctx, authQuery)
|
||||||
|
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 {
|
||||||
|
s.persistFadadaCorpAuthStatus(ctx, cert, record, authStatus, "corp_get_select_platform")
|
||||||
|
if 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)
|
authRes, err := provider.GenerateEnterpriseAuth(ctx, authReq)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return authRes, false, nil
|
return authRes, false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// 法大大:企业已对本应用授权(210002)= 授权已完成,直接跳过授权页
|
// 3) 兜底:get-auth-url 返回授权范围重复获取(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),
|
||||||
zap.String("uscc", authReq.UnifiedSocialCode),
|
zap.String("uscc", authReq.UnifiedSocialCode),
|
||||||
zap.String("platform", string(provider.Platform())),
|
zap.String("platform", string(provider.Platform())),
|
||||||
zap.Error(err))
|
zap.Error(err))
|
||||||
|
// 再查一次 /corp/get,尽量把 openCorpId 落库
|
||||||
|
if authStatus2, qErr2 := provider.QueryCorpAuthStatus(ctx, authQuery); qErr2 == nil && authStatus2 != nil {
|
||||||
|
s.persistFadadaCorpAuthStatus(ctx, cert, record, authStatus2, "corp_get_after_duplicate_auth")
|
||||||
|
}
|
||||||
return nil, true, nil
|
return nil, true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -614,6 +614,11 @@ func NewContainer() *Container {
|
|||||||
certification_repo.NewGormEnterpriseInfoSubmitRecordRepository,
|
certification_repo.NewGormEnterpriseInfoSubmitRecordRepository,
|
||||||
fx.As(new(domain_certification_repo.EnterpriseInfoSubmitRecordRepository)),
|
fx.As(new(domain_certification_repo.EnterpriseInfoSubmitRecordRepository)),
|
||||||
),
|
),
|
||||||
|
// 法大大企业授权记录仓储
|
||||||
|
fx.Annotate(
|
||||||
|
certification_repo.NewGormFadadaCorpAuthRecordRepository,
|
||||||
|
fx.As(new(domain_certification_repo.FadadaCorpAuthRecordRepository)),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
// 仓储层 - 财务域
|
// 仓储层 - 财务域
|
||||||
@@ -986,6 +991,7 @@ func NewContainer() *Container {
|
|||||||
userAggregateService user_service.UserAggregateService,
|
userAggregateService user_service.UserAggregateService,
|
||||||
queryRepository domain_certification_repo.CertificationQueryRepository,
|
queryRepository domain_certification_repo.CertificationQueryRepository,
|
||||||
enterpriseInfoSubmitRecordRepo domain_certification_repo.EnterpriseInfoSubmitRecordRepository,
|
enterpriseInfoSubmitRecordRepo domain_certification_repo.EnterpriseInfoSubmitRecordRepository,
|
||||||
|
fadadaCorpAuthRecordRepo domain_certification_repo.FadadaCorpAuthRecordRepository,
|
||||||
smsCodeService *user_service.SMSCodeService,
|
smsCodeService *user_service.SMSCodeService,
|
||||||
esignClient *esign.Client,
|
esignClient *esign.Client,
|
||||||
esignConfig *esign.Config,
|
esignConfig *esign.Config,
|
||||||
@@ -1005,6 +1011,7 @@ func NewContainer() *Container {
|
|||||||
userAggregateService,
|
userAggregateService,
|
||||||
queryRepository,
|
queryRepository,
|
||||||
enterpriseInfoSubmitRecordRepo,
|
enterpriseInfoSubmitRecordRepo,
|
||||||
|
fadadaCorpAuthRecordRepo,
|
||||||
smsCodeService,
|
smsCodeService,
|
||||||
esignClient,
|
esignClient,
|
||||||
esignConfig,
|
esignConfig,
|
||||||
|
|||||||
@@ -0,0 +1,89 @@
|
|||||||
|
package entities
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
// FadadaCorpAuthRecord 法大大企业授权相关数据(与 certifications 分离,便于签署链路复用)
|
||||||
|
// open_corp_id:企业对本应用授权后,法大大分配的企业标识
|
||||||
|
type FadadaCorpAuthRecord struct {
|
||||||
|
ID string `json:"id" gorm:"primaryKey;type:varchar(36)"`
|
||||||
|
UserID string `json:"user_id" gorm:"type:varchar(36);not null;index;comment:用户ID"`
|
||||||
|
CertificationID string `json:"certification_id" gorm:"type:varchar(36);index;comment:关联认证记录ID"`
|
||||||
|
|
||||||
|
AppID string `json:"app_id" gorm:"type:varchar(32);index;comment:法大大应用AppId"`
|
||||||
|
CompanyName string `json:"company_name" gorm:"type:varchar(200);comment:企业名称"`
|
||||||
|
UnifiedSocialCode string `json:"unified_social_code" gorm:"column:unified_social_code;type:varchar(50);index;comment:统一社会信用代码(corpIdentNo)"`
|
||||||
|
ClientCorpID string `json:"client_corp_id" gorm:"column:client_corp_id;type:varchar(64);index;comment:应用内企业标识(clientCorpId)"`
|
||||||
|
OpenCorpID string `json:"open_corp_id" gorm:"column:open_corp_id;type:varchar(64);index;comment:法大大企业openCorpId"`
|
||||||
|
|
||||||
|
BindingStatus string `json:"binding_status" gorm:"type:varchar(32);comment:授权绑定状态 unauthorized/authorized"`
|
||||||
|
IdentStatus string `json:"ident_status" gorm:"type:varchar(32);comment:实名状态 unidentified/identified"`
|
||||||
|
AvailableStatus string `json:"available_status" gorm:"type:varchar(32);comment:启用状态 disable/enable"`
|
||||||
|
AuthScopes string `json:"auth_scopes" gorm:"type:text;comment:授权范围JSON数组字符串"`
|
||||||
|
Source string `json:"source" gorm:"type:varchar(64);comment:数据来源"`
|
||||||
|
|
||||||
|
CreatedAt time.Time `json:"created_at" gorm:"not null"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at" gorm:"not null"`
|
||||||
|
DeletedAt gorm.DeletedAt `json:"deleted_at" gorm:"index"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (FadadaCorpAuthRecord) TableName() string {
|
||||||
|
return "fadada_corp_auth_records"
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewFadadaCorpAuthRecord(userID, certificationID, appID, companyName, uscc, clientCorpID string) *FadadaCorpAuthRecord {
|
||||||
|
now := time.Now()
|
||||||
|
return &FadadaCorpAuthRecord{
|
||||||
|
ID: uuid.New().String(),
|
||||||
|
UserID: strings.TrimSpace(userID),
|
||||||
|
CertificationID: strings.TrimSpace(certificationID),
|
||||||
|
AppID: strings.TrimSpace(appID),
|
||||||
|
CompanyName: strings.TrimSpace(companyName),
|
||||||
|
UnifiedSocialCode: strings.TrimSpace(uscc),
|
||||||
|
ClientCorpID: strings.TrimSpace(clientCorpID),
|
||||||
|
Source: "init",
|
||||||
|
CreatedAt: now,
|
||||||
|
UpdatedAt: now,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *FadadaCorpAuthRecord) ApplyAuthStatus(
|
||||||
|
openCorpID, clientCorpID, bindingStatus, identStatus, availableStatus string,
|
||||||
|
authScopes []string,
|
||||||
|
source string,
|
||||||
|
) {
|
||||||
|
if r == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if v := strings.TrimSpace(openCorpID); v != "" {
|
||||||
|
r.OpenCorpID = v
|
||||||
|
}
|
||||||
|
if v := strings.TrimSpace(clientCorpID); v != "" {
|
||||||
|
r.ClientCorpID = v
|
||||||
|
}
|
||||||
|
if v := strings.TrimSpace(bindingStatus); v != "" {
|
||||||
|
r.BindingStatus = v
|
||||||
|
}
|
||||||
|
if v := strings.TrimSpace(identStatus); v != "" {
|
||||||
|
r.IdentStatus = v
|
||||||
|
}
|
||||||
|
if v := strings.TrimSpace(availableStatus); v != "" {
|
||||||
|
r.AvailableStatus = v
|
||||||
|
}
|
||||||
|
if len(authScopes) > 0 {
|
||||||
|
r.AuthScopes = strings.Join(authScopes, ",")
|
||||||
|
}
|
||||||
|
if v := strings.TrimSpace(source); v != "" {
|
||||||
|
r.Source = v
|
||||||
|
}
|
||||||
|
r.UpdatedAt = time.Now()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *FadadaCorpAuthRecord) IsAuthorized() bool {
|
||||||
|
return r != nil && strings.EqualFold(strings.TrimSpace(r.BindingStatus), "authorized") && strings.TrimSpace(r.OpenCorpID) != ""
|
||||||
|
}
|
||||||
@@ -34,6 +34,25 @@ type OrgIdentityQuery struct {
|
|||||||
ClientCorpID string
|
ClientCorpID string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CorpAuthStatusQuery 企业授权状态查询(法大大 /corp/get;e签宝可返回未授权)
|
||||||
|
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 合同模板填单
|
// ContractGenerateRequest 合同模板填单
|
||||||
type ContractGenerateRequest struct {
|
type ContractGenerateRequest struct {
|
||||||
AgreementNo string
|
AgreementNo string
|
||||||
@@ -121,6 +140,8 @@ type CallbackEvent struct {
|
|||||||
SignRejected bool
|
SignRejected bool
|
||||||
SignFlowID string
|
SignFlowID string
|
||||||
AuthFlowID string
|
AuthFlowID string
|
||||||
|
OpenCorpID string
|
||||||
|
ClientCorpID string
|
||||||
OrgName string
|
OrgName string
|
||||||
Raw map[string]interface{}
|
Raw map[string]interface{}
|
||||||
}
|
}
|
||||||
@@ -131,6 +152,8 @@ type SignPlatformProvider interface {
|
|||||||
|
|
||||||
GenerateEnterpriseAuth(ctx context.Context, req *EnterpriseAuthRequest) (*AuthLinkResult, error)
|
GenerateEnterpriseAuth(ctx context.Context, req *EnterpriseAuthRequest) (*AuthLinkResult, error)
|
||||||
QueryOrgVerified(ctx context.Context, req *OrgIdentityQuery) (bool, 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)
|
GenerateContractFile(ctx context.Context, req *ContractGenerateRequest) (*ContractFileResult, error)
|
||||||
CreateSignFlow(ctx context.Context, req *SignFlowCreateRequest) (*SignFlowResult, error)
|
CreateSignFlow(ctx context.Context, req *SignFlowCreateRequest) (*SignFlowResult, error)
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package repositories
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"hyapi-server/internal/domains/certification/entities"
|
||||||
|
)
|
||||||
|
|
||||||
|
// FadadaCorpAuthRecordRepository 法大大企业授权数据仓储
|
||||||
|
type FadadaCorpAuthRecordRepository interface {
|
||||||
|
Create(ctx context.Context, record *entities.FadadaCorpAuthRecord) error
|
||||||
|
Update(ctx context.Context, record *entities.FadadaCorpAuthRecord) error
|
||||||
|
FindByID(ctx context.Context, id string) (*entities.FadadaCorpAuthRecord, error)
|
||||||
|
FindLatestByUserID(ctx context.Context, userID string) (*entities.FadadaCorpAuthRecord, error)
|
||||||
|
FindByCertificationID(ctx context.Context, certificationID string) (*entities.FadadaCorpAuthRecord, error)
|
||||||
|
FindByOpenCorpID(ctx context.Context, openCorpID string) (*entities.FadadaCorpAuthRecord, error)
|
||||||
|
FindByUnifiedSocialCode(ctx context.Context, uscc string) (*entities.FadadaCorpAuthRecord, error)
|
||||||
|
FindByClientCorpID(ctx context.Context, clientCorpID string) (*entities.FadadaCorpAuthRecord, error)
|
||||||
|
// UpsertByBizKey 按认证ID优先,否则按 USCC/clientCorpId 合并写入
|
||||||
|
UpsertByBizKey(ctx context.Context, record *entities.FadadaCorpAuthRecord) error
|
||||||
|
}
|
||||||
@@ -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 完成认证
|
||||||
|
|||||||
@@ -0,0 +1,210 @@
|
|||||||
|
package certification
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"hyapi-server/internal/domains/certification/entities"
|
||||||
|
"hyapi-server/internal/domains/certification/repositories"
|
||||||
|
"hyapi-server/internal/shared/database"
|
||||||
|
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
const FadadaCorpAuthRecordsTable = "fadada_corp_auth_records"
|
||||||
|
|
||||||
|
type GormFadadaCorpAuthRecordRepository struct {
|
||||||
|
*database.CachedBaseRepositoryImpl
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewGormFadadaCorpAuthRecordRepository(db *gorm.DB, logger *zap.Logger) *GormFadadaCorpAuthRecordRepository {
|
||||||
|
return &GormFadadaCorpAuthRecordRepository{
|
||||||
|
CachedBaseRepositoryImpl: database.NewCachedBaseRepositoryImpl(db, logger, FadadaCorpAuthRecordsTable),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *GormFadadaCorpAuthRecordRepository) Create(ctx context.Context, record *entities.FadadaCorpAuthRecord) error {
|
||||||
|
return r.CreateEntity(ctx, record)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *GormFadadaCorpAuthRecordRepository) Update(ctx context.Context, record *entities.FadadaCorpAuthRecord) error {
|
||||||
|
return r.UpdateEntity(ctx, record)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *GormFadadaCorpAuthRecordRepository) FindByID(ctx context.Context, id string) (*entities.FadadaCorpAuthRecord, error) {
|
||||||
|
var record entities.FadadaCorpAuthRecord
|
||||||
|
if err := r.GetDB(ctx).Where("id = ?", id).First(&record).Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &record, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *GormFadadaCorpAuthRecordRepository) FindLatestByUserID(ctx context.Context, userID string) (*entities.FadadaCorpAuthRecord, error) {
|
||||||
|
var record entities.FadadaCorpAuthRecord
|
||||||
|
err := r.GetDB(ctx).
|
||||||
|
Where("user_id = ?", userID).
|
||||||
|
Order("updated_at DESC").
|
||||||
|
First(&record).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &record, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *GormFadadaCorpAuthRecordRepository) FindByCertificationID(ctx context.Context, certificationID string) (*entities.FadadaCorpAuthRecord, error) {
|
||||||
|
if strings.TrimSpace(certificationID) == "" {
|
||||||
|
return nil, gorm.ErrRecordNotFound
|
||||||
|
}
|
||||||
|
var record entities.FadadaCorpAuthRecord
|
||||||
|
err := r.GetDB(ctx).
|
||||||
|
Where("certification_id = ?", certificationID).
|
||||||
|
Order("updated_at DESC").
|
||||||
|
First(&record).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &record, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *GormFadadaCorpAuthRecordRepository) FindByOpenCorpID(ctx context.Context, openCorpID string) (*entities.FadadaCorpAuthRecord, error) {
|
||||||
|
if strings.TrimSpace(openCorpID) == "" {
|
||||||
|
return nil, gorm.ErrRecordNotFound
|
||||||
|
}
|
||||||
|
var record entities.FadadaCorpAuthRecord
|
||||||
|
err := r.GetDB(ctx).
|
||||||
|
Where("open_corp_id = ?", openCorpID).
|
||||||
|
Order("updated_at DESC").
|
||||||
|
First(&record).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &record, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *GormFadadaCorpAuthRecordRepository) FindByUnifiedSocialCode(ctx context.Context, uscc string) (*entities.FadadaCorpAuthRecord, error) {
|
||||||
|
if strings.TrimSpace(uscc) == "" {
|
||||||
|
return nil, gorm.ErrRecordNotFound
|
||||||
|
}
|
||||||
|
var record entities.FadadaCorpAuthRecord
|
||||||
|
err := r.GetDB(ctx).
|
||||||
|
Where("unified_social_code = ?", uscc).
|
||||||
|
Order("updated_at DESC").
|
||||||
|
First(&record).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &record, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *GormFadadaCorpAuthRecordRepository) FindByClientCorpID(ctx context.Context, clientCorpID string) (*entities.FadadaCorpAuthRecord, error) {
|
||||||
|
if strings.TrimSpace(clientCorpID) == "" {
|
||||||
|
return nil, gorm.ErrRecordNotFound
|
||||||
|
}
|
||||||
|
var record entities.FadadaCorpAuthRecord
|
||||||
|
err := r.GetDB(ctx).
|
||||||
|
Where("client_corp_id = ?", clientCorpID).
|
||||||
|
Order("updated_at DESC").
|
||||||
|
First(&record).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &record, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *GormFadadaCorpAuthRecordRepository) UpsertByBizKey(ctx context.Context, record *entities.FadadaCorpAuthRecord) error {
|
||||||
|
if record == nil {
|
||||||
|
return errors.New("法大大授权记录不能为空")
|
||||||
|
}
|
||||||
|
existing, err := r.findExisting(ctx, record)
|
||||||
|
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if existing == nil {
|
||||||
|
return r.Create(ctx, record)
|
||||||
|
}
|
||||||
|
mergeFadadaCorpAuthRecord(existing, record)
|
||||||
|
return r.Update(ctx, existing)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *GormFadadaCorpAuthRecordRepository) findExisting(ctx context.Context, record *entities.FadadaCorpAuthRecord) (*entities.FadadaCorpAuthRecord, error) {
|
||||||
|
if record.CertificationID != "" {
|
||||||
|
if rec, err := r.FindByCertificationID(ctx, record.CertificationID); err == nil {
|
||||||
|
return rec, nil
|
||||||
|
} else if !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if record.OpenCorpID != "" {
|
||||||
|
if rec, err := r.FindByOpenCorpID(ctx, record.OpenCorpID); err == nil {
|
||||||
|
return rec, nil
|
||||||
|
} else if !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if record.UnifiedSocialCode != "" {
|
||||||
|
if rec, err := r.FindByUnifiedSocialCode(ctx, record.UnifiedSocialCode); err == nil {
|
||||||
|
return rec, nil
|
||||||
|
} else if !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if record.ClientCorpID != "" {
|
||||||
|
if rec, err := r.FindByClientCorpID(ctx, record.ClientCorpID); err == nil {
|
||||||
|
return rec, nil
|
||||||
|
} else if !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if record.UserID != "" {
|
||||||
|
if rec, err := r.FindLatestByUserID(ctx, record.UserID); err == nil {
|
||||||
|
return rec, nil
|
||||||
|
} else if !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, gorm.ErrRecordNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
func mergeFadadaCorpAuthRecord(dst, src *entities.FadadaCorpAuthRecord) {
|
||||||
|
if src.UserID != "" {
|
||||||
|
dst.UserID = src.UserID
|
||||||
|
}
|
||||||
|
if src.CertificationID != "" {
|
||||||
|
dst.CertificationID = src.CertificationID
|
||||||
|
}
|
||||||
|
if src.AppID != "" {
|
||||||
|
dst.AppID = src.AppID
|
||||||
|
}
|
||||||
|
if src.CompanyName != "" {
|
||||||
|
dst.CompanyName = src.CompanyName
|
||||||
|
}
|
||||||
|
if src.UnifiedSocialCode != "" {
|
||||||
|
dst.UnifiedSocialCode = src.UnifiedSocialCode
|
||||||
|
}
|
||||||
|
if src.ClientCorpID != "" {
|
||||||
|
dst.ClientCorpID = src.ClientCorpID
|
||||||
|
}
|
||||||
|
if src.OpenCorpID != "" {
|
||||||
|
dst.OpenCorpID = src.OpenCorpID
|
||||||
|
}
|
||||||
|
if src.BindingStatus != "" {
|
||||||
|
dst.BindingStatus = src.BindingStatus
|
||||||
|
}
|
||||||
|
if src.IdentStatus != "" {
|
||||||
|
dst.IdentStatus = src.IdentStatus
|
||||||
|
}
|
||||||
|
if src.AvailableStatus != "" {
|
||||||
|
dst.AvailableStatus = src.AvailableStatus
|
||||||
|
}
|
||||||
|
if src.AuthScopes != "" {
|
||||||
|
dst.AuthScopes = src.AuthScopes
|
||||||
|
}
|
||||||
|
if src.Source != "" {
|
||||||
|
dst.Source = src.Source
|
||||||
|
}
|
||||||
|
dst.UpdatedAt = src.UpdatedAt
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ repositories.FadadaCorpAuthRecordRepository = (*GormFadadaCorpAuthRecordRepository)(nil)
|
||||||
@@ -11,6 +11,7 @@ import (
|
|||||||
|
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
|
"gorm.io/gorm/clause"
|
||||||
|
|
||||||
"hyapi-server/internal/domains/user/entities"
|
"hyapi-server/internal/domains/user/entities"
|
||||||
"hyapi-server/internal/domains/user/repositories"
|
"hyapi-server/internal/domains/user/repositories"
|
||||||
@@ -107,7 +108,48 @@ func (r *GormUserRepository) ExistsByUnifiedSocialCode(ctx context.Context, unif
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *GormUserRepository) Update(ctx context.Context, user entities.User) error {
|
func (r *GormUserRepository) Update(ctx context.Context, user entities.User) error {
|
||||||
return r.UpdateEntity(ctx, &user)
|
db := r.GetDB(ctx)
|
||||||
|
|
||||||
|
return db.Transaction(func(tx *gorm.DB) error {
|
||||||
|
// 避免 GORM 自动保存关联触发 ON CONFLICT(受历史库索引差异影响)
|
||||||
|
if err := tx.WithContext(ctx).Omit(clause.Associations).Save(&user).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 企业信息单独按 user_id 做更新或创建:新用户创建 / 重新认证更新
|
||||||
|
if user.EnterpriseInfo == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
enterpriseInfo := *user.EnterpriseInfo
|
||||||
|
enterpriseInfo.UserID = user.ID
|
||||||
|
enterpriseInfo.User = nil
|
||||||
|
|
||||||
|
var count int64
|
||||||
|
if err := tx.WithContext(ctx).
|
||||||
|
Model(&entities.EnterpriseInfo{}).
|
||||||
|
Where("user_id = ?", user.ID).
|
||||||
|
Count(&count).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if count > 0 {
|
||||||
|
updates := map[string]interface{}{
|
||||||
|
"company_name": enterpriseInfo.CompanyName,
|
||||||
|
"unified_social_code": enterpriseInfo.UnifiedSocialCode,
|
||||||
|
"legal_person_name": enterpriseInfo.LegalPersonName,
|
||||||
|
"legal_person_id": enterpriseInfo.LegalPersonID,
|
||||||
|
"legal_person_phone": enterpriseInfo.LegalPersonPhone,
|
||||||
|
"enterprise_address": enterpriseInfo.EnterpriseAddress,
|
||||||
|
}
|
||||||
|
return tx.WithContext(ctx).
|
||||||
|
Model(&entities.EnterpriseInfo{}).
|
||||||
|
Where("user_id = ?", user.ID).
|
||||||
|
Updates(updates).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
return tx.WithContext(ctx).Create(&enterpriseInfo).Error
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *GormUserRepository) CreateBatch(ctx context.Context, users []entities.User) error {
|
func (r *GormUserRepository) CreateBatch(ctx context.Context, users []entities.User) error {
|
||||||
|
|||||||
@@ -56,6 +56,13 @@ func (p *Provider) QueryOrgVerified(ctx context.Context, req *ports.OrgIdentityQ
|
|||||||
return identity != nil && identity.Data.RealnameStatus == 1, nil
|
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) {
|
func (p *Provider) GenerateContractFile(ctx context.Context, req *ports.ContractGenerateRequest) (*ports.ContractFileResult, error) {
|
||||||
_ = ctx
|
_ = ctx
|
||||||
components := map[string]string{
|
components := map[string]string{
|
||||||
|
|||||||
@@ -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),
|
// GenerateContractFile 法大大走签署任务模板(/sign-task/create-with-template),
|
||||||
// 此处返回空结果,实际填单由 CreateSignFlow 通过 /sign-task/field/fill-values 完成。
|
// 此处返回空结果,实际填单由 CreateSignFlow 通过 /sign-task/field/fill-values 完成。
|
||||||
func (p *Provider) GenerateContractFile(ctx context.Context, req *ports.ContractGenerateRequest) (*ports.ContractFileResult, error) {
|
func (p *Provider) GenerateContractFile(ctx context.Context, req *ports.ContractGenerateRequest) (*ports.ContractFileResult, error) {
|
||||||
@@ -199,10 +227,12 @@ func (p *Provider) ParseCallback(ctx context.Context, headers map[string]string,
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
out := &ports.CallbackEvent{
|
out := &ports.CallbackEvent{
|
||||||
Event: ev.Event,
|
Event: ev.Event,
|
||||||
SignFlowID: ev.SignTaskID,
|
SignFlowID: ev.SignTaskID,
|
||||||
AuthFlowID: firstNonEmpty(ev.ClientCorpID, ev.OpenCorpID),
|
AuthFlowID: firstNonEmpty(ev.ClientCorpID, ev.OpenCorpID),
|
||||||
Raw: ev.Raw,
|
OpenCorpID: ev.OpenCorpID,
|
||||||
|
ClientCorpID: ev.ClientCorpID,
|
||||||
|
Raw: ev.Raw,
|
||||||
}
|
}
|
||||||
if ev.IsSignCompletedCallback() {
|
if ev.IsSignCompletedCallback() {
|
||||||
out.SignCompleted = true
|
out.SignCompleted = true
|
||||||
|
|||||||
@@ -49,6 +49,25 @@ type getCorpAuthURLData struct {
|
|||||||
AuthURL string `json:"authUrl"`
|
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 ----
|
// ---- /corp/get-identified-status ----
|
||||||
|
|
||||||
type getIdentifiedStatusReq struct {
|
type getIdentifiedStatusReq struct {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import "sync"
|
|||||||
const (
|
const (
|
||||||
pathGetAccessToken = "/service/get-access-token"
|
pathGetAccessToken = "/service/get-access-token"
|
||||||
pathGetCorpAuthURL = "/corp/get-auth-url"
|
pathGetCorpAuthURL = "/corp/get-auth-url"
|
||||||
|
pathGetCorp = "/corp/get"
|
||||||
pathGetCorpIdentifiedStatus = "/corp/get-identified-status"
|
pathGetCorpIdentifiedStatus = "/corp/get-identified-status"
|
||||||
pathFillDocTemplateValues = "/doc-template/fill-values"
|
pathFillDocTemplateValues = "/doc-template/fill-values"
|
||||||
pathCreateSignTaskTemplate = "/sign-task/create-with-template"
|
pathCreateSignTaskTemplate = "/sign-task/create-with-template"
|
||||||
|
|||||||
@@ -74,7 +74,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
|
||||||
@@ -92,6 +92,83 @@ func (c *Client) GenerateEnterpriseAuth(req *EnterpriseAuthRequest) (*Enterprise
|
|||||||
}, nil
|
}, 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)
|
// QueryOrgVerified 查询企业是否已在法大大完成实名(POST /corp/get-identified-status)
|
||||||
func (c *Client) QueryOrgVerified(req *QueryOrgIdentityRequest) (bool, error) {
|
func (c *Client) QueryOrgVerified(req *QueryOrgIdentityRequest) (bool, error) {
|
||||||
result, err := c.QueryOrgIdentity(req)
|
result, err := c.QueryOrgIdentity(req)
|
||||||
@@ -125,7 +202,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
|
||||||
|
|||||||
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"`
|
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 入参)
|
// ContractFillRequest 合作协议模板填单请求(对齐 e签宝 generateAndAddContractFile 入参)
|
||||||
type ContractFillRequest struct {
|
type ContractFillRequest struct {
|
||||||
AgreementNo string `json:"agreementNo"` // 协议编号
|
AgreementNo string `json:"agreementNo"` // 协议编号
|
||||||
|
|||||||
Reference in New Issue
Block a user