Merge branch 'main' of http://1.117.67.95:3000/team/hyapi-server
This commit is contained in:
@@ -0,0 +1,51 @@
|
||||
package certification
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestIsEnterpriseAlreadyAuthorizedErr(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
err error
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "fadada 210002",
|
||||
err: errors.New("获取法大大企业认证链接失败: code=210002 msg=该企业已授权,无需重复操作 requestId=abc"),
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "exist authorize wording",
|
||||
err: errors.New("当前应用已存在授权"),
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "other error",
|
||||
err: errors.New("获取法大大企业认证链接失败: code=100011 msg=参数错误"),
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "nil",
|
||||
err: nil,
|
||||
want: false,
|
||||
},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
if got := isEnterpriseAlreadyAuthorizedErr(tc.err); got != tc.want {
|
||||
t.Fatalf("got %v want %v", got, tc.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsEnterpriseAlreadyRealnamedErr(t *testing.T) {
|
||||
if !isEnterpriseAlreadyRealnamedErr(errors.New("企业用户已实名")) {
|
||||
t.Fatal("expected true for 已实名")
|
||||
}
|
||||
if isEnterpriseAlreadyRealnamedErr(errors.New("该企业已授权,无需重复操作")) {
|
||||
t.Fatal("已授权 should not match 已实名 helper")
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,14 @@ type CertificationApplicationService interface {
|
||||
ConfirmSign(ctx context.Context, cmd *queries.ConfirmSignCommand) (*responses.ConfirmSignResponse, error)
|
||||
// 申请合同签署
|
||||
ApplyContract(ctx context.Context, cmd *commands.ApplyContractCommand) (*responses.ContractSignUrlResponse, error)
|
||||
// RefreshContractSignURL 重新获取签署 iframe 长链(法大大 EmbedURL 约 10 分钟/单次有效)
|
||||
RefreshContractSignURL(ctx context.Context, userID string) (*responses.ContractSignUrlResponse, error)
|
||||
// RefreshContractPreviewURL 获取签署任务预览链接(法大大 get-preview-url)
|
||||
RefreshContractPreviewURL(ctx context.Context, userID string) (*responses.ContractPreviewUrlResponse, error)
|
||||
// ListSignPlatforms 可选签署平台列表
|
||||
ListSignPlatforms(ctx context.Context) (*responses.SignPlatformsResponse, error)
|
||||
// SelectSignPlatform 选择签署平台并生成企业认证链接
|
||||
SelectSignPlatform(ctx context.Context, cmd *commands.SelectSignPlatformCommand) (*responses.CertificationResponse, error)
|
||||
|
||||
// OCR营业执照识别
|
||||
RecognizeBusinessLicense(ctx context.Context, imageBytes []byte) (*responses.BusinessLicenseResult, error)
|
||||
@@ -48,8 +56,10 @@ type CertificationApplicationService interface {
|
||||
// AdminTransitionCertificationStatus 管理端按用户变更认证状态(以状态机为准:info_submitted=通过 / info_rejected=拒绝)
|
||||
AdminTransitionCertificationStatus(ctx context.Context, cmd *commands.AdminTransitionCertificationStatusCommand) error
|
||||
|
||||
// ================ e签宝回调处理 ================
|
||||
// ================ 第三方回调处理 ================
|
||||
|
||||
// 处理e签宝回调
|
||||
HandleEsignCallback(ctx context.Context, cmd *commands.EsignCallbackCommand) error
|
||||
// 处理法大大回调
|
||||
HandleFadadaCallback(ctx context.Context, headers map[string]string, body []byte) error
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ import (
|
||||
"hyapi-server/internal/domains/certification/entities"
|
||||
certification_value_objects "hyapi-server/internal/domains/certification/entities/value_objects"
|
||||
"hyapi-server/internal/domains/certification/enums"
|
||||
"hyapi-server/internal/domains/certification/ports"
|
||||
"hyapi-server/internal/domains/certification/repositories"
|
||||
"hyapi-server/internal/domains/certification/services"
|
||||
finance_service "hyapi-server/internal/domains/finance/services"
|
||||
@@ -40,6 +41,7 @@ type CertificationApplicationServiceImpl struct {
|
||||
smsCodeService *user_service.SMSCodeService
|
||||
esignClient *esign.Client
|
||||
esignConfig *esign.Config
|
||||
platformRegistry ports.SignPlatformRegistry
|
||||
qiniuStorageService *storage.QiNiuStorageService
|
||||
contractAggregateService user_service.ContractAggregateService
|
||||
walletAggregateService finance_service.WalletAggregateService
|
||||
@@ -65,6 +67,7 @@ func NewCertificationApplicationService(
|
||||
smsCodeService *user_service.SMSCodeService,
|
||||
esignClient *esign.Client,
|
||||
esignConfig *esign.Config,
|
||||
platformRegistry ports.SignPlatformRegistry,
|
||||
qiniuStorageService *storage.QiNiuStorageService,
|
||||
contractAggregateService user_service.ContractAggregateService,
|
||||
walletAggregateService finance_service.WalletAggregateService,
|
||||
@@ -87,6 +90,7 @@ func NewCertificationApplicationService(
|
||||
smsCodeService: smsCodeService,
|
||||
esignClient: esignClient,
|
||||
esignConfig: esignConfig,
|
||||
platformRegistry: platformRegistry,
|
||||
qiniuStorageService: qiniuStorageService,
|
||||
contractAggregateService: contractAggregateService,
|
||||
walletAggregateService: walletAggregateService,
|
||||
@@ -443,16 +447,22 @@ func (s *CertificationApplicationServiceImpl) ConfirmAuth(
|
||||
zap.String("record_id", record.ID))
|
||||
s.logger.Info("确认状态-步骤3-开始查询三方实名状态",
|
||||
zap.String("user_id", cmd.UserID),
|
||||
zap.String("company_name", record.CompanyName))
|
||||
identity, err := s.esignClient.QueryOrgIdentityInfo(&esign.QueryOrgIdentityRequest{
|
||||
OrgName: record.CompanyName,
|
||||
zap.String("company_name", record.CompanyName),
|
||||
zap.String("sign_platform", string(cert.ResolvedSignPlatform())))
|
||||
provider, err := s.resolveProvider(cert)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
verified, err := provider.QueryOrgVerified(ctx, &ports.OrgIdentityQuery{
|
||||
OrgName: record.CompanyName,
|
||||
OrgIdentNo: record.UnifiedSocialCode,
|
||||
})
|
||||
if err != nil {
|
||||
s.logger.Error("查询企业认证信息失败", zap.Error(err))
|
||||
return nil, fmt.Errorf("查询企业认证信息失败: %w", err)
|
||||
}
|
||||
reason := ""
|
||||
if identity != nil && identity.Data.RealnameStatus == 1 {
|
||||
if verified {
|
||||
s.logger.Info("确认状态-步骤3-三方实名状态已完成,准备事务内推进认证",
|
||||
zap.String("user_id", cmd.UserID))
|
||||
err = s.txManager.ExecuteInTx(ctx, func(txCtx context.Context) error {
|
||||
@@ -501,7 +511,7 @@ func (s *CertificationApplicationServiceImpl) ConfirmSign(
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ApplyContract 申请合同签署
|
||||
// ApplyContract 立即签署:若认证完成时已创建任务则只取 iframe 长链并推进状态;否则创建任务
|
||||
func (s *CertificationApplicationServiceImpl) ApplyContract(
|
||||
ctx context.Context,
|
||||
cmd *commands.ApplyContractCommand,
|
||||
@@ -509,59 +519,179 @@ func (s *CertificationApplicationServiceImpl) ApplyContract(
|
||||
s.logger.Info("开始申请合同签署",
|
||||
zap.String("user_id", cmd.UserID))
|
||||
|
||||
// 1. 验证命令完整性
|
||||
if err := s.validateApplyContractCommand(cmd); err != nil {
|
||||
return nil, fmt.Errorf("命令验证失败: %s", err.Error())
|
||||
}
|
||||
|
||||
// 2. 加载认证聚合根
|
||||
cert, err := s.aggregateService.LoadCertificationByUserID(ctx, cmd.UserID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("加载认证信息失败: %s", err.Error())
|
||||
}
|
||||
|
||||
// 3. 验证业务前置条件
|
||||
if err := s.validateContractApplicationPreconditions(cert, cmd.UserID); err != nil {
|
||||
return nil, fmt.Errorf("业务前置条件验证失败: %s", err.Error())
|
||||
}
|
||||
|
||||
// 5. 生成合同和签署链接
|
||||
enterpriseInfo, err := s.userAggregateService.GetUserWithEnterpriseInfo(ctx, cmd.UserID)
|
||||
if err != nil {
|
||||
s.logger.Error("获取企业信息失败", zap.Error(err))
|
||||
return nil, fmt.Errorf("获取企业信息失败: %w", err)
|
||||
}
|
||||
contractInfo, err := s.generateContractAndSignURL(ctx, cert, enterpriseInfo.EnterpriseInfo)
|
||||
if err != nil {
|
||||
s.logger.Error("生成合同失败", zap.Error(err))
|
||||
return nil, fmt.Errorf("生成合同失败: %s", err.Error())
|
||||
|
||||
var embedURL, shortURL string
|
||||
flowID := strings.TrimSpace(cert.EsignFlowID)
|
||||
|
||||
if flowID != "" {
|
||||
// 任务已在企业认证完成后创建(乙方通常已免签),此处只刷新甲方 iframe 长链
|
||||
provider, err := s.resolveProvider(cert)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ei := enterpriseInfo.EnterpriseInfo
|
||||
urlRes, err := provider.GetActorSignURL(ctx, &ports.GetActorSignURLRequest{
|
||||
SignFlowID: flowID,
|
||||
TransactorMobile: ei.LegalPersonPhone,
|
||||
PartyAName: ei.CompanyName,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("获取签署链接失败: %s", err.Error())
|
||||
}
|
||||
embedURL = firstNonEmptyStr(urlRes.EmbedURL, urlRes.ShortURL)
|
||||
shortURL = strings.TrimSpace(urlRes.ShortURL)
|
||||
} else {
|
||||
contractInfo, err := s.generateContractAndSignURL(ctx, cert, enterpriseInfo.EnterpriseInfo)
|
||||
if err != nil {
|
||||
s.logger.Error("生成合同失败", zap.Error(err))
|
||||
return nil, fmt.Errorf("生成合同失败: %s", err.Error())
|
||||
}
|
||||
flowID = contractInfo.EsignFlowID
|
||||
embedURL = contractInfo.ContractSignURL
|
||||
shortURL = contractInfo.ContractSignShortURL
|
||||
}
|
||||
err = cert.ApplyContract(contractInfo.EsignFlowID, contractInfo.ContractSignURL)
|
||||
if embedURL == "" {
|
||||
return nil, fmt.Errorf("签署链接为空")
|
||||
}
|
||||
|
||||
storeURL := firstNonEmptyStr(shortURL, cert.ContractSignURL, embedURL)
|
||||
err = cert.ApplyContract(flowID, storeURL)
|
||||
if err != nil {
|
||||
s.logger.Error("合同申请状态转换失败", zap.Error(err))
|
||||
return nil, fmt.Errorf("合同申请失败: %s", err.Error())
|
||||
}
|
||||
|
||||
// 7. 保存认证信息
|
||||
err = s.aggregateService.SaveCertification(ctx, cert)
|
||||
if err != nil {
|
||||
s.logger.Error("保存认证信息失败", zap.Error(err))
|
||||
return nil, fmt.Errorf("保存认证信息失败: %s", err.Error())
|
||||
}
|
||||
|
||||
// 8. 构建响应
|
||||
response := responses.NewContractSignUrlResponse(
|
||||
cert.ID,
|
||||
contractInfo.ContractSignURL,
|
||||
contractInfo.ContractURL,
|
||||
embedURL,
|
||||
shortURL,
|
||||
cert.ContractURL,
|
||||
"请在规定时间内完成合同签署",
|
||||
"合同申请成功",
|
||||
)
|
||||
|
||||
s.logger.Info("合同申请成功", zap.String("user_id", cmd.UserID))
|
||||
s.logger.Info("合同申请成功", zap.String("user_id", cmd.UserID), zap.String("esign_flow_id", flowID))
|
||||
return response, nil
|
||||
}
|
||||
|
||||
// RefreshContractSignURL 进入签署页时刷新 iframe 长链
|
||||
func (s *CertificationApplicationServiceImpl) RefreshContractSignURL(
|
||||
ctx context.Context,
|
||||
userID string,
|
||||
) (*responses.ContractSignUrlResponse, error) {
|
||||
if strings.TrimSpace(userID) == "" {
|
||||
return nil, fmt.Errorf("用户ID不能为空")
|
||||
}
|
||||
cert, err := s.aggregateService.LoadCertificationByUserID(ctx, userID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("加载认证信息失败: %s", err.Error())
|
||||
}
|
||||
if cert.Status != enums.StatusContractApplied && cert.Status != enums.StatusEnterpriseVerified {
|
||||
return nil, fmt.Errorf("当前状态不允许获取签署链接")
|
||||
}
|
||||
if strings.TrimSpace(cert.EsignFlowID) == "" {
|
||||
return nil, fmt.Errorf("签署任务尚未创建")
|
||||
}
|
||||
|
||||
provider, err := s.resolveProvider(cert)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
enterpriseInfo, err := s.userAggregateService.GetUserWithEnterpriseInfo(ctx, userID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("获取企业信息失败: %w", err)
|
||||
}
|
||||
ei := enterpriseInfo.EnterpriseInfo
|
||||
urlRes, err := provider.GetActorSignURL(ctx, &ports.GetActorSignURLRequest{
|
||||
SignFlowID: cert.EsignFlowID,
|
||||
TransactorMobile: ei.LegalPersonPhone,
|
||||
PartyAName: ei.CompanyName,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("刷新签署链接失败: %s", err.Error())
|
||||
}
|
||||
embedURL := firstNonEmptyStr(urlRes.EmbedURL, urlRes.ShortURL)
|
||||
if embedURL == "" {
|
||||
return nil, fmt.Errorf("签署链接为空")
|
||||
}
|
||||
// 短链可回写库(稳定);长链不落库(易过期且可能超长)
|
||||
if short := strings.TrimSpace(urlRes.ShortURL); short != "" && short != cert.ContractSignURL {
|
||||
cert.ContractSignURL = short
|
||||
_ = s.aggregateService.SaveCertification(ctx, cert)
|
||||
}
|
||||
|
||||
return responses.NewContractSignUrlResponse(
|
||||
cert.ID,
|
||||
embedURL,
|
||||
urlRes.ShortURL,
|
||||
cert.ContractURL,
|
||||
"请在规定时间内完成合同签署",
|
||||
"签署链接已刷新",
|
||||
), nil
|
||||
}
|
||||
|
||||
// RefreshContractPreviewURL 合同预览页:调用法大大 get-preview-url(非 PDF 直链)
|
||||
func (s *CertificationApplicationServiceImpl) RefreshContractPreviewURL(
|
||||
ctx context.Context,
|
||||
userID string,
|
||||
) (*responses.ContractPreviewUrlResponse, error) {
|
||||
if strings.TrimSpace(userID) == "" {
|
||||
return nil, fmt.Errorf("用户ID不能为空")
|
||||
}
|
||||
cert, err := s.aggregateService.LoadCertificationByUserID(ctx, userID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("加载认证信息失败: %s", err.Error())
|
||||
}
|
||||
if cert.Status != enums.StatusEnterpriseVerified && cert.Status != enums.StatusContractApplied {
|
||||
return nil, fmt.Errorf("当前状态不允许预览合同")
|
||||
}
|
||||
if strings.TrimSpace(cert.EsignFlowID) == "" {
|
||||
return nil, fmt.Errorf("签署任务尚未创建,无法预览")
|
||||
}
|
||||
provider, err := s.resolveProvider(cert)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
preview, err := provider.GetSignTaskPreviewURL(ctx, cert.EsignFlowID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("获取预览链接失败: %s", err.Error())
|
||||
}
|
||||
mode := "iframe"
|
||||
if cert.ResolvedSignPlatform() == enums.SignPlatformEsign {
|
||||
mode = "pdf"
|
||||
}
|
||||
return &responses.ContractPreviewUrlResponse{
|
||||
CertificationID: cert.ID,
|
||||
PreviewURL: preview.PreviewURL,
|
||||
PreviewMode: mode,
|
||||
SignFlowID: cert.EsignFlowID,
|
||||
Message: "预览链接已刷新",
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ================ 查询用例 ================
|
||||
|
||||
// GetCertification 获取认证详情
|
||||
@@ -603,10 +733,36 @@ func (s *CertificationApplicationServiceImpl) GetCertification(
|
||||
}
|
||||
}
|
||||
|
||||
// 脏数据修复:曾因预创建签署软失败推进到 enterprise_verified 但无任务,回退到待选平台
|
||||
if cert.Status == enums.StatusEnterpriseVerified && strings.TrimSpace(cert.EsignFlowID) == "" {
|
||||
if err := cert.ResetAfterSignTaskCreateFailed(); err != nil {
|
||||
s.logger.Warn("回退未创建签署任务的脏状态失败", zap.String("user_id", query.UserID), zap.Error(err))
|
||||
} else if saveErr := s.aggregateService.SaveCertification(ctx, cert); saveErr != nil {
|
||||
s.logger.Warn("保存回退状态失败", zap.String("user_id", query.UserID), zap.Error(saveErr))
|
||||
} else {
|
||||
s.logger.Info("已回退未创建签署任务的脏状态到待选平台", zap.String("user_id", query.UserID))
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
@@ -873,8 +1029,10 @@ func (s *CertificationApplicationServiceImpl) AdminListSubmitRecords(
|
||||
items := make([]*responses.AdminSubmitRecordItem, 0, len(result.Records))
|
||||
for _, r := range result.Records {
|
||||
certStatus := ""
|
||||
signPlatform := ""
|
||||
if cert, err := s.aggregateService.LoadCertificationByUserID(ctx, r.UserID); err == nil && cert != nil {
|
||||
certStatus = string(cert.Status)
|
||||
signPlatform = string(cert.SignPlatform)
|
||||
}
|
||||
items = append(items, &responses.AdminSubmitRecordItem{
|
||||
ID: r.ID,
|
||||
@@ -884,6 +1042,8 @@ func (s *CertificationApplicationServiceImpl) AdminListSubmitRecords(
|
||||
LegalPersonName: r.LegalPersonName,
|
||||
SubmitAt: r.SubmitAt,
|
||||
Status: r.Status,
|
||||
ManualReviewStatus: r.ManualReviewStatus,
|
||||
SignPlatform: signPlatform,
|
||||
CertificationStatus: certStatus,
|
||||
})
|
||||
}
|
||||
@@ -907,8 +1067,10 @@ func (s *CertificationApplicationServiceImpl) AdminGetSubmitRecordByID(ctx conte
|
||||
return nil, fmt.Errorf("获取提交记录失败: %w", err)
|
||||
}
|
||||
certStatus := ""
|
||||
signPlatform := ""
|
||||
if cert, loadErr := s.aggregateService.LoadCertificationByUserID(ctx, record.UserID); loadErr == nil && cert != nil {
|
||||
certStatus = string(cert.Status)
|
||||
signPlatform = string(cert.SignPlatform)
|
||||
}
|
||||
return &responses.AdminSubmitRecordDetail{
|
||||
ID: record.ID,
|
||||
@@ -932,6 +1094,9 @@ func (s *CertificationApplicationServiceImpl) AdminGetSubmitRecordByID(ctx conte
|
||||
VerifiedAt: record.VerifiedAt,
|
||||
FailedAt: record.FailedAt,
|
||||
FailureReason: record.FailureReason,
|
||||
ManualReviewStatus: record.ManualReviewStatus,
|
||||
ManualReviewRemark: record.ManualReviewRemark,
|
||||
SignPlatform: signPlatform,
|
||||
CertificationStatus: certStatus,
|
||||
CreatedAt: record.CreatedAt,
|
||||
UpdatedAt: record.UpdatedAt,
|
||||
@@ -967,52 +1132,19 @@ func (s *CertificationApplicationServiceImpl) AdminApproveSubmitRecord(ctx conte
|
||||
if cert.Status != enums.StatusInfoPendingReview {
|
||||
return fmt.Errorf("认证状态不是待审核,当前: %s", enums.GetStatusName(cert.Status))
|
||||
}
|
||||
enterpriseInfo := &certification_value_objects.EnterpriseInfo{
|
||||
CompanyName: record.CompanyName,
|
||||
UnifiedSocialCode: record.UnifiedSocialCode,
|
||||
LegalPersonName: record.LegalPersonName,
|
||||
LegalPersonID: record.LegalPersonID,
|
||||
LegalPersonPhone: record.LegalPersonPhone,
|
||||
EnterpriseAddress: record.EnterpriseAddress,
|
||||
}
|
||||
authReq := &esign.EnterpriseAuthRequest{
|
||||
CompanyName: enterpriseInfo.CompanyName,
|
||||
UnifiedSocialCode: enterpriseInfo.UnifiedSocialCode,
|
||||
LegalPersonName: enterpriseInfo.LegalPersonName,
|
||||
LegalPersonID: enterpriseInfo.LegalPersonID,
|
||||
TransactorName: enterpriseInfo.LegalPersonName,
|
||||
TransactorMobile: enterpriseInfo.LegalPersonPhone,
|
||||
TransactorID: enterpriseInfo.LegalPersonID,
|
||||
}
|
||||
authURL, alreadyVerified, err := s.generateEnterpriseAuthOrDetectVerified(ctx, authReq)
|
||||
if err != nil {
|
||||
return fmt.Errorf("生成企业认证链接失败: %w", err)
|
||||
}
|
||||
if alreadyVerified {
|
||||
if err := cert.ApproveEnterpriseInfoReview("", "", adminID); err != nil {
|
||||
return fmt.Errorf("更新认证状态失败: %w", err)
|
||||
}
|
||||
if err := s.completeEnterpriseVerification(ctx, cert, cert.UserID, record.CompanyName, record.LegalPersonName); err != nil {
|
||||
return err
|
||||
}
|
||||
record.MarkManualApproved(adminID, remark)
|
||||
if err := s.enterpriseInfoSubmitRecordService.Save(ctx, record); err != nil {
|
||||
return fmt.Errorf("保存企业信息提交记录失败: %w", err)
|
||||
}
|
||||
s.logger.Info("管理员审核通过企业信息", zap.String("record_id", recordID), zap.String("admin_id", adminID))
|
||||
// 幂等:已审核通过、等待用户选平台
|
||||
if record.ManualReviewStatus == "approved" {
|
||||
return nil
|
||||
}
|
||||
if err := cert.ApproveEnterpriseInfoReview(authURL.AuthShortURL, authURL.AuthFlowID, adminID); err != nil {
|
||||
return fmt.Errorf("更新认证状态失败: %w", err)
|
||||
}
|
||||
if err := s.aggregateService.SaveCertification(ctx, cert); err != nil {
|
||||
return fmt.Errorf("保存认证信息失败: %w", err)
|
||||
}
|
||||
// 审核通过后不立即生成认证链接,由用户选择签署平台(e签宝/法大大)后再生成
|
||||
record.MarkManualApproved(adminID, remark)
|
||||
if err := s.enterpriseInfoSubmitRecordService.Save(ctx, record); err != nil {
|
||||
return fmt.Errorf("保存企业信息提交记录失败: %w", err)
|
||||
}
|
||||
s.logger.Info("管理员审核通过企业信息", zap.String("record_id", recordID), zap.String("admin_id", adminID))
|
||||
s.logger.Info("管理员审核通过企业信息,等待用户选择签署平台",
|
||||
zap.String("record_id", recordID),
|
||||
zap.String("admin_id", adminID),
|
||||
zap.String("user_id", record.UserID))
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1076,7 +1208,7 @@ func (s *CertificationApplicationServiceImpl) AdminTransitionCertificationStatus
|
||||
}
|
||||
switch cmd.TargetStatus {
|
||||
case string(enums.StatusInfoSubmitted):
|
||||
// 审核通过:与 AdminApproveSubmitRecord 一致,推状态并生成企业认证链接
|
||||
// 审核通过:与 AdminApproveSubmitRecord 一致,仅标记审核通过,等待用户选择签署平台
|
||||
switch cert.Status {
|
||||
case enums.StatusInfoSubmitted, enums.StatusEnterpriseVerified, enums.StatusContractApplied,
|
||||
enums.StatusContractSigned, enums.StatusCompleted, enums.StatusContractRejected, enums.StatusContractExpired:
|
||||
@@ -1085,45 +1217,13 @@ func (s *CertificationApplicationServiceImpl) AdminTransitionCertificationStatus
|
||||
if cert.Status != enums.StatusInfoPendingReview {
|
||||
return fmt.Errorf("认证状态不是待审核,当前: %s", enums.GetStatusName(cert.Status))
|
||||
}
|
||||
enterpriseInfo := &certification_value_objects.EnterpriseInfo{
|
||||
CompanyName: record.CompanyName, UnifiedSocialCode: record.UnifiedSocialCode,
|
||||
LegalPersonName: record.LegalPersonName, LegalPersonID: record.LegalPersonID,
|
||||
LegalPersonPhone: record.LegalPersonPhone, EnterpriseAddress: record.EnterpriseAddress,
|
||||
}
|
||||
authReq := &esign.EnterpriseAuthRequest{
|
||||
CompanyName: enterpriseInfo.CompanyName, UnifiedSocialCode: enterpriseInfo.UnifiedSocialCode,
|
||||
LegalPersonName: enterpriseInfo.LegalPersonName, LegalPersonID: enterpriseInfo.LegalPersonID,
|
||||
TransactorName: enterpriseInfo.LegalPersonName, TransactorMobile: enterpriseInfo.LegalPersonPhone, TransactorID: enterpriseInfo.LegalPersonID,
|
||||
}
|
||||
authURL, alreadyVerified, err := s.generateEnterpriseAuthOrDetectVerified(ctx, authReq)
|
||||
if err != nil {
|
||||
return fmt.Errorf("生成企业认证链接失败: %w", err)
|
||||
}
|
||||
if alreadyVerified {
|
||||
if err := cert.ApproveEnterpriseInfoReview("", "", cmd.AdminID); err != nil {
|
||||
return fmt.Errorf("更新认证状态失败: %w", err)
|
||||
}
|
||||
if err := s.completeEnterpriseVerification(ctx, cert, cert.UserID, record.CompanyName, record.LegalPersonName); err != nil {
|
||||
return err
|
||||
}
|
||||
record.MarkManualApproved(cmd.AdminID, cmd.Remark)
|
||||
if err := s.enterpriseInfoSubmitRecordService.Save(ctx, record); err != nil {
|
||||
return fmt.Errorf("保存企业信息提交记录失败: %w", err)
|
||||
}
|
||||
s.logger.Info("管理端变更认证状态为通过", zap.String("user_id", cmd.UserID), zap.String("admin_id", cmd.AdminID))
|
||||
return nil
|
||||
}
|
||||
if err := cert.ApproveEnterpriseInfoReview(authURL.AuthShortURL, authURL.AuthFlowID, cmd.AdminID); err != nil {
|
||||
return fmt.Errorf("更新认证状态失败: %w", err)
|
||||
}
|
||||
if err := s.aggregateService.SaveCertification(ctx, cert); err != nil {
|
||||
return fmt.Errorf("保存认证信息失败: %w", err)
|
||||
}
|
||||
record.MarkManualApproved(cmd.AdminID, cmd.Remark)
|
||||
if err := s.enterpriseInfoSubmitRecordService.Save(ctx, record); err != nil {
|
||||
return fmt.Errorf("保存企业信息提交记录失败: %w", err)
|
||||
}
|
||||
s.logger.Info("管理端变更认证状态为通过", zap.String("user_id", cmd.UserID), zap.String("admin_id", cmd.AdminID))
|
||||
s.logger.Info("管理端审核通过,等待用户选择签署平台",
|
||||
zap.String("user_id", cmd.UserID),
|
||||
zap.String("admin_id", cmd.AdminID))
|
||||
return nil
|
||||
case string(enums.StatusInfoRejected):
|
||||
// 审核拒绝
|
||||
@@ -1160,6 +1260,7 @@ func (s *CertificationApplicationServiceImpl) convertToResponse(cert *entities.C
|
||||
UserID: cert.UserID,
|
||||
Status: cert.Status,
|
||||
StatusName: enums.GetStatusName(cert.Status),
|
||||
SignPlatform: string(cert.SignPlatform),
|
||||
Progress: cert.GetProgress(),
|
||||
CreatedAt: cert.CreatedAt,
|
||||
UpdatedAt: cert.UpdatedAt,
|
||||
@@ -1198,58 +1299,6 @@ func (s *CertificationApplicationServiceImpl) convertToResponse(cert *entities.C
|
||||
return response
|
||||
}
|
||||
|
||||
func (s *CertificationApplicationServiceImpl) generateEnterpriseAuthOrDetectVerified(
|
||||
ctx context.Context,
|
||||
req *esign.EnterpriseAuthRequest,
|
||||
) (*esign.EnterpriseAuthResult, bool, error) {
|
||||
s.logger.Info("企业认证链接生成-步骤1-开始调用三方创建认证链接",
|
||||
zap.String("company_name", req.CompanyName),
|
||||
zap.String("unified_social_code", req.UnifiedSocialCode))
|
||||
authURL, err := s.esignClient.GenerateEnterpriseAuth(req)
|
||||
if err == nil {
|
||||
s.logger.Info("企业认证链接生成-步骤1-创建成功",
|
||||
zap.String("company_name", req.CompanyName),
|
||||
zap.String("auth_flow_id", authURL.AuthFlowID))
|
||||
return authURL, false, nil
|
||||
}
|
||||
if !isEnterpriseAlreadyRealnamedErr(err) {
|
||||
s.logger.Error("企业认证链接生成-步骤1-创建失败且非已实名场景",
|
||||
zap.String("company_name", req.CompanyName),
|
||||
zap.Error(err))
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
s.logger.Warn("企业已实名,跳过生成认证链接并转为自动确认",
|
||||
zap.String("company_name", req.CompanyName),
|
||||
zap.String("unified_social_code", req.UnifiedSocialCode),
|
||||
zap.Error(err))
|
||||
|
||||
identity, identityErr := s.esignClient.QueryOrgIdentityInfo(&esign.QueryOrgIdentityRequest{
|
||||
OrgIDCardNum: req.UnifiedSocialCode,
|
||||
OrgIDCardType: esign.OrgIDCardTypeUSCC,
|
||||
})
|
||||
if identityErr != nil {
|
||||
s.logger.Warn("企业认证链接生成-步骤2-按信用代码查询实名状态失败,回退按企业名查询",
|
||||
zap.String("company_name", req.CompanyName),
|
||||
zap.Error(identityErr))
|
||||
identity, identityErr = s.esignClient.QueryOrgIdentityInfo(&esign.QueryOrgIdentityRequest{
|
||||
OrgName: req.CompanyName,
|
||||
})
|
||||
}
|
||||
if identityErr != nil {
|
||||
return nil, false, fmt.Errorf("企业用户已实名,但查询实名状态失败: %w", identityErr)
|
||||
}
|
||||
s.logger.Info("企业认证链接生成-步骤2-实名状态查询成功",
|
||||
zap.String("company_name", req.CompanyName),
|
||||
zap.Int32("realname_status", identity.Data.RealnameStatus))
|
||||
if identity == nil || identity.Data.RealnameStatus != 1 {
|
||||
return nil, false, err
|
||||
}
|
||||
s.logger.Info("企业认证链接生成-步骤3-确认企业已实名,返回自动确认标记",
|
||||
zap.String("company_name", req.CompanyName))
|
||||
return nil, true, nil
|
||||
}
|
||||
|
||||
func isEnterpriseAlreadyRealnamedErr(err error) bool {
|
||||
if err == nil {
|
||||
return false
|
||||
@@ -1258,6 +1307,18 @@ func isEnterpriseAlreadyRealnamedErr(err error) bool {
|
||||
return strings.Contains(msg, "企业用户已实名") || strings.Contains(msg, "已实名")
|
||||
}
|
||||
|
||||
// isEnterpriseAlreadyAuthorizedErr 法大大等平台:企业已对本应用授权,无需再获取授权链接(如 code=210002)
|
||||
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, "已存在授权")
|
||||
}
|
||||
|
||||
// validateApplyContractCommand 验证申请合同命令
|
||||
func (s *CertificationApplicationServiceImpl) validateApplyContractCommand(cmd *commands.ApplyContractCommand) error {
|
||||
if cmd.UserID == "" {
|
||||
@@ -1279,27 +1340,41 @@ func (s *CertificationApplicationServiceImpl) validateContractApplicationPrecond
|
||||
|
||||
// generateContractAndSignURL 生成合同和签署链接
|
||||
func (s *CertificationApplicationServiceImpl) generateContractAndSignURL(ctx context.Context, cert *entities.Certification, enterpriseInfo *user_entities.EnterpriseInfo) (*certification_value_objects.ContractInfo, error) {
|
||||
// 发起签署流程
|
||||
signFlowID, err := s.esignClient.CreateSignFlow(&esign.CreateSignFlowRequest{
|
||||
FileID: cert.ContractFileID,
|
||||
SignerAccount: enterpriseInfo.UnifiedSocialCode,
|
||||
SignerName: enterpriseInfo.CompanyName,
|
||||
TransactorPhone: enterpriseInfo.LegalPersonPhone,
|
||||
TransactorName: enterpriseInfo.LegalPersonName,
|
||||
TransactorIDCardNum: enterpriseInfo.LegalPersonID,
|
||||
})
|
||||
provider, err := s.resolveProvider(cert)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("生成合同失败: %s", err.Error())
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, shortUrl, err := s.esignClient.GetSignURL(signFlowID, enterpriseInfo.LegalPersonPhone, enterpriseInfo.CompanyName)
|
||||
record, _ := s.enterpriseInfoSubmitRecordRepo.FindLatestByUserID(ctx, cert.UserID)
|
||||
repName := enterpriseInfo.LegalPersonName
|
||||
address := enterpriseInfo.EnterpriseAddress
|
||||
if record != nil {
|
||||
repName = pickAuthorizedRepName(record, enterpriseInfo.LegalPersonName)
|
||||
if address == "" {
|
||||
address = record.EnterpriseAddress
|
||||
}
|
||||
}
|
||||
req := &ports.SignFlowCreateRequest{
|
||||
Subject: "海宇数据-合作协议-" + enterpriseInfo.CompanyName,
|
||||
FileID: cert.ContractFileID,
|
||||
PartyAName: enterpriseInfo.CompanyName,
|
||||
PartyAUSCC: enterpriseInfo.UnifiedSocialCode,
|
||||
TransactorName: enterpriseInfo.LegalPersonName,
|
||||
TransactorMobile: enterpriseInfo.LegalPersonPhone,
|
||||
TransactorID: enterpriseInfo.LegalPersonID,
|
||||
TransReferenceID: cert.ID,
|
||||
Fill: ensureContractFill(cert, enterpriseInfo.CompanyName, enterpriseInfo.UnifiedSocialCode, address, repName),
|
||||
}
|
||||
signRes, err := provider.CreateSignFlow(ctx, req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("获取签署链接失败: %s", err.Error())
|
||||
return nil, fmt.Errorf("生成签署流程失败: %s", err.Error())
|
||||
}
|
||||
// iframe 必须用长链 SignURL(法大大 EmbedURL);短链不可嵌入
|
||||
embedURL := firstNonEmptyStr(signRes.SignURL, signRes.ShortURL)
|
||||
return &certification_value_objects.ContractInfo{
|
||||
ContractFileID: cert.ContractFileID,
|
||||
EsignFlowID: signFlowID,
|
||||
ContractSignURL: shortUrl,
|
||||
ContractFileID: cert.ContractFileID,
|
||||
EsignFlowID: signRes.SignFlowID,
|
||||
ContractSignURL: embedURL,
|
||||
ContractSignShortURL: strings.TrimSpace(signRes.ShortURL),
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -1333,7 +1408,7 @@ func (s *CertificationApplicationServiceImpl) completeEnterpriseVerification(
|
||||
zap.String("user_id", userID),
|
||||
zap.String("record_id", record.ID))
|
||||
|
||||
err = s.userAggregateService.CreateEnterpriseInfo(
|
||||
err = s.userAggregateService.CreateOrUpdateEnterpriseInfo(
|
||||
ctx,
|
||||
userID,
|
||||
record.CompanyName,
|
||||
@@ -1346,17 +1421,26 @@ 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 {
|
||||
return err
|
||||
}
|
||||
s.logger.Info("完成企业认证-步骤3-合同文件生成并写入认证成功", zap.String("user_id", userID))
|
||||
|
||||
// 立即创建并提交签署任务:乙方免验证签先盖章;甲方稍后在「立即签署」取 iframe 长链
|
||||
// 创建失败必须整体失败,不得推进到 enterprise_verified / 签署合同步骤
|
||||
if err := s.createSignTaskAfterEnterpriseVerified(ctx, cert, record); err != nil {
|
||||
s.logger.Error("企业认证后预创建签署任务失败", zap.Error(err), zap.String("user_id", userID))
|
||||
return fmt.Errorf("创建签署任务失败: %w", err)
|
||||
}
|
||||
s.logger.Info("完成企业认证-步骤3b-签署任务已创建(乙方免签)",
|
||||
zap.String("user_id", userID),
|
||||
zap.String("esign_flow_id", cert.EsignFlowID))
|
||||
|
||||
// 保存认证信息
|
||||
err = s.aggregateService.SaveCertification(ctx, cert)
|
||||
if err != nil {
|
||||
@@ -1368,6 +1452,46 @@ func (s *CertificationApplicationServiceImpl) completeEnterpriseVerification(
|
||||
return nil
|
||||
}
|
||||
|
||||
// createSignTaskAfterEnterpriseVerified 认证完成后创建签署任务并启动(乙方免签),不推进到 contract_applied
|
||||
func (s *CertificationApplicationServiceImpl) createSignTaskAfterEnterpriseVerified(
|
||||
ctx context.Context,
|
||||
cert *entities.Certification,
|
||||
record *entities.EnterpriseInfoSubmitRecord,
|
||||
) error {
|
||||
if cert == nil || record == nil {
|
||||
return fmt.Errorf("认证或企业信息为空")
|
||||
}
|
||||
if strings.TrimSpace(cert.EsignFlowID) != "" {
|
||||
return nil
|
||||
}
|
||||
provider, err := s.resolveProvider(cert)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
repName := pickAuthorizedRepName(record, record.LegalPersonName)
|
||||
address := record.EnterpriseAddress
|
||||
req := &ports.SignFlowCreateRequest{
|
||||
Subject: "海宇数据-合作协议-" + record.CompanyName,
|
||||
FileID: cert.ContractFileID,
|
||||
PartyAName: record.CompanyName,
|
||||
PartyAUSCC: record.UnifiedSocialCode,
|
||||
TransactorName: record.LegalPersonName,
|
||||
TransactorMobile: record.LegalPersonPhone,
|
||||
TransactorID: record.LegalPersonID,
|
||||
TransReferenceID: cert.ID,
|
||||
Fill: ensureContractFill(cert, record.CompanyName, record.UnifiedSocialCode, address, repName),
|
||||
SkipActorURL: true, // 预创建不取甲方链接,避免消耗单次 EmbedURL
|
||||
}
|
||||
signRes, err := provider.CreateSignFlow(ctx, req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
storeURL := firstNonEmptyStr(signRes.ShortURL, signRes.SignURL)
|
||||
cert.BindSignTask(signRes.SignFlowID, storeURL)
|
||||
// 预览链接约 2 小时/单次,进入预览页时再调 get-preview-url,此处不落库 PDF 直链
|
||||
return nil
|
||||
}
|
||||
|
||||
// pickAuthorizedRepName 合同模板「客户授权代表」: 优先企业提交记录中的授权代表, 否则为法定代表人
|
||||
func pickAuthorizedRepName(record *entities.EnterpriseInfoSubmitRecord, legalPersonName string) string {
|
||||
if record != nil && strings.TrimSpace(record.AuthorizedRepName) != "" {
|
||||
@@ -1396,22 +1520,18 @@ func (s *CertificationApplicationServiceImpl) generateAndAddContractFile(
|
||||
agreementNo := cert.ContractCode
|
||||
|
||||
signDate := time.Now().Format("2006年01月02日")
|
||||
|
||||
// 控件 key 与 e 签宝合同模板中控件名一致(新合同)
|
||||
fileComponent := map[string]string{
|
||||
"jfqym": companyName,
|
||||
"jfqym2": companyName,
|
||||
"jfsqdb": authorizedRepName,
|
||||
"jftyshxydm": unifiedSocialCode,
|
||||
"jflxdz": enterpriseAddress,
|
||||
// 甲方
|
||||
"xybh": agreementNo,
|
||||
"qsrq1": signDate,
|
||||
"qsrq3": signDate,
|
||||
// 乙方
|
||||
"qsrq2": signDate,
|
||||
provider, err := s.resolveProvider(cert)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fillTemplateResp, err := s.esignClient.FillTemplate(fileComponent)
|
||||
fillTemplateResp, err := provider.GenerateContractFile(ctx, &ports.ContractGenerateRequest{
|
||||
AgreementNo: agreementNo,
|
||||
CompanyName: companyName,
|
||||
UnifiedSocialCode: unifiedSocialCode,
|
||||
EnterpriseAddress: enterpriseAddress,
|
||||
AuthorizedRepName: authorizedRepName,
|
||||
SignDate: signDate,
|
||||
})
|
||||
if err != nil {
|
||||
s.logger.Error("生成合同失败", zap.Error(err))
|
||||
return fmt.Errorf("生成合同失败: %s", err.Error())
|
||||
@@ -1419,8 +1539,9 @@ func (s *CertificationApplicationServiceImpl) generateAndAddContractFile(
|
||||
s.logger.Info("合同生成-步骤1-模板填充成功",
|
||||
zap.String("user_id", cert.UserID),
|
||||
zap.String("file_id", fillTemplateResp.FileID),
|
||||
zap.String("contract_code", agreementNo))
|
||||
err = cert.AddContractFileID(fillTemplateResp.FileID, fillTemplateResp.FileDownloadUrl)
|
||||
zap.String("contract_code", agreementNo),
|
||||
zap.String("sign_platform", string(cert.ResolvedSignPlatform())))
|
||||
err = cert.AddContractFileID(fillTemplateResp.FileID, fillTemplateResp.FileDownloadURL)
|
||||
if err != nil {
|
||||
s.logger.Error("加入合同文件ID链接失败", zap.Error(err))
|
||||
return fmt.Errorf("加入合同文件ID链接失败: %s", err.Error())
|
||||
@@ -1466,13 +1587,19 @@ func (s *CertificationApplicationServiceImpl) checkAndCompleteEnterpriseVerifica
|
||||
if err != nil {
|
||||
return fmt.Errorf("查找企业信息失败: %w", err)
|
||||
}
|
||||
identity, err := s.esignClient.QueryOrgIdentityInfo(&esign.QueryOrgIdentityRequest{
|
||||
OrgName: record.CompanyName,
|
||||
provider, err := s.resolveProvider(cert)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
verified, err := provider.QueryOrgVerified(ctx, &ports.OrgIdentityQuery{
|
||||
OrgName: record.CompanyName,
|
||||
OrgIdentNo: record.UnifiedSocialCode,
|
||||
})
|
||||
if err != nil {
|
||||
s.logger.Error("查询企业认证信息失败", zap.Error(err))
|
||||
return nil
|
||||
}
|
||||
if identity != nil && identity.Data.RealnameStatus == 1 {
|
||||
if verified {
|
||||
err = s.txManager.ExecuteInTx(ctx, func(txCtx context.Context) error {
|
||||
return s.completeEnterpriseVerification(txCtx, cert, cert.UserID, record.CompanyName, record.LegalPersonName)
|
||||
})
|
||||
@@ -1490,11 +1617,15 @@ func (s *CertificationApplicationServiceImpl) checkAndUpdateSignStatus(ctx conte
|
||||
if cert.Status != enums.StatusContractApplied {
|
||||
return fmt.Errorf("认证状态不正确")
|
||||
}
|
||||
detail, err := s.esignClient.QuerySignFlowDetail(cert.EsignFlowID)
|
||||
provider, err := s.resolveProvider(cert)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
detail, err := provider.QuerySignStatus(txCtx, cert.EsignFlowID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("查询签署流程详情失败: %s", err.Error())
|
||||
}
|
||||
if detail.Data.SignFlowStatus == 2 {
|
||||
if detail.Completed {
|
||||
err = cert.SignSuccess()
|
||||
if err != nil {
|
||||
return fmt.Errorf("合同签署成功失败: %s", err.Error())
|
||||
@@ -1503,21 +1634,19 @@ func (s *CertificationApplicationServiceImpl) checkAndUpdateSignStatus(ctx conte
|
||||
if err != nil {
|
||||
return fmt.Errorf("完成认证失败: %s", err.Error())
|
||||
}
|
||||
// 同步合同信息到用户域
|
||||
err = s.handleContractAfterSignComplete(txCtx, cert)
|
||||
if err != nil {
|
||||
s.logger.Error("同步合同信息到用户域失败", zap.Error(err))
|
||||
return fmt.Errorf("同步合同信息失败: %s", err.Error())
|
||||
}
|
||||
|
||||
reason = "合同签署成功"
|
||||
} else if detail.Data.SignFlowStatus == 7 {
|
||||
err = cert.ContractRejection(detail.Data.SignFlowDescription)
|
||||
} else if detail.Rejected {
|
||||
err = cert.ContractRejection(detail.Message)
|
||||
if err != nil {
|
||||
return fmt.Errorf("合同签署失败: %s", err.Error())
|
||||
}
|
||||
reason = "合同签署拒签"
|
||||
} else if detail.Data.SignFlowStatus == 5 {
|
||||
} else if detail.Expired {
|
||||
err = cert.ContractExpiration()
|
||||
if err != nil {
|
||||
return fmt.Errorf("合同签署过期失败: %s", err.Error())
|
||||
@@ -1549,40 +1678,42 @@ func (s *CertificationApplicationServiceImpl) handleContractAfterSignComplete(ct
|
||||
return fmt.Errorf("用户企业信息不存在")
|
||||
}
|
||||
|
||||
// 1. 获取所有已签署合同文件信息
|
||||
downloadSignedFileResponse, err := s.esignClient.DownloadSignedFile(cert.EsignFlowID)
|
||||
provider, err := s.resolveProvider(cert)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
files, err := provider.DownloadSignedFiles(ctx, cert.EsignFlowID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("下载已签署文件失败: %s", err.Error())
|
||||
}
|
||||
files := downloadSignedFileResponse.Data.Files
|
||||
if len(files) == 0 {
|
||||
return fmt.Errorf("未获取到已签署合同文件")
|
||||
}
|
||||
|
||||
for _, file := range files {
|
||||
fileUrl := file.DownloadUrl
|
||||
fileName := file.FileName
|
||||
fileId := file.FileId
|
||||
for i, file := range files {
|
||||
fileUrl := file.DownloadURL
|
||||
fileName := fmt.Sprintf("合作协议-%s-%d.pdf", cert.ContractCode, i+1)
|
||||
fileId := firstNonEmptyStr(file.DownloadID, cert.EsignFlowID)
|
||||
s.logger.Info("下载已签署文件准备", zap.String("file_url", fileUrl), zap.String("file_name", fileName))
|
||||
|
||||
// 2. 下载文件内容
|
||||
fileBytes, err := s.downloadFileContent(ctx, fileUrl)
|
||||
if err != nil {
|
||||
s.logger.Error("下载合同文件内容失败", zap.String("file_name", fileName), zap.Error(err))
|
||||
continue
|
||||
}
|
||||
|
||||
// 3. 上传到七牛云
|
||||
uploadResult, err := s.qiniuStorageService.UploadFile(ctx, fileBytes, fileName)
|
||||
if err != nil {
|
||||
s.logger.Error("上传合同文件到七牛云失败", zap.String("file_name", fileName), zap.Error(err))
|
||||
continue
|
||||
}
|
||||
qiniuURL := uploadResult.URL
|
||||
|
||||
s.logger.Info("合同文件已上传七牛云", zap.String("file_name", fileName), zap.String("qiniu_url", qiniuURL))
|
||||
|
||||
// 4. 保存到合同聚合根(复用认证阶段的合同编号)
|
||||
if i == 0 {
|
||||
cert.ContractURL = qiniuURL
|
||||
}
|
||||
|
||||
_, err = s.contractAggregateService.CreateContractWithCode(
|
||||
ctx,
|
||||
user.EnterpriseInfo.ID,
|
||||
@@ -1597,7 +1728,6 @@ func (s *CertificationApplicationServiceImpl) handleContractAfterSignComplete(ct
|
||||
s.logger.Error("保存合同信息到聚合根失败", zap.String("file_name", fileName), zap.Error(err))
|
||||
continue
|
||||
}
|
||||
|
||||
s.logger.Info("合同信息已保存到聚合根", zap.String("file_name", fileName), zap.String("qiniu_url", qiniuURL))
|
||||
}
|
||||
|
||||
@@ -1648,10 +1778,22 @@ func (s *CertificationApplicationServiceImpl) AddStatusMetadata(ctx context.Cont
|
||||
metadata["contract_url"] = contracts[0].ContractFileURL
|
||||
}
|
||||
}
|
||||
|
||||
metadata = s.appendPlatformSelectMetadata(ctx, cert, metadata)
|
||||
if need, _ := metadata["need_select_platform"].(bool); need {
|
||||
metadata["next_action"] = "请选择签署平台以继续企业认证"
|
||||
}
|
||||
return metadata, nil
|
||||
}
|
||||
|
||||
func firstNonEmptyStr(values ...string) string {
|
||||
for _, v := range values {
|
||||
if strings.TrimSpace(v) != "" {
|
||||
return v
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// completeUserActivationWithoutContract 创建钱包、API用户并在用户域标记完成认证(不依赖合同信息)
|
||||
func (s *CertificationApplicationServiceImpl) completeUserActivationWithoutContract(ctx context.Context, cert *entities.Certification) error {
|
||||
// 创建钱包
|
||||
|
||||
@@ -102,6 +102,12 @@ type AdminTransitionCertificationStatusCommand struct {
|
||||
Remark string `json:"remark"`
|
||||
}
|
||||
|
||||
// SelectSignPlatformCommand 用户选择签署平台
|
||||
type SelectSignPlatformCommand struct {
|
||||
UserID string `json:"-"`
|
||||
SignPlatform string `json:"sign_platform" binding:"required"`
|
||||
}
|
||||
|
||||
// SubmitEnterpriseInfoCommand 提交企业信息命令
|
||||
type SubmitEnterpriseInfoCommand struct {
|
||||
UserID string `json:"-" comment:"用户唯一标识,从JWT token获取,不在JSON中暴露"`
|
||||
|
||||
@@ -9,11 +9,12 @@ import (
|
||||
|
||||
// CertificationResponse 认证响应
|
||||
type CertificationResponse struct {
|
||||
ID string `json:"id"`
|
||||
UserID string `json:"user_id"`
|
||||
Status enums.CertificationStatus `json:"status"`
|
||||
StatusName string `json:"status_name"`
|
||||
Progress int `json:"progress"`
|
||||
ID string `json:"id"`
|
||||
UserID string `json:"user_id"`
|
||||
Status enums.CertificationStatus `json:"status"`
|
||||
StatusName string `json:"status_name"`
|
||||
SignPlatform string `json:"sign_platform,omitempty"`
|
||||
Progress int `json:"progress"`
|
||||
|
||||
// 企业信息
|
||||
EnterpriseInfo *value_objects.EnterpriseInfo `json:"enterprise_info,omitempty"`
|
||||
@@ -73,12 +74,26 @@ type CertificationListResponse struct {
|
||||
|
||||
// ContractSignUrlResponse 合同签署URL响应
|
||||
type ContractSignUrlResponse struct {
|
||||
CertificationID string `json:"certification_id"`
|
||||
ContractSignURL string `json:"contract_sign_url"`
|
||||
ContractURL string `json:"contract_url,omitempty"`
|
||||
ExpireAt *time.Time `json:"expire_at,omitempty"`
|
||||
NextAction string `json:"next_action"`
|
||||
Message string `json:"message"`
|
||||
CertificationID string `json:"certification_id"`
|
||||
// ContractSignURL iframe 可用的签署长链(法大大 EmbedURL,约 10 分钟/单次)
|
||||
ContractSignURL string `json:"contract_sign_url"`
|
||||
// ContractSignShortURL 短链(不可 iframe,一年有效)
|
||||
ContractSignShortURL string `json:"contract_sign_short_url,omitempty"`
|
||||
ContractURL string `json:"contract_url,omitempty"`
|
||||
ExpireAt *time.Time `json:"expire_at,omitempty"`
|
||||
NextAction string `json:"next_action"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
// ContractPreviewUrlResponse 合同预览链接响应(法大大 get-preview-url)
|
||||
type ContractPreviewUrlResponse struct {
|
||||
CertificationID string `json:"certification_id"`
|
||||
// PreviewURL 签署任务预览 EUI 链接(约 2 小时/单次),用 iframe 打开,勿当 PDF 直链
|
||||
PreviewURL string `json:"preview_url"`
|
||||
// PreviewMode iframe | pdf
|
||||
PreviewMode string `json:"preview_mode"`
|
||||
SignFlowID string `json:"sign_flow_id,omitempty"`
|
||||
Message string `json:"message,omitempty"`
|
||||
}
|
||||
|
||||
// SystemMonitoringResponse 系统监控响应
|
||||
@@ -119,7 +134,9 @@ type AdminSubmitRecordItem struct {
|
||||
LegalPersonName string `json:"legal_person_name"`
|
||||
SubmitAt time.Time `json:"submit_at"`
|
||||
Status string `json:"status"`
|
||||
CertificationStatus string `json:"certification_status,omitempty"` // 以状态机为准:info_pending_review/info_submitted/info_rejected 等
|
||||
ManualReviewStatus string `json:"manual_review_status,omitempty"` // pending/approved/rejected
|
||||
SignPlatform string `json:"sign_platform,omitempty"` // esign/fadada
|
||||
CertificationStatus string `json:"certification_status,omitempty"` // 以状态机为准:info_pending_review/info_submitted/info_rejected 等
|
||||
}
|
||||
|
||||
// AdminSubmitRecordDetail 管理端提交记录详情(含完整信息与图片 URL)
|
||||
@@ -145,6 +162,9 @@ type AdminSubmitRecordDetail struct {
|
||||
VerifiedAt *time.Time `json:"verified_at,omitempty"`
|
||||
FailedAt *time.Time `json:"failed_at,omitempty"`
|
||||
FailureReason string `json:"failure_reason,omitempty"`
|
||||
ManualReviewStatus string `json:"manual_review_status,omitempty"`
|
||||
ManualReviewRemark string `json:"manual_review_remark,omitempty"`
|
||||
SignPlatform string `json:"sign_platform,omitempty"`
|
||||
CertificationStatus string `json:"certification_status,omitempty"` // 以状态机为准
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
@@ -178,17 +198,19 @@ func NewCertificationListResponse(items []*CertificationResponse, total int64, p
|
||||
}
|
||||
|
||||
// NewContractSignUrlResponse 创建合同签署URL响应
|
||||
func NewContractSignUrlResponse(certificationID, signURL, contractURL, nextAction, message string) *ContractSignUrlResponse {
|
||||
// signURL 为 iframe 长链;shortURL 为短链(可选)
|
||||
func NewContractSignUrlResponse(certificationID, signURL, shortURL, contractURL, nextAction, message string) *ContractSignUrlResponse {
|
||||
response := &ContractSignUrlResponse{
|
||||
CertificationID: certificationID,
|
||||
ContractSignURL: signURL,
|
||||
ContractURL: contractURL,
|
||||
NextAction: nextAction,
|
||||
Message: message,
|
||||
CertificationID: certificationID,
|
||||
ContractSignURL: signURL,
|
||||
ContractSignShortURL: shortURL,
|
||||
ContractURL: contractURL,
|
||||
NextAction: nextAction,
|
||||
Message: message,
|
||||
}
|
||||
|
||||
// 设置过期时间(默认24小时)
|
||||
expireAt := time.Now().Add(24 * time.Hour)
|
||||
// 法大大嵌入链接约 10 分钟;统一按 10 分钟提示前端刷新
|
||||
expireAt := time.Now().Add(10 * time.Minute)
|
||||
response.ExpireAt = &expireAt
|
||||
|
||||
return response
|
||||
@@ -208,6 +230,19 @@ func NewSystemAlert(level, alertType, message, metric string, value, threshold i
|
||||
}
|
||||
}
|
||||
|
||||
// SignPlatformItem 可选签署平台
|
||||
type SignPlatformItem struct {
|
||||
Platform string `json:"platform"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Available bool `json:"available"`
|
||||
}
|
||||
|
||||
// SignPlatformsResponse 签署平台列表响应
|
||||
type SignPlatformsResponse struct {
|
||||
Platforms []*SignPlatformItem `json:"platforms"`
|
||||
}
|
||||
|
||||
// IsHealthy 检查系统是否健康
|
||||
func (r *SystemMonitoringResponse) IsHealthy() bool {
|
||||
return r.SystemHealth.Overall == "healthy"
|
||||
|
||||
273
internal/application/certification/platform_flow.go
Normal file
273
internal/application/certification/platform_flow.go
Normal file
@@ -0,0 +1,273 @@
|
||||
package certification
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"hyapi-server/internal/application/certification/dto/commands"
|
||||
"hyapi-server/internal/application/certification/dto/responses"
|
||||
"hyapi-server/internal/domains/certification/entities"
|
||||
"hyapi-server/internal/domains/certification/enums"
|
||||
"hyapi-server/internal/domains/certification/ports"
|
||||
user_entities "hyapi-server/internal/domains/user/entities"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// ListSignPlatforms 可选签署平台列表
|
||||
func (s *CertificationApplicationServiceImpl) ListSignPlatforms(ctx context.Context) (*responses.SignPlatformsResponse, error) {
|
||||
_ = ctx
|
||||
items := make([]*responses.SignPlatformItem, 0, 2)
|
||||
for _, p := range s.platformRegistry.List() {
|
||||
platform := p.Platform()
|
||||
item := &responses.SignPlatformItem{
|
||||
Platform: string(platform),
|
||||
Name: enums.GetSignPlatformName(platform),
|
||||
Available: true,
|
||||
Description: "",
|
||||
}
|
||||
switch platform {
|
||||
case enums.SignPlatformEsign:
|
||||
item.Description = "通过 e签宝 完成企业实名认证与合同签署"
|
||||
case enums.SignPlatformFadada:
|
||||
item.Description = "通过法大大完成企业实名认证与合同签署"
|
||||
}
|
||||
items = append(items, item)
|
||||
}
|
||||
return &responses.SignPlatformsResponse{Platforms: items}, nil
|
||||
}
|
||||
|
||||
// SelectSignPlatform 用户选择签署平台并生成企业认证链接
|
||||
func (s *CertificationApplicationServiceImpl) SelectSignPlatform(
|
||||
ctx context.Context,
|
||||
cmd *commands.SelectSignPlatformCommand,
|
||||
) (*responses.CertificationResponse, error) {
|
||||
if cmd == nil || cmd.UserID == "" {
|
||||
return nil, fmt.Errorf("用户ID不能为空")
|
||||
}
|
||||
platform := enums.SignPlatform(strings.TrimSpace(cmd.SignPlatform))
|
||||
if !enums.IsValidSignPlatform(platform) {
|
||||
return nil, fmt.Errorf("无效的签署平台: %s", cmd.SignPlatform)
|
||||
}
|
||||
|
||||
cert, err := s.aggregateService.LoadCertificationByUserID(ctx, cmd.UserID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("加载认证信息失败: %w", err)
|
||||
}
|
||||
|
||||
record, err := s.enterpriseInfoSubmitRecordRepo.FindLatestByUserID(ctx, cmd.UserID)
|
||||
if err != nil || record == nil {
|
||||
return nil, fmt.Errorf("未找到企业信息提交记录")
|
||||
}
|
||||
if record.ManualReviewStatus != "approved" {
|
||||
return nil, fmt.Errorf("请先等待管理员审核通过后再选择签署平台")
|
||||
}
|
||||
|
||||
// 已选择且已有认证链接:幂等返回
|
||||
if cert.SignPlatform == platform && cert.Status == enums.StatusInfoSubmitted && cert.AuthURL != "" {
|
||||
resp := s.convertToResponse(cert)
|
||||
meta, _ := s.AddStatusMetadata(ctx, cert)
|
||||
resp.Metadata = meta
|
||||
return resp, nil
|
||||
}
|
||||
if cert.SignPlatform != "" && cert.SignPlatform != platform {
|
||||
return nil, fmt.Errorf("签署平台已选择为 %s,不可更改", enums.GetSignPlatformName(cert.SignPlatform))
|
||||
}
|
||||
if cert.Status != enums.StatusInfoPendingReview && !(cert.Status == enums.StatusInfoSubmitted && cert.AuthURL == "") {
|
||||
return nil, fmt.Errorf("当前状态不允许选择签署平台: %s", enums.GetStatusName(cert.Status))
|
||||
}
|
||||
|
||||
if err := cert.SetSignPlatform(platform); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
provider, err := s.platformRegistry.Get(platform)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
authReq := &ports.EnterpriseAuthRequest{
|
||||
ClientCorpID: record.UnifiedSocialCode,
|
||||
ClientUserID: record.LegalPersonID,
|
||||
CompanyName: record.CompanyName,
|
||||
UnifiedSocialCode: record.UnifiedSocialCode,
|
||||
LegalPersonName: record.LegalPersonName,
|
||||
LegalPersonID: record.LegalPersonID,
|
||||
TransactorName: record.LegalPersonName,
|
||||
TransactorMobile: record.LegalPersonPhone,
|
||||
TransactorID: record.LegalPersonID,
|
||||
}
|
||||
|
||||
authRes, alreadyVerified, err := s.generateEnterpriseAuthOrDetectVerifiedWithProvider(ctx, provider, authReq)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("生成企业认证链接失败: %w", err)
|
||||
}
|
||||
|
||||
if alreadyVerified {
|
||||
// 已授权/已实名:跳过授权页,直接推进到 enterprise_verified
|
||||
err = s.txManager.ExecuteInTx(ctx, func(txCtx context.Context) error {
|
||||
clientCorpID := strings.TrimSpace(record.UnifiedSocialCode)
|
||||
if cert.Status == enums.StatusInfoPendingReview {
|
||||
if err := cert.ApproveEnterpriseInfoReview("", clientCorpID, cmd.UserID); err != nil {
|
||||
return err
|
||||
}
|
||||
} else if cert.Status == enums.StatusInfoSubmitted {
|
||||
if clientCorpID != "" && cert.AuthFlowID == "" {
|
||||
cert.AuthFlowID = clientCorpID
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf("当前状态不允许完成已授权企业认证: %s", enums.GetStatusName(cert.Status))
|
||||
}
|
||||
return s.completeEnterpriseVerification(txCtx, cert, cert.UserID, record.CompanyName, record.LegalPersonName)
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
authURL := authRes.AuthShortURL
|
||||
if authURL == "" {
|
||||
authURL = authRes.AuthURL
|
||||
}
|
||||
if err := cert.ApproveEnterpriseInfoReview(authURL, authRes.AuthFlowID, cmd.UserID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := s.aggregateService.SaveCertification(ctx, cert); err != nil {
|
||||
return nil, fmt.Errorf("保存认证信息失败: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
s.logger.Info("用户已选择签署平台",
|
||||
zap.String("user_id", cmd.UserID),
|
||||
zap.String("sign_platform", string(platform)),
|
||||
zap.Bool("already_verified", alreadyVerified))
|
||||
|
||||
resp := s.convertToResponse(cert)
|
||||
meta, _ := s.AddStatusMetadata(ctx, cert)
|
||||
resp.Metadata = meta
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// HandleFadadaCallback 法大大回调
|
||||
func (s *CertificationApplicationServiceImpl) HandleFadadaCallback(ctx context.Context, headers map[string]string, body []byte) error {
|
||||
provider, err := s.platformRegistry.Get(enums.SignPlatformFadada)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := provider.VerifyCallback(ctx, headers, body); err != nil {
|
||||
return fmt.Errorf("法大大回调验签失败: %w", err)
|
||||
}
|
||||
ev, err := provider.ParseCallback(ctx, headers, body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("法大大回调解析失败: %w", err)
|
||||
}
|
||||
s.logger.Info("收到法大大回调",
|
||||
zap.String("event", ev.Event),
|
||||
zap.Bool("auth_passed", ev.AuthPassed),
|
||||
zap.Bool("sign_completed", ev.SignCompleted),
|
||||
zap.String("sign_flow_id", ev.SignFlowID))
|
||||
|
||||
// 认证完成主要依赖 ConfirmAuth / details 轮询;签署完成按任务 ID 兜底推进
|
||||
if ev.SignCompleted && ev.SignFlowID != "" {
|
||||
cert, err := s.queryRepository.FindByEsignFlowID(ctx, ev.SignFlowID)
|
||||
if err == nil && cert != nil && cert.Status == enums.StatusContractApplied {
|
||||
_, _ = s.checkAndUpdateSignStatus(ctx, cert)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *CertificationApplicationServiceImpl) resolveProvider(cert *entities.Certification) (ports.SignPlatformProvider, error) {
|
||||
return s.platformRegistry.Get(cert.ResolvedSignPlatform())
|
||||
}
|
||||
|
||||
func (s *CertificationApplicationServiceImpl) generateEnterpriseAuthOrDetectVerifiedWithProvider(
|
||||
ctx context.Context,
|
||||
provider ports.SignPlatformProvider,
|
||||
authReq *ports.EnterpriseAuthRequest,
|
||||
) (*ports.AuthLinkResult, bool, error) {
|
||||
authRes, err := provider.GenerateEnterpriseAuth(ctx, authReq)
|
||||
if err == nil {
|
||||
return authRes, false, nil
|
||||
}
|
||||
|
||||
// 法大大:企业已对本应用授权(210002)= 授权已完成,直接跳过授权页
|
||||
if isEnterpriseAlreadyAuthorizedErr(err) {
|
||||
s.logger.Info("第三方返回企业已授权,跳过认证链接并视为认证完成",
|
||||
zap.String("company_name", authReq.CompanyName),
|
||||
zap.String("uscc", authReq.UnifiedSocialCode),
|
||||
zap.String("platform", string(provider.Platform())),
|
||||
zap.Error(err))
|
||||
return nil, true, nil
|
||||
}
|
||||
|
||||
// e签宝等:提示已实名时再查一次实名状态确认
|
||||
if !isEnterpriseAlreadyRealnamedErr(err) && !strings.Contains(strings.ToLower(err.Error()), "identified") {
|
||||
return nil, false, err
|
||||
}
|
||||
ok, qErr := provider.QueryOrgVerified(ctx, &ports.OrgIdentityQuery{
|
||||
OrgName: authReq.CompanyName,
|
||||
OrgIdentNo: authReq.UnifiedSocialCode,
|
||||
})
|
||||
if qErr != nil {
|
||||
return nil, false, fmt.Errorf("%v; 且查询实名状态失败: %w", err, qErr)
|
||||
}
|
||||
if ok {
|
||||
s.logger.Info("第三方返回企业已实名,跳过认证链接并视为认证完成",
|
||||
zap.String("company_name", authReq.CompanyName),
|
||||
zap.String("platform", string(provider.Platform())))
|
||||
return nil, true, nil
|
||||
}
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
func (s *CertificationApplicationServiceImpl) appendPlatformSelectMetadata(
|
||||
ctx context.Context,
|
||||
cert *entities.Certification,
|
||||
metadata map[string]interface{},
|
||||
) map[string]interface{} {
|
||||
if metadata == nil {
|
||||
metadata = map[string]interface{}{}
|
||||
}
|
||||
record, err := s.enterpriseInfoSubmitRecordRepo.FindLatestByUserID(ctx, cert.UserID)
|
||||
if err != nil || record == nil {
|
||||
return metadata
|
||||
}
|
||||
metadata["manual_review_status"] = record.ManualReviewStatus
|
||||
if record.ManualReviewStatus == "approved" &&
|
||||
cert.Status == enums.StatusInfoPendingReview &&
|
||||
(cert.SignPlatform == "" || cert.AuthURL == "") {
|
||||
metadata["need_select_platform"] = true
|
||||
platforms := make([]map[string]string, 0, 2)
|
||||
for _, p := range s.platformRegistry.List() {
|
||||
platforms = append(platforms, map[string]string{
|
||||
"platform": string(p.Platform()),
|
||||
"name": enums.GetSignPlatformName(p.Platform()),
|
||||
})
|
||||
}
|
||||
metadata["available_platforms"] = platforms
|
||||
metadata["next_action"] = "请选择签署平台以继续企业认证"
|
||||
}
|
||||
if cert.SignPlatform != "" {
|
||||
metadata["sign_platform"] = string(cert.SignPlatform)
|
||||
metadata["sign_platform_name"] = enums.GetSignPlatformName(cert.SignPlatform)
|
||||
}
|
||||
return metadata
|
||||
}
|
||||
|
||||
// ensureContractFill 签署模板路径补齐协议编号与日期(协议编号生成规则与 e签宝一致)
|
||||
func ensureContractFill(cert *entities.Certification, companyName, uscc, address, repName string) *ports.ContractGenerateRequest {
|
||||
if cert.ContractCode == "" {
|
||||
cert.SetContractCode(user_entities.GenerateContractCode(user_entities.ContractTypeCooperation))
|
||||
}
|
||||
signDate := time.Now().Format("2006年01月02日")
|
||||
return &ports.ContractGenerateRequest{
|
||||
AgreementNo: cert.ContractCode,
|
||||
CompanyName: companyName,
|
||||
UnifiedSocialCode: uscc,
|
||||
EnterpriseAddress: address,
|
||||
AuthorizedRepName: repName,
|
||||
SignDate: signDate,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user