2025-07-13 16:36:20 +08:00
|
|
|
|
package certification
|
|
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
|
"context"
|
2026-03-17 17:18:54 +08:00
|
|
|
|
"encoding/json"
|
2025-07-13 16:36:20 +08:00
|
|
|
|
"fmt"
|
2025-07-28 01:46:39 +08:00
|
|
|
|
"io"
|
|
|
|
|
|
"net/http"
|
2026-03-26 15:31:37 +08:00
|
|
|
|
"strings"
|
2025-07-28 01:46:39 +08:00
|
|
|
|
"time"
|
2025-07-13 16:36:20 +08:00
|
|
|
|
|
2026-04-25 11:59:10 +08:00
|
|
|
|
"github.com/shopspring/decimal"
|
2025-07-13 16:36:20 +08:00
|
|
|
|
"tyapi-server/internal/application/certification/dto/commands"
|
|
|
|
|
|
"tyapi-server/internal/application/certification/dto/queries"
|
|
|
|
|
|
"tyapi-server/internal/application/certification/dto/responses"
|
2026-03-17 17:18:54 +08:00
|
|
|
|
"tyapi-server/internal/config"
|
2025-07-28 01:46:39 +08:00
|
|
|
|
api_service "tyapi-server/internal/domains/api/services"
|
2025-07-13 16:36:20 +08:00
|
|
|
|
"tyapi-server/internal/domains/certification/entities"
|
2025-07-28 01:46:39 +08:00
|
|
|
|
certification_value_objects "tyapi-server/internal/domains/certification/entities/value_objects"
|
2025-07-20 20:53:26 +08:00
|
|
|
|
"tyapi-server/internal/domains/certification/enums"
|
2025-07-21 15:13:26 +08:00
|
|
|
|
"tyapi-server/internal/domains/certification/repositories"
|
2026-04-25 11:59:10 +08:00
|
|
|
|
finance_entities "tyapi-server/internal/domains/finance/entities"
|
|
|
|
|
|
finance_repositories "tyapi-server/internal/domains/finance/repositories"
|
2025-07-13 16:36:20 +08:00
|
|
|
|
"tyapi-server/internal/domains/certification/services"
|
2026-04-25 11:59:10 +08:00
|
|
|
|
subordinate_repositories "tyapi-server/internal/domains/subordinate/repositories"
|
2025-07-28 01:46:39 +08:00
|
|
|
|
finance_service "tyapi-server/internal/domains/finance/services"
|
|
|
|
|
|
user_entities "tyapi-server/internal/domains/user/entities"
|
2026-03-17 17:18:54 +08:00
|
|
|
|
user_service "tyapi-server/internal/domains/user/services"
|
2026-02-25 19:33:56 +08:00
|
|
|
|
"tyapi-server/internal/infrastructure/external/notification"
|
2025-07-28 01:46:39 +08:00
|
|
|
|
"tyapi-server/internal/infrastructure/external/storage"
|
|
|
|
|
|
"tyapi-server/internal/shared/database"
|
|
|
|
|
|
"tyapi-server/internal/shared/esign"
|
2025-09-12 01:15:09 +08:00
|
|
|
|
sharedOCR "tyapi-server/internal/shared/ocr"
|
2025-07-21 15:13:26 +08:00
|
|
|
|
|
|
|
|
|
|
"go.uber.org/zap"
|
2025-07-13 16:36:20 +08:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
// CertificationApplicationServiceImpl 认证应用服务实现
|
2025-07-21 15:13:26 +08:00
|
|
|
|
// 负责用例协调,DTO转换,是应用层的核心组件
|
2025-07-13 16:36:20 +08:00
|
|
|
|
type CertificationApplicationServiceImpl struct {
|
2025-07-21 15:13:26 +08:00
|
|
|
|
// 领域服务依赖
|
2025-07-28 01:46:39 +08:00
|
|
|
|
aggregateService services.CertificationAggregateService
|
|
|
|
|
|
userAggregateService user_service.UserAggregateService
|
|
|
|
|
|
smsCodeService *user_service.SMSCodeService
|
|
|
|
|
|
esignClient *esign.Client
|
|
|
|
|
|
esignConfig *esign.Config
|
|
|
|
|
|
qiniuStorageService *storage.QiNiuStorageService
|
|
|
|
|
|
contractAggregateService user_service.ContractAggregateService
|
|
|
|
|
|
walletAggregateService finance_service.WalletAggregateService
|
|
|
|
|
|
apiUserAggregateService api_service.ApiUserAggregateService
|
|
|
|
|
|
enterpriseInfoSubmitRecordService *services.EnterpriseInfoSubmitRecordService
|
2025-09-12 01:15:09 +08:00
|
|
|
|
ocrService sharedOCR.OCRService
|
2025-07-21 15:13:26 +08:00
|
|
|
|
// 仓储依赖
|
2025-07-28 01:46:39 +08:00
|
|
|
|
queryRepository repositories.CertificationQueryRepository
|
|
|
|
|
|
enterpriseInfoSubmitRecordRepo repositories.EnterpriseInfoSubmitRecordRepository
|
2026-04-25 11:59:10 +08:00
|
|
|
|
subordinateRepo subordinate_repositories.SubordinateRepository
|
|
|
|
|
|
walletRepo finance_repositories.WalletRepository
|
2025-07-28 01:46:39 +08:00
|
|
|
|
txManager *database.TransactionManager
|
2025-07-21 15:13:26 +08:00
|
|
|
|
|
2026-02-25 19:33:56 +08:00
|
|
|
|
wechatWorkService *notification.WeChatWorkService
|
|
|
|
|
|
logger *zap.Logger
|
2026-03-17 17:18:54 +08:00
|
|
|
|
config *config.Config
|
2025-07-13 16:36:20 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// NewCertificationApplicationService 创建认证应用服务
|
|
|
|
|
|
func NewCertificationApplicationService(
|
2025-07-21 15:13:26 +08:00
|
|
|
|
aggregateService services.CertificationAggregateService,
|
2025-07-28 01:46:39 +08:00
|
|
|
|
userAggregateService user_service.UserAggregateService,
|
2025-07-21 15:13:26 +08:00
|
|
|
|
queryRepository repositories.CertificationQueryRepository,
|
2025-07-28 01:46:39 +08:00
|
|
|
|
enterpriseInfoSubmitRecordRepo repositories.EnterpriseInfoSubmitRecordRepository,
|
|
|
|
|
|
smsCodeService *user_service.SMSCodeService,
|
|
|
|
|
|
esignClient *esign.Client,
|
|
|
|
|
|
esignConfig *esign.Config,
|
|
|
|
|
|
qiniuStorageService *storage.QiNiuStorageService,
|
|
|
|
|
|
contractAggregateService user_service.ContractAggregateService,
|
|
|
|
|
|
walletAggregateService finance_service.WalletAggregateService,
|
|
|
|
|
|
apiUserAggregateService api_service.ApiUserAggregateService,
|
|
|
|
|
|
enterpriseInfoSubmitRecordService *services.EnterpriseInfoSubmitRecordService,
|
2025-09-12 01:15:09 +08:00
|
|
|
|
ocrService sharedOCR.OCRService,
|
2026-04-25 11:59:10 +08:00
|
|
|
|
subordinateRepo subordinate_repositories.SubordinateRepository,
|
|
|
|
|
|
walletRepo finance_repositories.WalletRepository,
|
2025-07-28 01:46:39 +08:00
|
|
|
|
txManager *database.TransactionManager,
|
2025-07-13 16:36:20 +08:00
|
|
|
|
logger *zap.Logger,
|
2026-02-25 19:33:56 +08:00
|
|
|
|
cfg *config.Config,
|
2025-07-13 16:36:20 +08:00
|
|
|
|
) CertificationApplicationService {
|
2026-02-25 19:33:56 +08:00
|
|
|
|
var wechatSvc *notification.WeChatWorkService
|
|
|
|
|
|
if cfg != nil && cfg.WechatWork.WebhookURL != "" {
|
|
|
|
|
|
wechatSvc = notification.NewWeChatWorkService(cfg.WechatWork.WebhookURL, cfg.WechatWork.Secret, logger)
|
|
|
|
|
|
}
|
2025-07-13 16:36:20 +08:00
|
|
|
|
return &CertificationApplicationServiceImpl{
|
2025-07-28 01:46:39 +08:00
|
|
|
|
aggregateService: aggregateService,
|
|
|
|
|
|
userAggregateService: userAggregateService,
|
|
|
|
|
|
queryRepository: queryRepository,
|
|
|
|
|
|
enterpriseInfoSubmitRecordRepo: enterpriseInfoSubmitRecordRepo,
|
|
|
|
|
|
smsCodeService: smsCodeService,
|
|
|
|
|
|
esignClient: esignClient,
|
|
|
|
|
|
esignConfig: esignConfig,
|
|
|
|
|
|
qiniuStorageService: qiniuStorageService,
|
|
|
|
|
|
contractAggregateService: contractAggregateService,
|
|
|
|
|
|
walletAggregateService: walletAggregateService,
|
|
|
|
|
|
apiUserAggregateService: apiUserAggregateService,
|
|
|
|
|
|
enterpriseInfoSubmitRecordService: enterpriseInfoSubmitRecordService,
|
2025-09-12 01:15:09 +08:00
|
|
|
|
ocrService: ocrService,
|
2026-04-25 11:59:10 +08:00
|
|
|
|
subordinateRepo: subordinateRepo,
|
|
|
|
|
|
walletRepo: walletRepo,
|
2025-07-28 01:46:39 +08:00
|
|
|
|
txManager: txManager,
|
2026-02-25 19:33:56 +08:00
|
|
|
|
wechatWorkService: wechatSvc,
|
2025-07-28 01:46:39 +08:00
|
|
|
|
logger: logger,
|
2026-03-17 17:18:54 +08:00
|
|
|
|
config: cfg,
|
2025-07-13 16:36:20 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-07-21 15:13:26 +08:00
|
|
|
|
// ================ 用户操作用例 ================
|
2025-07-13 16:36:20 +08:00
|
|
|
|
|
2025-07-28 01:46:39 +08:00
|
|
|
|
// SubmitEnterpriseInfo 提交企业信息
|
|
|
|
|
|
func (s *CertificationApplicationServiceImpl) SubmitEnterpriseInfo(
|
2025-07-21 15:13:26 +08:00
|
|
|
|
ctx context.Context,
|
2025-07-28 01:46:39 +08:00
|
|
|
|
cmd *commands.SubmitEnterpriseInfoCommand,
|
2025-07-21 15:13:26 +08:00
|
|
|
|
) (*responses.CertificationResponse, error) {
|
2025-07-28 01:46:39 +08:00
|
|
|
|
s.logger.Info("开始提交企业信息",
|
2026-03-26 16:08:48 +08:00
|
|
|
|
zap.String("user_id", cmd.UserID),
|
|
|
|
|
|
zap.String("company_name", cmd.CompanyName),
|
|
|
|
|
|
zap.String("unified_social_code", cmd.UnifiedSocialCode))
|
2025-07-21 15:13:26 +08:00
|
|
|
|
|
2026-03-19 13:23:48 +08:00
|
|
|
|
// 0. 若该用户已有待审核(认证状态仍在待审核),则不允许重复提交
|
2026-03-18 11:44:37 +08:00
|
|
|
|
latestRecord, err := s.enterpriseInfoSubmitRecordRepo.FindLatestByUserID(ctx, cmd.UserID)
|
2026-03-19 13:23:48 +08:00
|
|
|
|
if err == nil && latestRecord != nil {
|
2026-03-26 16:08:48 +08:00
|
|
|
|
s.logger.Info("步骤0-检测到历史提交记录",
|
|
|
|
|
|
zap.String("user_id", cmd.UserID),
|
|
|
|
|
|
zap.String("latest_record_id", latestRecord.ID))
|
2026-03-19 13:23:48 +08:00
|
|
|
|
cert, loadErr := s.aggregateService.LoadCertificationByUserID(ctx, cmd.UserID)
|
|
|
|
|
|
if loadErr == nil && cert != nil && cert.Status == enums.StatusInfoPendingReview {
|
2026-03-26 16:08:48 +08:00
|
|
|
|
s.logger.Warn("步骤0-存在待审核记录,拒绝重复提交",
|
|
|
|
|
|
zap.String("user_id", cmd.UserID),
|
|
|
|
|
|
zap.String("cert_status", string(cert.Status)))
|
2026-03-19 13:23:48 +08:00
|
|
|
|
return nil, fmt.Errorf("您已有待审核的提交,请等待管理员审核后再操作")
|
|
|
|
|
|
}
|
2026-03-18 11:44:37 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-02 12:46:42 +08:00
|
|
|
|
// 0.5 已通过人工审核或已进入后续流程:幂等返回当前认证数据(不调 e签宝、不新建提交记录)
|
|
|
|
|
|
existsCertEarly, err := s.aggregateService.ExistsByUserID(ctx, cmd.UserID)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return nil, fmt.Errorf("检查认证记录失败: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
if existsCertEarly {
|
|
|
|
|
|
certEarly, loadErr := s.aggregateService.LoadCertificationByUserID(ctx, cmd.UserID)
|
|
|
|
|
|
if loadErr != nil {
|
|
|
|
|
|
return nil, fmt.Errorf("加载认证信息失败: %w", loadErr)
|
|
|
|
|
|
}
|
|
|
|
|
|
switch certEarly.Status {
|
|
|
|
|
|
case enums.StatusInfoSubmitted, enums.StatusEnterpriseVerified, enums.StatusContractApplied,
|
|
|
|
|
|
enums.StatusContractSigned, enums.StatusCompleted, enums.StatusContractRejected, enums.StatusContractExpired:
|
|
|
|
|
|
meta, metaErr := s.AddStatusMetadata(ctx, certEarly)
|
|
|
|
|
|
if metaErr != nil {
|
|
|
|
|
|
return nil, metaErr
|
|
|
|
|
|
}
|
|
|
|
|
|
resp := s.convertToResponse(certEarly)
|
|
|
|
|
|
if meta != nil {
|
|
|
|
|
|
resp.Metadata = meta
|
|
|
|
|
|
} else {
|
|
|
|
|
|
resp.Metadata = map[string]interface{}{}
|
|
|
|
|
|
}
|
|
|
|
|
|
resp.Metadata["next_action"] = enums.GetUserActionHint(certEarly.Status)
|
|
|
|
|
|
s.logger.Info("企业信息提交幂等返回", zap.String("user_id", cmd.UserID), zap.String("status", string(certEarly.Status)))
|
|
|
|
|
|
return resp, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-17 17:18:54 +08:00
|
|
|
|
// 1.5 插入企业信息提交记录(包含扩展字段)
|
2025-07-30 02:44:02 +08:00
|
|
|
|
record := entities.NewEnterpriseInfoSubmitRecord(
|
|
|
|
|
|
cmd.UserID,
|
|
|
|
|
|
cmd.CompanyName,
|
|
|
|
|
|
cmd.UnifiedSocialCode,
|
|
|
|
|
|
cmd.LegalPersonName,
|
|
|
|
|
|
cmd.LegalPersonID,
|
|
|
|
|
|
cmd.LegalPersonPhone,
|
|
|
|
|
|
cmd.EnterpriseAddress,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2026-03-17 17:18:54 +08:00
|
|
|
|
// 扩展字段赋值
|
|
|
|
|
|
record.BusinessLicenseImageURL = cmd.BusinessLicenseImageURL
|
|
|
|
|
|
if len(cmd.OfficePlaceImageURLs) > 0 {
|
|
|
|
|
|
if data, mErr := json.Marshal(cmd.OfficePlaceImageURLs); mErr == nil {
|
|
|
|
|
|
record.OfficePlaceImageURLs = string(data)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
s.logger.Warn("序列化办公场地图片URL失败", zap.Error(mErr))
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
record.APIUsage = cmd.APIUsage
|
|
|
|
|
|
if len(cmd.ScenarioAttachmentURLs) > 0 {
|
|
|
|
|
|
if data, mErr := json.Marshal(cmd.ScenarioAttachmentURLs); mErr == nil {
|
|
|
|
|
|
record.ScenarioAttachmentURLs = string(data)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
s.logger.Warn("序列化场景附件图片URL失败", zap.Error(mErr))
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
// 授权代表信息落库
|
|
|
|
|
|
record.AuthorizedRepName = cmd.AuthorizedRepName
|
|
|
|
|
|
record.AuthorizedRepID = cmd.AuthorizedRepID
|
|
|
|
|
|
record.AuthorizedRepPhone = cmd.AuthorizedRepPhone
|
|
|
|
|
|
if len(cmd.AuthorizedRepIDImageURLs) > 0 {
|
|
|
|
|
|
if data, mErr := json.Marshal(cmd.AuthorizedRepIDImageURLs); mErr == nil {
|
|
|
|
|
|
record.AuthorizedRepIDImageURLs = string(data)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
s.logger.Warn("序列化授权代表身份证图片URL失败", zap.Error(mErr))
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-07-28 01:46:39 +08:00
|
|
|
|
// 验证验证码
|
2025-12-23 15:04:53 +08:00
|
|
|
|
// 特殊验证码"768005"直接跳过验证环节
|
|
|
|
|
|
if cmd.VerificationCode != "768005" {
|
2026-03-26 16:08:48 +08:00
|
|
|
|
s.logger.Info("步骤1-开始验证短信验证码", zap.String("user_id", cmd.UserID))
|
2025-12-23 15:04:53 +08:00
|
|
|
|
if err := s.smsCodeService.VerifyCode(ctx, cmd.LegalPersonPhone, cmd.VerificationCode, user_entities.SMSSceneCertification); err != nil {
|
2026-03-26 16:08:48 +08:00
|
|
|
|
s.logger.Warn("步骤1-短信验证码校验失败",
|
|
|
|
|
|
zap.String("user_id", cmd.UserID),
|
|
|
|
|
|
zap.Error(err))
|
2025-12-23 15:04:53 +08:00
|
|
|
|
record.MarkAsFailed(err.Error())
|
|
|
|
|
|
saveErr := s.enterpriseInfoSubmitRecordService.Save(ctx, record)
|
|
|
|
|
|
if saveErr != nil {
|
|
|
|
|
|
return nil, fmt.Errorf("保存企业信息提交记录失败: %s", saveErr.Error())
|
|
|
|
|
|
}
|
|
|
|
|
|
return nil, fmt.Errorf("验证码错误或已过期")
|
2025-07-30 02:44:02 +08:00
|
|
|
|
}
|
2026-03-26 16:08:48 +08:00
|
|
|
|
s.logger.Info("步骤1-短信验证码校验通过", zap.String("user_id", cmd.UserID))
|
|
|
|
|
|
} else {
|
|
|
|
|
|
s.logger.Info("步骤1-命中特殊验证码,跳过校验", zap.String("user_id", cmd.UserID))
|
2025-07-28 01:46:39 +08:00
|
|
|
|
}
|
|
|
|
|
|
s.logger.Info("开始处理企业信息提交",
|
|
|
|
|
|
zap.String("user_id", cmd.UserID))
|
2026-03-18 11:44:37 +08:00
|
|
|
|
// 1. 检查企业信息是否重复(统一社会信用代码:已认证或已提交待审核的都不能重复)
|
|
|
|
|
|
// 1.1 已写入用户域 enterprise_infos 的(已完成认证)
|
2025-07-28 01:46:39 +08:00
|
|
|
|
exists, err := s.userAggregateService.CheckUnifiedSocialCodeExists(ctx, cmd.UnifiedSocialCode, cmd.UserID)
|
|
|
|
|
|
if err != nil {
|
2026-03-26 16:08:48 +08:00
|
|
|
|
s.logger.Error("步骤2.1-检查用户域统一社会信用代码失败",
|
|
|
|
|
|
zap.String("user_id", cmd.UserID),
|
|
|
|
|
|
zap.String("unified_social_code", cmd.UnifiedSocialCode),
|
|
|
|
|
|
zap.Error(err))
|
2025-07-30 02:44:02 +08:00
|
|
|
|
record.MarkAsFailed(err.Error())
|
2025-08-05 17:20:49 +08:00
|
|
|
|
saveErr := s.enterpriseInfoSubmitRecordService.Save(ctx, record)
|
|
|
|
|
|
if saveErr != nil {
|
|
|
|
|
|
return nil, fmt.Errorf("保存企业信息提交记录失败: %s", saveErr.Error())
|
2025-07-30 02:44:02 +08:00
|
|
|
|
}
|
2025-07-28 01:46:39 +08:00
|
|
|
|
return nil, fmt.Errorf("检查企业信息失败: %s", err.Error())
|
|
|
|
|
|
}
|
|
|
|
|
|
if exists {
|
2026-03-26 16:08:48 +08:00
|
|
|
|
s.logger.Warn("步骤2.1-统一社会信用代码已被占用(用户域)",
|
|
|
|
|
|
zap.String("user_id", cmd.UserID),
|
|
|
|
|
|
zap.String("unified_social_code", cmd.UnifiedSocialCode))
|
2025-08-05 17:20:49 +08:00
|
|
|
|
record.MarkAsFailed("该企业信息已被其他用户使用,请确认企业信息是否正确")
|
|
|
|
|
|
saveErr := s.enterpriseInfoSubmitRecordService.Save(ctx, record)
|
|
|
|
|
|
if saveErr != nil {
|
|
|
|
|
|
return nil, fmt.Errorf("保存企业信息提交记录失败: %s", saveErr.Error())
|
2025-07-30 02:44:02 +08:00
|
|
|
|
}
|
2025-07-28 01:46:39 +08:00
|
|
|
|
return nil, fmt.Errorf("该企业信息已被其他用户使用,请确认企业信息是否正确")
|
2026-03-18 11:44:37 +08:00
|
|
|
|
}
|
|
|
|
|
|
// 1.2 已提交/已通过验证的提交记录(尚未完成认证但已占用的信用代码)
|
|
|
|
|
|
existsInSubmit, err := s.enterpriseInfoSubmitRecordRepo.ExistsByUnifiedSocialCodeExcludeUser(ctx, cmd.UnifiedSocialCode, cmd.UserID)
|
|
|
|
|
|
if err != nil {
|
2026-03-26 16:08:48 +08:00
|
|
|
|
s.logger.Error("步骤2.2-检查提交记录统一社会信用代码失败",
|
|
|
|
|
|
zap.String("user_id", cmd.UserID),
|
|
|
|
|
|
zap.String("unified_social_code", cmd.UnifiedSocialCode),
|
|
|
|
|
|
zap.Error(err))
|
2026-03-18 11:44:37 +08:00
|
|
|
|
record.MarkAsFailed(err.Error())
|
|
|
|
|
|
saveErr := s.enterpriseInfoSubmitRecordService.Save(ctx, record)
|
|
|
|
|
|
if saveErr != nil {
|
|
|
|
|
|
return nil, fmt.Errorf("保存企业信息提交记录失败: %s", saveErr.Error())
|
|
|
|
|
|
}
|
|
|
|
|
|
return nil, fmt.Errorf("检查企业信息失败: %s", err.Error())
|
|
|
|
|
|
}
|
|
|
|
|
|
if existsInSubmit {
|
2026-03-26 16:08:48 +08:00
|
|
|
|
s.logger.Warn("步骤2.2-统一社会信用代码已被占用(提交记录)",
|
|
|
|
|
|
zap.String("user_id", cmd.UserID),
|
|
|
|
|
|
zap.String("unified_social_code", cmd.UnifiedSocialCode))
|
2026-03-18 11:44:37 +08:00
|
|
|
|
record.MarkAsFailed("该企业信息已被其他用户使用,请确认企业信息是否正确")
|
|
|
|
|
|
saveErr := s.enterpriseInfoSubmitRecordService.Save(ctx, record)
|
|
|
|
|
|
if saveErr != nil {
|
|
|
|
|
|
return nil, fmt.Errorf("保存企业信息提交记录失败: %s", saveErr.Error())
|
|
|
|
|
|
}
|
|
|
|
|
|
return nil, fmt.Errorf("该企业信息已被其他用户使用,请确认企业信息是否正确")
|
2025-07-28 01:46:39 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
enterpriseInfo := &certification_value_objects.EnterpriseInfo{
|
|
|
|
|
|
CompanyName: cmd.CompanyName,
|
|
|
|
|
|
UnifiedSocialCode: cmd.UnifiedSocialCode,
|
|
|
|
|
|
LegalPersonName: cmd.LegalPersonName,
|
|
|
|
|
|
LegalPersonID: cmd.LegalPersonID,
|
|
|
|
|
|
LegalPersonPhone: cmd.LegalPersonPhone,
|
|
|
|
|
|
EnterpriseAddress: cmd.EnterpriseAddress,
|
|
|
|
|
|
}
|
|
|
|
|
|
err = enterpriseInfo.Validate()
|
2025-07-13 16:36:20 +08:00
|
|
|
|
if err != nil {
|
2025-07-28 01:46:39 +08:00
|
|
|
|
s.logger.Error("企业信息验证失败", zap.Error(err))
|
|
|
|
|
|
record.MarkAsFailed(err.Error())
|
2025-08-05 17:20:49 +08:00
|
|
|
|
saveErr := s.enterpriseInfoSubmitRecordService.Save(ctx, record)
|
|
|
|
|
|
if saveErr != nil {
|
|
|
|
|
|
return nil, fmt.Errorf("保存企业信息提交记录失败: %s", saveErr.Error())
|
2025-07-30 02:44:02 +08:00
|
|
|
|
}
|
2025-07-28 01:46:39 +08:00
|
|
|
|
return nil, fmt.Errorf("企业信息验证失败: %s", err.Error())
|
|
|
|
|
|
}
|
2026-03-26 16:08:48 +08:00
|
|
|
|
s.logger.Info("步骤3-企业信息基础校验通过",
|
|
|
|
|
|
zap.String("user_id", cmd.UserID),
|
|
|
|
|
|
zap.String("company_name", enterpriseInfo.CompanyName))
|
2025-12-11 19:32:46 +08:00
|
|
|
|
err = s.enterpriseInfoSubmitRecordService.ValidateWithWestdex(ctx, enterpriseInfo)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
s.logger.Error("企业信息验证失败", zap.Error(err))
|
|
|
|
|
|
record.MarkAsFailed(err.Error())
|
|
|
|
|
|
saveErr := s.enterpriseInfoSubmitRecordService.Save(ctx, record)
|
|
|
|
|
|
if saveErr != nil {
|
|
|
|
|
|
return nil, fmt.Errorf("保存企业信息提交记录失败: %s", saveErr.Error())
|
2025-07-28 01:46:39 +08:00
|
|
|
|
}
|
2025-12-11 19:32:46 +08:00
|
|
|
|
return nil, fmt.Errorf("企业信息验证失败, %s", err.Error())
|
2025-07-28 01:46:39 +08:00
|
|
|
|
}
|
2026-03-26 16:08:48 +08:00
|
|
|
|
s.logger.Info("步骤4-企业信息三方校验通过",
|
|
|
|
|
|
zap.String("user_id", cmd.UserID),
|
|
|
|
|
|
zap.String("company_name", enterpriseInfo.CompanyName))
|
2025-07-28 01:46:39 +08:00
|
|
|
|
record.MarkAsVerified()
|
2025-07-21 15:13:26 +08:00
|
|
|
|
|
2025-07-28 01:46:39 +08:00
|
|
|
|
var response *responses.CertificationResponse
|
|
|
|
|
|
err = s.txManager.ExecuteInTx(ctx, func(txCtx context.Context) error {
|
2026-03-26 16:08:48 +08:00
|
|
|
|
s.logger.Info("步骤5-开始事务处理认证提交流程", zap.String("user_id", cmd.UserID))
|
2025-07-28 01:46:39 +08:00
|
|
|
|
// 2. 检查用户认证是否存在
|
|
|
|
|
|
existsCert, err := s.aggregateService.ExistsByUserID(txCtx, cmd.UserID)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return fmt.Errorf("检查用户认证是否存在失败: %s", err.Error())
|
|
|
|
|
|
}
|
|
|
|
|
|
if !existsCert {
|
|
|
|
|
|
// 创建
|
2026-03-26 16:08:48 +08:00
|
|
|
|
s.logger.Info("步骤5.1-认证记录不存在,开始创建", zap.String("user_id", cmd.UserID))
|
2025-07-28 01:46:39 +08:00
|
|
|
|
_, err := s.aggregateService.CreateCertification(txCtx, cmd.UserID)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return fmt.Errorf("创建认证信息失败: %s", err.Error())
|
|
|
|
|
|
}
|
2026-03-26 16:08:48 +08:00
|
|
|
|
s.logger.Info("步骤5.1-认证记录创建成功", zap.String("user_id", cmd.UserID))
|
2025-07-28 01:46:39 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 3. 加载认证聚合根
|
|
|
|
|
|
cert, err := s.aggregateService.LoadCertificationByUserID(txCtx, cmd.UserID)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return fmt.Errorf("加载认证信息失败: %s", err.Error())
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-02 12:46:42 +08:00
|
|
|
|
// 4. 提交企业信息:进入人工审核(三真/企业信息审核);e签宝链接仅在管理员审核通过后生成(见 AdminApproveSubmitRecord)
|
|
|
|
|
|
if err := cert.SubmitEnterpriseInfoForReview(enterpriseInfo); err != nil {
|
|
|
|
|
|
return fmt.Errorf("提交企业信息失败: %s", err.Error())
|
2026-03-19 17:29:06 +08:00
|
|
|
|
}
|
2026-04-02 12:46:42 +08:00
|
|
|
|
if err := s.aggregateService.SaveCertification(txCtx, cert); err != nil {
|
|
|
|
|
|
return fmt.Errorf("保存认证信息失败: %s", err.Error())
|
2025-07-28 01:46:39 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-19 15:21:02 +08:00
|
|
|
|
// 5. 提交记录与认证状态在同一事务内保存
|
2026-03-18 11:44:37 +08:00
|
|
|
|
if saveErr := s.enterpriseInfoSubmitRecordService.Save(txCtx, record); saveErr != nil {
|
|
|
|
|
|
return fmt.Errorf("保存企业信息提交记录失败: %s", saveErr.Error())
|
|
|
|
|
|
}
|
2026-03-26 16:08:48 +08:00
|
|
|
|
s.logger.Info("步骤5.3-企业信息提交记录保存成功",
|
|
|
|
|
|
zap.String("user_id", cmd.UserID),
|
|
|
|
|
|
zap.String("record_id", record.ID))
|
2026-03-18 11:44:37 +08:00
|
|
|
|
|
2026-04-02 12:46:42 +08:00
|
|
|
|
var enterpriseInfoMeta map[string]interface{}
|
|
|
|
|
|
if raw, mErr := json.Marshal(enterpriseInfo); mErr == nil {
|
|
|
|
|
|
_ = json.Unmarshal(raw, &enterpriseInfoMeta)
|
|
|
|
|
|
}
|
|
|
|
|
|
if enterpriseInfoMeta == nil {
|
|
|
|
|
|
enterpriseInfoMeta = map[string]interface{}{}
|
|
|
|
|
|
}
|
|
|
|
|
|
enterpriseInfoMeta["submit_at"] = record.SubmitAt.Format(time.RFC3339)
|
|
|
|
|
|
|
2026-03-18 11:44:37 +08:00
|
|
|
|
respMeta := map[string]interface{}{
|
2026-04-02 12:46:42 +08:00
|
|
|
|
"enterprise_info": enterpriseInfoMeta,
|
2026-03-26 15:31:37 +08:00
|
|
|
|
"polling": map[string]interface{}{
|
2026-04-02 12:46:42 +08:00
|
|
|
|
"enabled": false,
|
2026-03-26 15:31:37 +08:00
|
|
|
|
"endpoint": "/api/v1/certifications/confirm-auth",
|
|
|
|
|
|
"interval_seconds": 3,
|
|
|
|
|
|
},
|
2026-04-02 12:46:42 +08:00
|
|
|
|
"next_action": "请等待管理员审核企业信息",
|
|
|
|
|
|
"target_view": "manual_review",
|
2026-03-18 11:44:37 +08:00
|
|
|
|
}
|
|
|
|
|
|
// 6. 转换为响应 DTO
|
|
|
|
|
|
response = s.convertToResponse(cert)
|
2026-03-26 15:31:37 +08:00
|
|
|
|
response.Metadata = respMeta
|
2025-07-28 01:46:39 +08:00
|
|
|
|
return nil
|
|
|
|
|
|
})
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return nil, err
|
|
|
|
|
|
}
|
2025-07-21 15:13:26 +08:00
|
|
|
|
|
2026-04-02 12:46:42 +08:00
|
|
|
|
// 提醒管理员处理待审核申请(配置企业微信 Webhook 时生效)
|
|
|
|
|
|
if s.wechatWorkService != nil {
|
|
|
|
|
|
contactPhone := cmd.LegalPersonPhone
|
|
|
|
|
|
if strings.TrimSpace(cmd.AuthorizedRepPhone) != "" {
|
|
|
|
|
|
contactPhone = fmt.Sprintf("法人 %s;授权代表 %s", cmd.LegalPersonPhone, cmd.AuthorizedRepPhone)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
contactPhone = fmt.Sprintf("%s(法人)", cmd.LegalPersonPhone)
|
|
|
|
|
|
}
|
|
|
|
|
|
_ = s.wechatWorkService.SendCertificationNotification(ctx, "pending_manual_review", map[string]interface{}{
|
|
|
|
|
|
"company_name": cmd.CompanyName,
|
|
|
|
|
|
"legal_person_name": cmd.LegalPersonName,
|
|
|
|
|
|
"authorized_rep_name": cmd.AuthorizedRepName,
|
|
|
|
|
|
"contact_phone": contactPhone,
|
|
|
|
|
|
"api_usage": cmd.APIUsage,
|
|
|
|
|
|
"submit_at": record.SubmitAt.Format("2006-01-02 15:04:05"),
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-07-28 01:46:39 +08:00
|
|
|
|
s.logger.Info("企业信息提交成功", zap.String("user_id", cmd.UserID))
|
2025-07-21 15:13:26 +08:00
|
|
|
|
return response, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-19 13:23:48 +08:00
|
|
|
|
// 审核状态检查(步骤二)
|
|
|
|
|
|
// 规则:企业信息提交成功后进入待审核;审核通过后才允许进行企业认证确认(ConfirmAuth)。
|
|
|
|
|
|
func (s *CertificationApplicationServiceImpl) checkAuditStatus(ctx context.Context, cert *entities.Certification) error {
|
|
|
|
|
|
switch cert.Status {
|
|
|
|
|
|
case enums.StatusInfoSubmitted,
|
|
|
|
|
|
enums.StatusEnterpriseVerified,
|
|
|
|
|
|
enums.StatusContractApplied,
|
|
|
|
|
|
enums.StatusContractSigned,
|
|
|
|
|
|
enums.StatusCompleted:
|
|
|
|
|
|
return nil
|
|
|
|
|
|
case enums.StatusInfoPendingReview:
|
2026-04-02 12:46:42 +08:00
|
|
|
|
return fmt.Errorf("企业信息已提交,正在审核中")
|
2026-03-19 13:23:48 +08:00
|
|
|
|
case enums.StatusInfoRejected:
|
|
|
|
|
|
return fmt.Errorf("企业信息审核未通过")
|
|
|
|
|
|
default:
|
|
|
|
|
|
return fmt.Errorf("认证状态不正确,当前状态: %s", enums.GetStatusName(cert.Status))
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-07-28 01:46:39 +08:00
|
|
|
|
// ConfirmAuth 确认认证状态
|
|
|
|
|
|
func (s *CertificationApplicationServiceImpl) ConfirmAuth(
|
2025-07-21 15:13:26 +08:00
|
|
|
|
ctx context.Context,
|
2025-07-28 01:46:39 +08:00
|
|
|
|
cmd *queries.ConfirmAuthCommand,
|
|
|
|
|
|
) (*responses.ConfirmAuthResponse, error) {
|
|
|
|
|
|
s.logger.Info("开始确认状态", zap.String("user_id", cmd.UserID))
|
|
|
|
|
|
cert, err := s.aggregateService.LoadCertificationByUserID(ctx, cmd.UserID)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return nil, fmt.Errorf("加载认证信息失败: %s", err.Error())
|
2025-07-21 15:13:26 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-19 13:23:48 +08:00
|
|
|
|
// 步骤二:审核状态检查(审核通过后才能进入企业认证确认)
|
2026-03-26 16:08:48 +08:00
|
|
|
|
s.logger.Info("确认状态-步骤1-开始审核状态检查", zap.String("user_id", cmd.UserID))
|
2026-03-19 13:23:48 +08:00
|
|
|
|
if err := s.checkAuditStatus(ctx, cert); err != nil {
|
|
|
|
|
|
return nil, err
|
2025-07-28 01:46:39 +08:00
|
|
|
|
}
|
2026-03-26 16:08:48 +08:00
|
|
|
|
s.logger.Info("确认状态-步骤1-审核状态检查通过",
|
|
|
|
|
|
zap.String("user_id", cmd.UserID),
|
|
|
|
|
|
zap.String("cert_status", string(cert.Status)))
|
2025-07-28 01:46:39 +08:00
|
|
|
|
record, err := s.enterpriseInfoSubmitRecordRepo.FindLatestByUserID(ctx, cert.UserID)
|
2025-07-21 15:13:26 +08:00
|
|
|
|
if err != nil {
|
2025-07-28 01:46:39 +08:00
|
|
|
|
return nil, fmt.Errorf("查找企业信息失败: %w", err)
|
2025-07-13 16:36:20 +08:00
|
|
|
|
}
|
2026-03-26 16:08:48 +08:00
|
|
|
|
s.logger.Info("确认状态-步骤2-获取最近提交记录成功",
|
|
|
|
|
|
zap.String("user_id", cmd.UserID),
|
|
|
|
|
|
zap.String("record_id", record.ID))
|
|
|
|
|
|
s.logger.Info("确认状态-步骤3-开始查询三方实名状态",
|
|
|
|
|
|
zap.String("user_id", cmd.UserID),
|
|
|
|
|
|
zap.String("company_name", record.CompanyName))
|
2025-07-28 01:46:39 +08:00
|
|
|
|
identity, err := s.esignClient.QueryOrgIdentityInfo(&esign.QueryOrgIdentityRequest{
|
|
|
|
|
|
OrgName: record.CompanyName,
|
|
|
|
|
|
})
|
2025-07-20 20:53:26 +08:00
|
|
|
|
if err != nil {
|
2025-07-28 01:46:39 +08:00
|
|
|
|
s.logger.Error("查询企业认证信息失败", zap.Error(err))
|
|
|
|
|
|
return nil, fmt.Errorf("查询企业认证信息失败: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
reason := ""
|
|
|
|
|
|
if identity != nil && identity.Data.RealnameStatus == 1 {
|
2026-03-26 16:08:48 +08:00
|
|
|
|
s.logger.Info("确认状态-步骤3-三方实名状态已完成,准备事务内推进认证",
|
|
|
|
|
|
zap.String("user_id", cmd.UserID))
|
2025-07-28 01:46:39 +08:00
|
|
|
|
err = s.txManager.ExecuteInTx(ctx, func(txCtx context.Context) error {
|
|
|
|
|
|
err = s.completeEnterpriseVerification(txCtx, cert, cert.UserID, record.CompanyName, record.LegalPersonName)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
reason = "企业认证成功"
|
|
|
|
|
|
return nil
|
|
|
|
|
|
})
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return nil, fmt.Errorf("完成企业认证失败: %w", err)
|
|
|
|
|
|
}
|
2026-03-26 16:08:48 +08:00
|
|
|
|
s.logger.Info("确认状态-步骤4-认证状态推进完成",
|
|
|
|
|
|
zap.String("user_id", cmd.UserID),
|
|
|
|
|
|
zap.String("cert_status", string(cert.Status)))
|
2025-07-28 01:46:39 +08:00
|
|
|
|
} else {
|
|
|
|
|
|
reason = "企业未完成"
|
2026-03-26 16:08:48 +08:00
|
|
|
|
s.logger.Info("确认状态-步骤3-三方实名状态未完成",
|
|
|
|
|
|
zap.String("user_id", cmd.UserID))
|
2025-07-13 16:36:20 +08:00
|
|
|
|
}
|
2025-07-28 01:46:39 +08:00
|
|
|
|
return &responses.ConfirmAuthResponse{
|
|
|
|
|
|
Status: cert.Status,
|
|
|
|
|
|
Reason: reason,
|
|
|
|
|
|
}, nil
|
|
|
|
|
|
}
|
2025-07-13 16:36:20 +08:00
|
|
|
|
|
2025-07-28 01:46:39 +08:00
|
|
|
|
// ConfirmSign 确认签署状态
|
|
|
|
|
|
func (s *CertificationApplicationServiceImpl) ConfirmSign(
|
|
|
|
|
|
ctx context.Context,
|
|
|
|
|
|
cmd *queries.ConfirmSignCommand,
|
|
|
|
|
|
) (*responses.ConfirmSignResponse, error) {
|
|
|
|
|
|
cert, err := s.aggregateService.LoadCertificationByUserID(ctx, cmd.UserID)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return nil, fmt.Errorf("加载认证信息失败: %s", err.Error())
|
|
|
|
|
|
}
|
2025-07-21 15:13:26 +08:00
|
|
|
|
|
2025-07-28 01:46:39 +08:00
|
|
|
|
reason, err := s.checkAndUpdateSignStatus(ctx, cert)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return nil, fmt.Errorf("确认签署状态失败: %w", err)
|
2025-07-20 20:53:26 +08:00
|
|
|
|
}
|
2025-07-21 15:13:26 +08:00
|
|
|
|
|
2025-07-28 01:46:39 +08:00
|
|
|
|
return &responses.ConfirmSignResponse{
|
|
|
|
|
|
Status: cert.Status,
|
|
|
|
|
|
Reason: reason,
|
|
|
|
|
|
}, nil
|
2025-07-21 15:13:26 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ApplyContract 申请合同签署
|
|
|
|
|
|
func (s *CertificationApplicationServiceImpl) ApplyContract(
|
|
|
|
|
|
ctx context.Context,
|
|
|
|
|
|
cmd *commands.ApplyContractCommand,
|
|
|
|
|
|
) (*responses.ContractSignUrlResponse, error) {
|
|
|
|
|
|
s.logger.Info("开始申请合同签署",
|
|
|
|
|
|
zap.String("user_id", cmd.UserID))
|
|
|
|
|
|
|
2025-07-28 01:46:39 +08:00
|
|
|
|
// 1. 验证命令完整性
|
|
|
|
|
|
if err := s.validateApplyContractCommand(cmd); err != nil {
|
|
|
|
|
|
return nil, fmt.Errorf("命令验证失败: %s", err.Error())
|
2025-07-13 16:36:20 +08:00
|
|
|
|
}
|
2025-07-21 15:13:26 +08:00
|
|
|
|
|
2025-07-28 01:46:39 +08:00
|
|
|
|
// 2. 加载认证聚合根
|
|
|
|
|
|
cert, err := s.aggregateService.LoadCertificationByUserID(ctx, cmd.UserID)
|
2025-07-21 15:13:26 +08:00
|
|
|
|
if err != nil {
|
2025-07-28 01:46:39 +08:00
|
|
|
|
return nil, fmt.Errorf("加载认证信息失败: %s", err.Error())
|
2025-07-21 15:13:26 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-07-28 01:46:39 +08:00
|
|
|
|
// 3. 验证业务前置条件
|
|
|
|
|
|
if err := s.validateContractApplicationPreconditions(cert, cmd.UserID); err != nil {
|
|
|
|
|
|
return nil, fmt.Errorf("业务前置条件验证失败: %s", err.Error())
|
|
|
|
|
|
}
|
2025-07-21 15:13:26 +08:00
|
|
|
|
|
2025-07-28 01:46:39 +08:00
|
|
|
|
// 5. 生成合同和签署链接
|
|
|
|
|
|
enterpriseInfo, err := s.userAggregateService.GetUserWithEnterpriseInfo(ctx, cmd.UserID)
|
2025-07-20 20:53:26 +08:00
|
|
|
|
if err != nil {
|
2025-07-28 01:46:39 +08:00
|
|
|
|
s.logger.Error("获取企业信息失败", zap.Error(err))
|
|
|
|
|
|
return nil, fmt.Errorf("获取企业信息失败: %w", err)
|
2025-07-13 16:36:20 +08:00
|
|
|
|
}
|
2025-07-28 01:46:39 +08:00
|
|
|
|
contractInfo, err := s.generateContractAndSignURL(ctx, cert, enterpriseInfo.EnterpriseInfo)
|
2025-07-13 16:36:20 +08:00
|
|
|
|
if err != nil {
|
2025-07-28 01:46:39 +08:00
|
|
|
|
s.logger.Error("生成合同失败", zap.Error(err))
|
|
|
|
|
|
return nil, fmt.Errorf("生成合同失败: %s", err.Error())
|
|
|
|
|
|
}
|
|
|
|
|
|
err = cert.ApplyContract(contractInfo.EsignFlowID, contractInfo.ContractSignURL)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
s.logger.Error("合同申请状态转换失败", zap.Error(err))
|
|
|
|
|
|
return nil, fmt.Errorf("合同申请失败: %s", err.Error())
|
2025-07-13 16:36:20 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-07-28 01:46:39 +08:00
|
|
|
|
// 7. 保存认证信息
|
|
|
|
|
|
err = s.aggregateService.SaveCertification(ctx, cert)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
s.logger.Error("保存认证信息失败", zap.Error(err))
|
|
|
|
|
|
return nil, fmt.Errorf("保存认证信息失败: %s", err.Error())
|
2025-07-13 16:36:20 +08:00
|
|
|
|
}
|
2025-07-21 15:13:26 +08:00
|
|
|
|
|
2025-07-28 01:46:39 +08:00
|
|
|
|
// 8. 构建响应
|
|
|
|
|
|
response := responses.NewContractSignUrlResponse(
|
|
|
|
|
|
cert.ID,
|
|
|
|
|
|
contractInfo.ContractSignURL,
|
|
|
|
|
|
contractInfo.ContractURL,
|
|
|
|
|
|
"请在规定时间内完成合同签署",
|
|
|
|
|
|
"合同申请成功",
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
s.logger.Info("合同申请成功", zap.String("user_id", cmd.UserID))
|
2025-07-21 15:13:26 +08:00
|
|
|
|
return response, nil
|
2025-07-20 20:53:26 +08:00
|
|
|
|
}
|
2025-07-13 16:36:20 +08:00
|
|
|
|
|
2025-07-21 15:13:26 +08:00
|
|
|
|
// ================ 查询用例 ================
|
2025-07-13 16:36:20 +08:00
|
|
|
|
|
2025-07-21 15:13:26 +08:00
|
|
|
|
// GetCertification 获取认证详情
|
|
|
|
|
|
func (s *CertificationApplicationServiceImpl) GetCertification(
|
|
|
|
|
|
ctx context.Context,
|
|
|
|
|
|
query *queries.GetCertificationQuery,
|
|
|
|
|
|
) (*responses.CertificationResponse, error) {
|
2025-07-28 01:46:39 +08:00
|
|
|
|
s.logger.Debug("获取认证详情", zap.String("user_id", query.UserID))
|
2025-07-13 16:36:20 +08:00
|
|
|
|
|
2025-07-28 01:46:39 +08:00
|
|
|
|
// 1. 检查用户认证是否存在
|
|
|
|
|
|
exists, err := s.aggregateService.ExistsByUserID(ctx, query.UserID)
|
2025-07-13 16:36:20 +08:00
|
|
|
|
if err != nil {
|
2025-07-21 15:13:26 +08:00
|
|
|
|
s.logger.Error("获取认证信息失败", zap.Error(err))
|
2025-07-28 01:46:39 +08:00
|
|
|
|
return nil, fmt.Errorf("获取认证信息失败: %w", err)
|
2025-07-13 16:36:20 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-07-28 01:46:39 +08:00
|
|
|
|
var cert *entities.Certification
|
|
|
|
|
|
if !exists {
|
|
|
|
|
|
// 创建新的认证记录
|
|
|
|
|
|
cert, err = s.aggregateService.CreateCertification(ctx, query.UserID)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
s.logger.Error("创建认证信息失败", zap.Error(err))
|
|
|
|
|
|
return nil, fmt.Errorf("创建认证信息失败: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 加载现有认证记录
|
|
|
|
|
|
cert, err = s.aggregateService.LoadCertificationByUserID(ctx, query.UserID)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
s.logger.Error("加载认证信息失败", zap.Error(err))
|
|
|
|
|
|
return nil, fmt.Errorf("加载认证信息失败: %w", err)
|
|
|
|
|
|
}
|
2025-07-13 16:36:20 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-07-28 01:46:39 +08:00
|
|
|
|
// 2. 检查是否需要更新合同文件
|
|
|
|
|
|
if cert.IsContractFileNeedUpdate() {
|
|
|
|
|
|
err = s.updateContractFile(ctx, cert)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return nil, err
|
2025-07-21 15:13:26 +08:00
|
|
|
|
}
|
2025-07-13 16:36:20 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-07-28 01:46:39 +08:00
|
|
|
|
if cert.Status == enums.StatusInfoSubmitted {
|
|
|
|
|
|
err = s.checkAndCompleteEnterpriseVerification(ctx, cert)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return nil, err
|
|
|
|
|
|
}
|
2025-07-13 16:36:20 +08:00
|
|
|
|
}
|
2025-07-28 01:46:39 +08:00
|
|
|
|
if cert.Status == enums.StatusContractApplied {
|
|
|
|
|
|
_, err = s.checkAndUpdateSignStatus(ctx, cert)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return nil, err
|
|
|
|
|
|
}
|
2025-07-13 16:36:20 +08:00
|
|
|
|
}
|
2025-07-28 01:46:39 +08:00
|
|
|
|
// 2. 转换为响应DTO
|
|
|
|
|
|
response := s.convertToResponse(cert)
|
2025-07-21 15:13:26 +08:00
|
|
|
|
|
2025-07-28 01:46:39 +08:00
|
|
|
|
// 3. 添加状态相关的元数据
|
2025-07-30 00:51:22 +08:00
|
|
|
|
meta, err := s.AddStatusMetadata(ctx, cert)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return nil, err
|
|
|
|
|
|
}
|
2025-07-28 01:46:39 +08:00
|
|
|
|
if meta != nil {
|
|
|
|
|
|
response.Metadata = meta
|
|
|
|
|
|
}
|
2025-07-21 15:13:26 +08:00
|
|
|
|
|
2025-07-28 01:46:39 +08:00
|
|
|
|
s.logger.Info("获取认证详情成功", zap.String("user_id", query.UserID))
|
2025-07-21 15:13:26 +08:00
|
|
|
|
return response, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ListCertifications 获取认证列表(管理员)
|
|
|
|
|
|
func (s *CertificationApplicationServiceImpl) ListCertifications(
|
|
|
|
|
|
ctx context.Context,
|
|
|
|
|
|
query *queries.ListCertificationsQuery,
|
|
|
|
|
|
) (*responses.CertificationListResponse, error) {
|
|
|
|
|
|
s.logger.Debug("获取认证列表(管理员)")
|
|
|
|
|
|
|
|
|
|
|
|
// 1. 转换为领域查询对象
|
|
|
|
|
|
domainQuery := query.ToDomainQuery()
|
|
|
|
|
|
|
|
|
|
|
|
// 2. 执行查询
|
|
|
|
|
|
certs, total, err := s.queryRepository.List(ctx, domainQuery)
|
2025-07-13 16:36:20 +08:00
|
|
|
|
if err != nil {
|
2025-07-21 15:13:26 +08:00
|
|
|
|
s.logger.Error("查询认证列表失败", zap.Error(err))
|
|
|
|
|
|
return nil, fmt.Errorf("查询认证列表失败: %w", err)
|
2025-07-13 16:36:20 +08:00
|
|
|
|
}
|
2025-07-21 15:13:26 +08:00
|
|
|
|
|
|
|
|
|
|
// 3. 转换为响应DTO
|
|
|
|
|
|
items := make([]*responses.CertificationResponse, len(certs))
|
|
|
|
|
|
for i, cert := range certs {
|
|
|
|
|
|
items[i] = s.convertToResponse(cert)
|
2025-07-20 20:53:26 +08:00
|
|
|
|
}
|
2025-07-21 15:13:26 +08:00
|
|
|
|
|
|
|
|
|
|
// 4. 构建列表响应
|
|
|
|
|
|
response := responses.NewCertificationListResponse(items, total, query.Page, query.PageSize)
|
|
|
|
|
|
|
|
|
|
|
|
return response, nil
|
2025-07-13 16:36:20 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-07-21 15:13:26 +08:00
|
|
|
|
// ================ e签宝回调处理 ================
|
|
|
|
|
|
|
|
|
|
|
|
// HandleEsignCallback 处理e签宝回调
|
|
|
|
|
|
func (s *CertificationApplicationServiceImpl) HandleEsignCallback(
|
|
|
|
|
|
ctx context.Context,
|
|
|
|
|
|
cmd *commands.EsignCallbackCommand,
|
2025-07-28 01:46:39 +08:00
|
|
|
|
) error {
|
|
|
|
|
|
// if err := esign.VerifySignature(cmd.Data, cmd.Headers, cmd.QueryParams, s.esignConfig.AppSecret); err != nil {
|
|
|
|
|
|
// return fmt.Errorf("e签宝回调验签失败: %w", err)
|
|
|
|
|
|
// }
|
|
|
|
|
|
// 4. 根据回调类型处理业务逻辑
|
|
|
|
|
|
switch cmd.Data.Action {
|
|
|
|
|
|
case "AUTH_PASS":
|
|
|
|
|
|
// 只处理企业认证通过
|
|
|
|
|
|
if cmd.Data.AuthType == "ORG" {
|
|
|
|
|
|
err := s.txManager.ExecuteInTx(ctx, func(txCtx context.Context) error {
|
|
|
|
|
|
|
|
|
|
|
|
// 1. 根据AuthFlowId加载认证信息
|
|
|
|
|
|
cert, err := s.aggregateService.LoadCertificationByAuthFlowId(txCtx, cmd.Data.AuthFlowId)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return fmt.Errorf("加载认证信息失败: %s", err.Error())
|
|
|
|
|
|
}
|
|
|
|
|
|
if cmd.Data.Organization == nil || cmd.Data.Organization.OrgName == "" {
|
|
|
|
|
|
return fmt.Errorf("组织信息为空")
|
|
|
|
|
|
}
|
|
|
|
|
|
if cert.Status != enums.StatusInfoSubmitted {
|
|
|
|
|
|
return fmt.Errorf("认证状态不正确")
|
|
|
|
|
|
}
|
|
|
|
|
|
// 2. 完成企业认证
|
|
|
|
|
|
err = cert.CompleteEnterpriseVerification()
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return fmt.Errorf("完成企业认证失败: %s", err.Error())
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
record, err := s.enterpriseInfoSubmitRecordRepo.FindLatestByUserID(txCtx, cert.UserID)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
s.logger.Error("查找已认证企业信息失败", zap.Error(err))
|
|
|
|
|
|
return fmt.Errorf("查找已认证企业信息失败: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
// 5. 写入用户域
|
|
|
|
|
|
err = s.userAggregateService.CreateOrUpdateEnterpriseInfo(
|
|
|
|
|
|
txCtx,
|
|
|
|
|
|
record.UserID,
|
|
|
|
|
|
record.CompanyName,
|
|
|
|
|
|
record.UnifiedSocialCode,
|
|
|
|
|
|
record.LegalPersonName,
|
|
|
|
|
|
record.LegalPersonID,
|
|
|
|
|
|
record.LegalPersonPhone,
|
|
|
|
|
|
record.EnterpriseAddress,
|
|
|
|
|
|
)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
s.logger.Error("同步企业信息到用户域失败", zap.Error(err))
|
|
|
|
|
|
return fmt.Errorf("同步企业信息到用户域失败: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 生成合同
|
2025-07-30 00:51:22 +08:00
|
|
|
|
err = s.generateAndAddContractFile(txCtx, cert, record.CompanyName, record.LegalPersonName, record.UnifiedSocialCode, record.EnterpriseAddress, record.LegalPersonPhone, record.LegalPersonID)
|
2025-07-28 01:46:39 +08:00
|
|
|
|
if err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 3. 保存认证信息
|
|
|
|
|
|
err = s.aggregateService.SaveCertification(txCtx, cert)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return fmt.Errorf("保存认证信息失败: %s", err.Error())
|
|
|
|
|
|
}
|
|
|
|
|
|
s.logger.Info("完成企业认证", zap.String("certification_id", cert.ID))
|
|
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
|
})
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
s.logger.Error("完成企业认证失败", zap.Error(err))
|
|
|
|
|
|
return fmt.Errorf("完成企业认证失败: %w", err)
|
|
|
|
|
|
}
|
2025-07-13 16:36:20 +08:00
|
|
|
|
}
|
2025-07-28 01:46:39 +08:00
|
|
|
|
return nil
|
2025-07-21 15:13:26 +08:00
|
|
|
|
|
2025-07-28 01:46:39 +08:00
|
|
|
|
default:
|
|
|
|
|
|
s.logger.Info("忽略未知的回调动作", zap.String("action", cmd.Data.Action))
|
|
|
|
|
|
return nil
|
2025-07-21 15:13:26 +08:00
|
|
|
|
}
|
2025-07-13 16:36:20 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-27 16:26:48 +08:00
|
|
|
|
// ================ 管理员后台操作用例 ================
|
|
|
|
|
|
|
|
|
|
|
|
// AdminCompleteCertificationWithoutContract 管理员代用户完成认证(暂不关联合同)
|
|
|
|
|
|
func (s *CertificationApplicationServiceImpl) AdminCompleteCertificationWithoutContract(
|
|
|
|
|
|
ctx context.Context,
|
|
|
|
|
|
cmd *commands.AdminCompleteCertificationCommand,
|
|
|
|
|
|
) (*responses.CertificationResponse, error) {
|
|
|
|
|
|
s.logger.Info("管理员代用户完成认证(不关联合同)",
|
|
|
|
|
|
zap.String("admin_id", cmd.AdminID),
|
|
|
|
|
|
zap.String("user_id", cmd.UserID),
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
// 1. 基础参数及企业信息校验
|
|
|
|
|
|
enterpriseInfo := &certification_value_objects.EnterpriseInfo{
|
|
|
|
|
|
CompanyName: cmd.CompanyName,
|
|
|
|
|
|
UnifiedSocialCode: cmd.UnifiedSocialCode,
|
|
|
|
|
|
LegalPersonName: cmd.LegalPersonName,
|
|
|
|
|
|
LegalPersonID: cmd.LegalPersonID,
|
|
|
|
|
|
LegalPersonPhone: cmd.LegalPersonPhone,
|
|
|
|
|
|
EnterpriseAddress: cmd.EnterpriseAddress,
|
|
|
|
|
|
}
|
|
|
|
|
|
if err := enterpriseInfo.Validate(); err != nil {
|
|
|
|
|
|
return nil, fmt.Errorf("企业信息验证失败: %s", err.Error())
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 检查统一社会信用代码唯一性(排除当前用户)
|
|
|
|
|
|
exists, err := s.userAggregateService.CheckUnifiedSocialCodeExists(ctx, cmd.UnifiedSocialCode, cmd.UserID)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return nil, fmt.Errorf("检查企业信息失败: %s", err.Error())
|
|
|
|
|
|
}
|
|
|
|
|
|
if exists {
|
|
|
|
|
|
return nil, fmt.Errorf("统一社会信用代码已被其他用户使用")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var cert *entities.Certification
|
|
|
|
|
|
|
|
|
|
|
|
// 2. 事务内:创建/加载认证、写入企业信息、直接完成认证、创建钱包和API用户
|
|
|
|
|
|
err = s.txManager.ExecuteInTx(ctx, func(txCtx context.Context) error {
|
|
|
|
|
|
// 2.1 检查并创建认证记录
|
|
|
|
|
|
existsCert, err := s.aggregateService.ExistsByUserID(txCtx, cmd.UserID)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return fmt.Errorf("检查用户认证是否存在失败: %s", err.Error())
|
|
|
|
|
|
}
|
|
|
|
|
|
if !existsCert {
|
|
|
|
|
|
cert, err = s.aggregateService.CreateCertification(txCtx, cmd.UserID)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return fmt.Errorf("创建认证信息失败: %s", err.Error())
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
cert, err = s.aggregateService.LoadCertificationByUserID(txCtx, cmd.UserID)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return fmt.Errorf("加载认证信息失败: %s", err.Error())
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 2.2 写入/覆盖用户域企业信息
|
|
|
|
|
|
if err := s.userAggregateService.CreateOrUpdateEnterpriseInfo(
|
|
|
|
|
|
txCtx,
|
|
|
|
|
|
cmd.UserID,
|
|
|
|
|
|
cmd.CompanyName,
|
|
|
|
|
|
cmd.UnifiedSocialCode,
|
|
|
|
|
|
cmd.LegalPersonName,
|
|
|
|
|
|
cmd.LegalPersonID,
|
|
|
|
|
|
cmd.LegalPersonPhone,
|
|
|
|
|
|
cmd.EnterpriseAddress,
|
|
|
|
|
|
); err != nil {
|
|
|
|
|
|
return fmt.Errorf("保存企业信息失败: %s", err.Error())
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 2.3 直接将认证状态设置为完成(管理员操作,暂不校验合同信息)
|
|
|
|
|
|
if err := cert.TransitionTo(
|
|
|
|
|
|
enums.StatusCompleted,
|
|
|
|
|
|
enums.ActorTypeAdmin,
|
|
|
|
|
|
cmd.AdminID,
|
|
|
|
|
|
fmt.Sprintf("管理员代用户完成认证:%s", cmd.Reason),
|
|
|
|
|
|
); err != nil {
|
|
|
|
|
|
return fmt.Errorf("更新认证状态失败: %s", err.Error())
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 2.4 基础激活:创建钱包、API用户并在用户域标记完成认证
|
|
|
|
|
|
if err := s.completeUserActivationWithoutContract(txCtx, cert); err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 2.5 保存认证信息
|
|
|
|
|
|
if err := s.aggregateService.SaveCertification(txCtx, cert); err != nil {
|
|
|
|
|
|
return fmt.Errorf("保存认证信息失败: %s", err.Error())
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
|
})
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return nil, err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
response := s.convertToResponse(cert)
|
|
|
|
|
|
s.logger.Info("管理员代用户完成认证成功(不关联合同)",
|
|
|
|
|
|
zap.String("admin_id", cmd.AdminID),
|
|
|
|
|
|
zap.String("user_id", cmd.UserID),
|
|
|
|
|
|
zap.String("certification_id", cert.ID),
|
|
|
|
|
|
)
|
|
|
|
|
|
return response, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-17 17:18:54 +08:00
|
|
|
|
// AdminListSubmitRecords 管理端分页查询企业信息提交记录
|
|
|
|
|
|
func (s *CertificationApplicationServiceImpl) AdminListSubmitRecords(
|
|
|
|
|
|
ctx context.Context,
|
|
|
|
|
|
query *queries.AdminListSubmitRecordsQuery,
|
|
|
|
|
|
) (*responses.AdminSubmitRecordsListResponse, error) {
|
|
|
|
|
|
if query.PageSize <= 0 {
|
|
|
|
|
|
query.PageSize = 10
|
|
|
|
|
|
}
|
|
|
|
|
|
if query.Page <= 0 {
|
|
|
|
|
|
query.Page = 1
|
|
|
|
|
|
}
|
|
|
|
|
|
filter := repositories.ListSubmitRecordsFilter{
|
2026-03-19 13:23:48 +08:00
|
|
|
|
Page: query.Page,
|
|
|
|
|
|
PageSize: query.PageSize,
|
|
|
|
|
|
CertificationStatus: query.CertificationStatus,
|
|
|
|
|
|
CompanyName: query.CompanyName,
|
|
|
|
|
|
LegalPersonPhone: query.LegalPersonPhone,
|
|
|
|
|
|
LegalPersonName: query.LegalPersonName,
|
2026-03-17 17:18:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
result, err := s.enterpriseInfoSubmitRecordRepo.List(ctx, filter)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return nil, fmt.Errorf("查询提交记录失败: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
items := make([]*responses.AdminSubmitRecordItem, 0, len(result.Records))
|
|
|
|
|
|
for _, r := range result.Records {
|
|
|
|
|
|
certStatus := ""
|
|
|
|
|
|
if cert, err := s.aggregateService.LoadCertificationByUserID(ctx, r.UserID); err == nil && cert != nil {
|
|
|
|
|
|
certStatus = string(cert.Status)
|
|
|
|
|
|
}
|
|
|
|
|
|
items = append(items, &responses.AdminSubmitRecordItem{
|
|
|
|
|
|
ID: r.ID,
|
|
|
|
|
|
UserID: r.UserID,
|
|
|
|
|
|
CompanyName: r.CompanyName,
|
|
|
|
|
|
UnifiedSocialCode: r.UnifiedSocialCode,
|
|
|
|
|
|
LegalPersonName: r.LegalPersonName,
|
|
|
|
|
|
SubmitAt: r.SubmitAt,
|
|
|
|
|
|
Status: r.Status,
|
|
|
|
|
|
CertificationStatus: certStatus,
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
totalPages := int((result.Total + int64(query.PageSize) - 1) / int64(query.PageSize))
|
|
|
|
|
|
if totalPages == 0 {
|
|
|
|
|
|
totalPages = 1
|
|
|
|
|
|
}
|
|
|
|
|
|
return &responses.AdminSubmitRecordsListResponse{
|
|
|
|
|
|
Items: items,
|
|
|
|
|
|
Total: result.Total,
|
|
|
|
|
|
Page: query.Page,
|
|
|
|
|
|
PageSize: query.PageSize,
|
|
|
|
|
|
TotalPages: totalPages,
|
|
|
|
|
|
}, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// AdminGetSubmitRecordByID 管理端获取单条提交记录详情
|
|
|
|
|
|
func (s *CertificationApplicationServiceImpl) AdminGetSubmitRecordByID(ctx context.Context, recordID string) (*responses.AdminSubmitRecordDetail, error) {
|
|
|
|
|
|
record, err := s.enterpriseInfoSubmitRecordRepo.FindByID(ctx, recordID)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return nil, fmt.Errorf("获取提交记录失败: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
certStatus := ""
|
|
|
|
|
|
if cert, loadErr := s.aggregateService.LoadCertificationByUserID(ctx, record.UserID); loadErr == nil && cert != nil {
|
|
|
|
|
|
certStatus = string(cert.Status)
|
|
|
|
|
|
}
|
|
|
|
|
|
return &responses.AdminSubmitRecordDetail{
|
|
|
|
|
|
ID: record.ID,
|
|
|
|
|
|
UserID: record.UserID,
|
|
|
|
|
|
CompanyName: record.CompanyName,
|
|
|
|
|
|
UnifiedSocialCode: record.UnifiedSocialCode,
|
|
|
|
|
|
LegalPersonName: record.LegalPersonName,
|
|
|
|
|
|
LegalPersonID: record.LegalPersonID,
|
|
|
|
|
|
LegalPersonPhone: record.LegalPersonPhone,
|
|
|
|
|
|
EnterpriseAddress: record.EnterpriseAddress,
|
|
|
|
|
|
AuthorizedRepName: record.AuthorizedRepName,
|
|
|
|
|
|
AuthorizedRepID: record.AuthorizedRepID,
|
|
|
|
|
|
AuthorizedRepPhone: record.AuthorizedRepPhone,
|
|
|
|
|
|
AuthorizedRepIDImageURLs: record.AuthorizedRepIDImageURLs,
|
|
|
|
|
|
BusinessLicenseImageURL: record.BusinessLicenseImageURL,
|
|
|
|
|
|
OfficePlaceImageURLs: record.OfficePlaceImageURLs,
|
|
|
|
|
|
APIUsage: record.APIUsage,
|
|
|
|
|
|
ScenarioAttachmentURLs: record.ScenarioAttachmentURLs,
|
|
|
|
|
|
Status: record.Status,
|
|
|
|
|
|
SubmitAt: record.SubmitAt,
|
|
|
|
|
|
VerifiedAt: record.VerifiedAt,
|
|
|
|
|
|
FailedAt: record.FailedAt,
|
|
|
|
|
|
FailureReason: record.FailureReason,
|
|
|
|
|
|
CertificationStatus: certStatus,
|
|
|
|
|
|
CreatedAt: record.CreatedAt,
|
|
|
|
|
|
UpdatedAt: record.UpdatedAt,
|
|
|
|
|
|
}, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// AdminApproveSubmitRecord 管理端审核通过
|
|
|
|
|
|
func (s *CertificationApplicationServiceImpl) AdminApproveSubmitRecord(ctx context.Context, recordID, adminID, remark string) error {
|
|
|
|
|
|
record, err := s.enterpriseInfoSubmitRecordRepo.FindByID(ctx, recordID)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return fmt.Errorf("获取提交记录失败: %w", err)
|
|
|
|
|
|
}
|
2026-04-02 14:01:19 +08:00
|
|
|
|
if record.Status != "verified" {
|
|
|
|
|
|
return fmt.Errorf("该条提交记录未通过前置校验或已失败,无法审核通过")
|
|
|
|
|
|
}
|
2026-03-17 17:18:54 +08:00
|
|
|
|
cert, err := s.aggregateService.LoadCertificationByUserID(ctx, record.UserID)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return fmt.Errorf("加载认证信息失败: %w", err)
|
|
|
|
|
|
}
|
2026-03-18 16:05:01 +08:00
|
|
|
|
|
2026-03-19 13:23:48 +08:00
|
|
|
|
// 幂等:认证已进入「已提交企业信息」或更后续状态,说明已通过审核,无需重复操作
|
2026-03-18 16:05:01 +08:00
|
|
|
|
switch cert.Status {
|
|
|
|
|
|
case enums.StatusInfoSubmitted,
|
|
|
|
|
|
enums.StatusEnterpriseVerified,
|
|
|
|
|
|
enums.StatusContractApplied,
|
|
|
|
|
|
enums.StatusContractSigned,
|
|
|
|
|
|
enums.StatusCompleted,
|
|
|
|
|
|
enums.StatusContractRejected,
|
|
|
|
|
|
enums.StatusContractExpired:
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-17 17:18:54 +08:00
|
|
|
|
if cert.Status != enums.StatusInfoPendingReview {
|
2026-03-19 13:23:48 +08:00
|
|
|
|
return fmt.Errorf("认证状态不是待审核,当前: %s", enums.GetStatusName(cert.Status))
|
2026-03-17 17:18:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
enterpriseInfo := &certification_value_objects.EnterpriseInfo{
|
|
|
|
|
|
CompanyName: record.CompanyName,
|
|
|
|
|
|
UnifiedSocialCode: record.UnifiedSocialCode,
|
|
|
|
|
|
LegalPersonName: record.LegalPersonName,
|
|
|
|
|
|
LegalPersonID: record.LegalPersonID,
|
|
|
|
|
|
LegalPersonPhone: record.LegalPersonPhone,
|
|
|
|
|
|
EnterpriseAddress: record.EnterpriseAddress,
|
|
|
|
|
|
}
|
2026-03-26 15:31:37 +08:00
|
|
|
|
authReq := &esign.EnterpriseAuthRequest{
|
2026-03-17 17:18:54 +08:00
|
|
|
|
CompanyName: enterpriseInfo.CompanyName,
|
|
|
|
|
|
UnifiedSocialCode: enterpriseInfo.UnifiedSocialCode,
|
|
|
|
|
|
LegalPersonName: enterpriseInfo.LegalPersonName,
|
|
|
|
|
|
LegalPersonID: enterpriseInfo.LegalPersonID,
|
|
|
|
|
|
TransactorName: enterpriseInfo.LegalPersonName,
|
|
|
|
|
|
TransactorMobile: enterpriseInfo.LegalPersonPhone,
|
|
|
|
|
|
TransactorID: enterpriseInfo.LegalPersonID,
|
2026-03-26 15:31:37 +08:00
|
|
|
|
}
|
|
|
|
|
|
authURL, alreadyVerified, err := s.generateEnterpriseAuthOrDetectVerified(ctx, authReq)
|
2026-03-17 17:18:54 +08:00
|
|
|
|
if err != nil {
|
|
|
|
|
|
return fmt.Errorf("生成企业认证链接失败: %w", err)
|
|
|
|
|
|
}
|
2026-03-26 15:31:37 +08:00
|
|
|
|
if alreadyVerified {
|
|
|
|
|
|
if err := cert.ApproveEnterpriseInfoReview("", "", adminID); err != nil {
|
|
|
|
|
|
return fmt.Errorf("更新认证状态失败: %w", err)
|
|
|
|
|
|
}
|
2026-04-02 12:46:42 +08:00
|
|
|
|
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))
|
|
|
|
|
|
return nil
|
2026-03-26 15:31:37 +08:00
|
|
|
|
}
|
2026-03-17 17:18:54 +08:00
|
|
|
|
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)
|
|
|
|
|
|
}
|
2026-04-02 12:46:42 +08:00
|
|
|
|
record.MarkManualApproved(adminID, remark)
|
|
|
|
|
|
if err := s.enterpriseInfoSubmitRecordService.Save(ctx, record); err != nil {
|
|
|
|
|
|
return fmt.Errorf("保存企业信息提交记录失败: %w", err)
|
|
|
|
|
|
}
|
2026-03-17 17:18:54 +08:00
|
|
|
|
s.logger.Info("管理员审核通过企业信息", zap.String("record_id", recordID), zap.String("admin_id", adminID))
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// AdminRejectSubmitRecord 管理端审核拒绝
|
|
|
|
|
|
func (s *CertificationApplicationServiceImpl) AdminRejectSubmitRecord(ctx context.Context, recordID, adminID, remark string) error {
|
|
|
|
|
|
if remark == "" {
|
|
|
|
|
|
return fmt.Errorf("拒绝时必须填写审核备注")
|
|
|
|
|
|
}
|
|
|
|
|
|
record, err := s.enterpriseInfoSubmitRecordRepo.FindByID(ctx, recordID)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return fmt.Errorf("获取提交记录失败: %w", err)
|
|
|
|
|
|
}
|
2026-04-02 14:01:19 +08:00
|
|
|
|
if record.Status != "verified" {
|
|
|
|
|
|
return fmt.Errorf("该条提交记录未通过前置校验或已失败,无法从后台拒绝(请查看历史失败原因)")
|
|
|
|
|
|
}
|
2026-03-17 17:18:54 +08:00
|
|
|
|
cert, err := s.aggregateService.LoadCertificationByUserID(ctx, record.UserID)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return fmt.Errorf("加载认证信息失败: %w", err)
|
|
|
|
|
|
}
|
2026-03-19 13:23:48 +08:00
|
|
|
|
|
|
|
|
|
|
// 幂等:认证已处于拒绝或后续状态,无需重复拒绝
|
|
|
|
|
|
switch cert.Status {
|
|
|
|
|
|
case enums.StatusInfoRejected,
|
|
|
|
|
|
enums.StatusEnterpriseVerified,
|
|
|
|
|
|
enums.StatusContractApplied,
|
|
|
|
|
|
enums.StatusContractSigned,
|
|
|
|
|
|
enums.StatusCompleted,
|
|
|
|
|
|
enums.StatusContractRejected,
|
|
|
|
|
|
enums.StatusContractExpired:
|
|
|
|
|
|
return nil
|
2026-03-17 17:18:54 +08:00
|
|
|
|
}
|
2026-03-19 13:23:48 +08:00
|
|
|
|
if cert.Status != enums.StatusInfoPendingReview {
|
|
|
|
|
|
return fmt.Errorf("认证状态不是待审核,当前: %s", enums.GetStatusName(cert.Status))
|
2026-03-17 17:18:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
if err := cert.RejectEnterpriseInfoReview(adminID, remark); err != nil {
|
|
|
|
|
|
return fmt.Errorf("更新认证状态失败: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
if err := s.aggregateService.SaveCertification(ctx, cert); err != nil {
|
|
|
|
|
|
return fmt.Errorf("保存认证信息失败: %w", err)
|
|
|
|
|
|
}
|
2026-04-02 12:46:42 +08:00
|
|
|
|
record.MarkManualRejected(adminID, remark)
|
|
|
|
|
|
if err := s.enterpriseInfoSubmitRecordService.Save(ctx, record); err != nil {
|
|
|
|
|
|
return fmt.Errorf("保存企业信息提交记录失败: %w", err)
|
|
|
|
|
|
}
|
2026-03-17 17:18:54 +08:00
|
|
|
|
s.logger.Info("管理员审核拒绝企业信息", zap.String("record_id", recordID), zap.String("admin_id", adminID))
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-19 13:23:48 +08:00
|
|
|
|
// AdminTransitionCertificationStatus 管理端按用户变更认证状态(以状态机为准)
|
|
|
|
|
|
func (s *CertificationApplicationServiceImpl) AdminTransitionCertificationStatus(ctx context.Context, cmd *commands.AdminTransitionCertificationStatusCommand) error {
|
|
|
|
|
|
cert, err := s.aggregateService.LoadCertificationByUserID(ctx, cmd.UserID)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return fmt.Errorf("加载认证信息失败: %w", err)
|
2026-03-18 11:44:37 +08:00
|
|
|
|
}
|
2026-03-19 13:23:48 +08:00
|
|
|
|
record, err := s.enterpriseInfoSubmitRecordRepo.FindLatestByUserID(ctx, cmd.UserID)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return fmt.Errorf("查找企业信息提交记录失败: %w", err)
|
2026-03-18 11:44:37 +08:00
|
|
|
|
}
|
2026-03-19 13:23:48 +08:00
|
|
|
|
if record == nil {
|
|
|
|
|
|
return fmt.Errorf("未找到该用户的企业信息提交记录")
|
2026-03-18 11:44:37 +08:00
|
|
|
|
}
|
2026-03-19 13:23:48 +08:00
|
|
|
|
switch cmd.TargetStatus {
|
|
|
|
|
|
case string(enums.StatusInfoSubmitted):
|
|
|
|
|
|
// 审核通过:与 AdminApproveSubmitRecord 一致,推状态并生成企业认证链接
|
|
|
|
|
|
switch cert.Status {
|
|
|
|
|
|
case enums.StatusInfoSubmitted, enums.StatusEnterpriseVerified, enums.StatusContractApplied,
|
|
|
|
|
|
enums.StatusContractSigned, enums.StatusCompleted, enums.StatusContractRejected, enums.StatusContractExpired:
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
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,
|
|
|
|
|
|
}
|
2026-03-26 15:31:37 +08:00
|
|
|
|
authReq := &esign.EnterpriseAuthRequest{
|
2026-03-19 13:23:48 +08:00
|
|
|
|
CompanyName: enterpriseInfo.CompanyName, UnifiedSocialCode: enterpriseInfo.UnifiedSocialCode,
|
|
|
|
|
|
LegalPersonName: enterpriseInfo.LegalPersonName, LegalPersonID: enterpriseInfo.LegalPersonID,
|
|
|
|
|
|
TransactorName: enterpriseInfo.LegalPersonName, TransactorMobile: enterpriseInfo.LegalPersonPhone, TransactorID: enterpriseInfo.LegalPersonID,
|
2026-03-26 15:31:37 +08:00
|
|
|
|
}
|
|
|
|
|
|
authURL, alreadyVerified, err := s.generateEnterpriseAuthOrDetectVerified(ctx, authReq)
|
2026-03-19 13:23:48 +08:00
|
|
|
|
if err != nil {
|
|
|
|
|
|
return fmt.Errorf("生成企业认证链接失败: %w", err)
|
|
|
|
|
|
}
|
2026-03-26 15:31:37 +08:00
|
|
|
|
if alreadyVerified {
|
|
|
|
|
|
if err := cert.ApproveEnterpriseInfoReview("", "", cmd.AdminID); err != nil {
|
|
|
|
|
|
return fmt.Errorf("更新认证状态失败: %w", err)
|
|
|
|
|
|
}
|
2026-04-02 12:46:42 +08:00
|
|
|
|
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
|
2026-03-26 15:31:37 +08:00
|
|
|
|
}
|
2026-03-19 13:23:48 +08:00
|
|
|
|
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)
|
|
|
|
|
|
}
|
2026-04-02 12:46:42 +08:00
|
|
|
|
record.MarkManualApproved(cmd.AdminID, cmd.Remark)
|
|
|
|
|
|
if err := s.enterpriseInfoSubmitRecordService.Save(ctx, record); err != nil {
|
|
|
|
|
|
return fmt.Errorf("保存企业信息提交记录失败: %w", err)
|
|
|
|
|
|
}
|
2026-03-19 13:23:48 +08:00
|
|
|
|
s.logger.Info("管理端变更认证状态为通过", zap.String("user_id", cmd.UserID), zap.String("admin_id", cmd.AdminID))
|
|
|
|
|
|
return nil
|
|
|
|
|
|
case string(enums.StatusInfoRejected):
|
|
|
|
|
|
// 审核拒绝
|
|
|
|
|
|
if cert.Status == enums.StatusInfoRejected || cert.Status == enums.StatusEnterpriseVerified ||
|
|
|
|
|
|
cert.Status == enums.StatusContractApplied || cert.Status == enums.StatusContractSigned || cert.Status == enums.StatusCompleted {
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
if cert.Status != enums.StatusInfoPendingReview {
|
|
|
|
|
|
return fmt.Errorf("认证状态不是待审核,当前: %s", enums.GetStatusName(cert.Status))
|
|
|
|
|
|
}
|
|
|
|
|
|
if err := cert.RejectEnterpriseInfoReview(cmd.AdminID, cmd.Remark); err != nil {
|
|
|
|
|
|
return fmt.Errorf("更新认证状态失败: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
if err := s.aggregateService.SaveCertification(ctx, cert); err != nil {
|
|
|
|
|
|
return fmt.Errorf("保存认证信息失败: %w", err)
|
|
|
|
|
|
}
|
2026-04-02 12:46:42 +08:00
|
|
|
|
record.MarkManualRejected(cmd.AdminID, cmd.Remark)
|
|
|
|
|
|
if err := s.enterpriseInfoSubmitRecordService.Save(ctx, record); err != nil {
|
|
|
|
|
|
return fmt.Errorf("保存企业信息提交记录失败: %w", err)
|
|
|
|
|
|
}
|
2026-03-19 13:23:48 +08:00
|
|
|
|
s.logger.Info("管理端变更认证状态为拒绝", zap.String("user_id", cmd.UserID), zap.String("admin_id", cmd.AdminID))
|
|
|
|
|
|
return nil
|
|
|
|
|
|
default:
|
|
|
|
|
|
return fmt.Errorf("不支持的目标状态: %s", cmd.TargetStatus)
|
2026-03-18 11:44:37 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-19 13:23:48 +08:00
|
|
|
|
// ================ 辅助方法 ================
|
|
|
|
|
|
|
2025-07-21 15:13:26 +08:00
|
|
|
|
// convertToResponse 转换实体为响应DTO
|
|
|
|
|
|
func (s *CertificationApplicationServiceImpl) convertToResponse(cert *entities.Certification) *responses.CertificationResponse {
|
|
|
|
|
|
response := &responses.CertificationResponse{
|
|
|
|
|
|
ID: cert.ID,
|
|
|
|
|
|
UserID: cert.UserID,
|
|
|
|
|
|
Status: cert.Status,
|
|
|
|
|
|
StatusName: enums.GetStatusName(cert.Status),
|
|
|
|
|
|
Progress: cert.GetProgress(),
|
|
|
|
|
|
CreatedAt: cert.CreatedAt,
|
|
|
|
|
|
UpdatedAt: cert.UpdatedAt,
|
|
|
|
|
|
InfoSubmittedAt: cert.InfoSubmittedAt,
|
|
|
|
|
|
EnterpriseVerifiedAt: cert.EnterpriseVerifiedAt,
|
|
|
|
|
|
ContractAppliedAt: cert.ContractAppliedAt,
|
|
|
|
|
|
ContractSignedAt: cert.ContractSignedAt,
|
2025-07-28 01:46:39 +08:00
|
|
|
|
CompletedAt: cert.CompletedAt,
|
2025-07-21 15:13:26 +08:00
|
|
|
|
IsCompleted: cert.IsCompleted(),
|
|
|
|
|
|
IsFailed: enums.IsFailureStatus(cert.Status),
|
|
|
|
|
|
IsUserActionRequired: cert.IsUserActionRequired(),
|
|
|
|
|
|
NextAction: enums.GetUserActionHint(cert.Status),
|
|
|
|
|
|
AvailableActions: cert.GetAvailableActions(),
|
|
|
|
|
|
RetryCount: cert.RetryCount,
|
|
|
|
|
|
Metadata: make(map[string]interface{}),
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 设置企业信息(从认证实体中构建)
|
|
|
|
|
|
// TODO: 这里需要从企业信息服务或其他地方获取完整的企业信息
|
|
|
|
|
|
// response.EnterpriseInfo = cert.EnterpriseInfo
|
|
|
|
|
|
|
|
|
|
|
|
// 设置合同信息(从认证实体中构建)
|
|
|
|
|
|
if cert.ContractFileID != "" || cert.EsignFlowID != "" {
|
|
|
|
|
|
// TODO: 从认证实体字段构建合同信息值对象
|
|
|
|
|
|
// response.ContractInfo = &value_objects.ContractInfo{...}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 设置失败信息
|
|
|
|
|
|
if enums.IsFailureStatus(cert.Status) {
|
|
|
|
|
|
response.FailureReason = cert.FailureReason
|
|
|
|
|
|
response.FailureReasonName = enums.GetFailureReasonName(cert.FailureReason)
|
|
|
|
|
|
response.FailureMessage = cert.FailureMessage
|
|
|
|
|
|
response.CanRetry = enums.IsRetryable(cert.FailureReason)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return response
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-26 15:31:37 +08:00
|
|
|
|
func (s *CertificationApplicationServiceImpl) generateEnterpriseAuthOrDetectVerified(
|
|
|
|
|
|
ctx context.Context,
|
|
|
|
|
|
req *esign.EnterpriseAuthRequest,
|
|
|
|
|
|
) (*esign.EnterpriseAuthResult, bool, error) {
|
2026-03-26 16:08:48 +08:00
|
|
|
|
s.logger.Info("企业认证链接生成-步骤1-开始调用三方创建认证链接",
|
|
|
|
|
|
zap.String("company_name", req.CompanyName),
|
|
|
|
|
|
zap.String("unified_social_code", req.UnifiedSocialCode))
|
2026-03-26 15:31:37 +08:00
|
|
|
|
authURL, err := s.esignClient.GenerateEnterpriseAuth(req)
|
|
|
|
|
|
if err == nil {
|
2026-03-26 16:08:48 +08:00
|
|
|
|
s.logger.Info("企业认证链接生成-步骤1-创建成功",
|
|
|
|
|
|
zap.String("company_name", req.CompanyName),
|
|
|
|
|
|
zap.String("auth_flow_id", authURL.AuthFlowID))
|
2026-03-26 15:31:37 +08:00
|
|
|
|
return authURL, false, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
if !isEnterpriseAlreadyRealnamedErr(err) {
|
2026-03-26 16:08:48 +08:00
|
|
|
|
s.logger.Error("企业认证链接生成-步骤1-创建失败且非已实名场景",
|
|
|
|
|
|
zap.String("company_name", req.CompanyName),
|
|
|
|
|
|
zap.Error(err))
|
2026-03-26 15:31:37 +08:00
|
|
|
|
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 {
|
2026-03-26 16:08:48 +08:00
|
|
|
|
s.logger.Warn("企业认证链接生成-步骤2-按信用代码查询实名状态失败,回退按企业名查询",
|
|
|
|
|
|
zap.String("company_name", req.CompanyName),
|
|
|
|
|
|
zap.Error(identityErr))
|
2026-03-26 15:31:37 +08:00
|
|
|
|
identity, identityErr = s.esignClient.QueryOrgIdentityInfo(&esign.QueryOrgIdentityRequest{
|
|
|
|
|
|
OrgName: req.CompanyName,
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
if identityErr != nil {
|
|
|
|
|
|
return nil, false, fmt.Errorf("企业用户已实名,但查询实名状态失败: %w", identityErr)
|
|
|
|
|
|
}
|
2026-03-26 16:08:48 +08:00
|
|
|
|
s.logger.Info("企业认证链接生成-步骤2-实名状态查询成功",
|
|
|
|
|
|
zap.String("company_name", req.CompanyName),
|
|
|
|
|
|
zap.Int32("realname_status", identity.Data.RealnameStatus))
|
2026-03-26 15:31:37 +08:00
|
|
|
|
if identity == nil || identity.Data.RealnameStatus != 1 {
|
|
|
|
|
|
return nil, false, err
|
|
|
|
|
|
}
|
2026-03-26 16:08:48 +08:00
|
|
|
|
s.logger.Info("企业认证链接生成-步骤3-确认企业已实名,返回自动确认标记",
|
|
|
|
|
|
zap.String("company_name", req.CompanyName))
|
2026-03-26 15:31:37 +08:00
|
|
|
|
return nil, true, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func isEnterpriseAlreadyRealnamedErr(err error) bool {
|
|
|
|
|
|
if err == nil {
|
|
|
|
|
|
return false
|
|
|
|
|
|
}
|
|
|
|
|
|
msg := err.Error()
|
|
|
|
|
|
return strings.Contains(msg, "企业用户已实名") || strings.Contains(msg, "已实名")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-07-28 01:46:39 +08:00
|
|
|
|
// validateApplyContractCommand 验证申请合同命令
|
|
|
|
|
|
func (s *CertificationApplicationServiceImpl) validateApplyContractCommand(cmd *commands.ApplyContractCommand) error {
|
|
|
|
|
|
if cmd.UserID == "" {
|
|
|
|
|
|
return fmt.Errorf("用户ID不能为空")
|
|
|
|
|
|
}
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// validateContractApplicationPreconditions 验证合同申请前置条件
|
|
|
|
|
|
func (s *CertificationApplicationServiceImpl) validateContractApplicationPreconditions(cert *entities.Certification, userID string) error {
|
|
|
|
|
|
if cert.UserID != userID {
|
|
|
|
|
|
return fmt.Errorf("用户无权限操作此认证申请")
|
|
|
|
|
|
}
|
|
|
|
|
|
if cert.Status != enums.StatusEnterpriseVerified {
|
|
|
|
|
|
return fmt.Errorf("必须先完成企业认证才能申请合同")
|
|
|
|
|
|
}
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
2025-07-21 15:13:26 +08:00
|
|
|
|
|
2025-07-28 01:46:39 +08:00
|
|
|
|
// 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,
|
|
|
|
|
|
})
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return nil, fmt.Errorf("生成合同失败: %s", err.Error())
|
2025-07-21 15:13:26 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-07-28 01:46:39 +08:00
|
|
|
|
_, shortUrl, err := s.esignClient.GetSignURL(signFlowID, enterpriseInfo.LegalPersonPhone, enterpriseInfo.CompanyName)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return nil, fmt.Errorf("获取签署链接失败: %s", err.Error())
|
2025-07-21 15:13:26 +08:00
|
|
|
|
}
|
2025-07-28 01:46:39 +08:00
|
|
|
|
return &certification_value_objects.ContractInfo{
|
|
|
|
|
|
ContractFileID: cert.ContractFileID,
|
|
|
|
|
|
EsignFlowID: signFlowID,
|
|
|
|
|
|
ContractSignURL: shortUrl,
|
|
|
|
|
|
}, nil
|
|
|
|
|
|
}
|
2025-07-21 15:13:26 +08:00
|
|
|
|
|
2025-07-28 01:46:39 +08:00
|
|
|
|
// ================ 重构后的公共方法 ================
|
|
|
|
|
|
|
|
|
|
|
|
// completeEnterpriseVerification 完成企业认证的公共方法
|
|
|
|
|
|
func (s *CertificationApplicationServiceImpl) completeEnterpriseVerification(
|
|
|
|
|
|
ctx context.Context,
|
|
|
|
|
|
cert *entities.Certification,
|
|
|
|
|
|
userID string,
|
|
|
|
|
|
companyName string,
|
|
|
|
|
|
legalPersonName string,
|
|
|
|
|
|
) error {
|
2026-03-26 16:08:48 +08:00
|
|
|
|
s.logger.Info("完成企业认证-步骤1-开始状态流转",
|
|
|
|
|
|
zap.String("user_id", userID),
|
|
|
|
|
|
zap.String("company_name", companyName))
|
2025-07-28 01:46:39 +08:00
|
|
|
|
// 完成企业认证
|
|
|
|
|
|
err := cert.CompleteEnterpriseVerification()
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
s.logger.Error("完成企业认证失败", zap.Error(err))
|
|
|
|
|
|
return fmt.Errorf("完成企业认证失败: %w", err)
|
2025-07-13 16:36:20 +08:00
|
|
|
|
}
|
2025-07-21 15:13:26 +08:00
|
|
|
|
|
2025-07-28 01:46:39 +08:00
|
|
|
|
// 保存企业信息到用户域
|
|
|
|
|
|
record, err := s.enterpriseInfoSubmitRecordRepo.FindLatestByUserID(ctx, userID)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
s.logger.Error("查找企业信息失败", zap.Error(err))
|
|
|
|
|
|
return fmt.Errorf("查找企业信息失败: %w", err)
|
|
|
|
|
|
}
|
2026-03-26 16:08:48 +08:00
|
|
|
|
s.logger.Info("完成企业认证-步骤2-获取提交记录成功",
|
|
|
|
|
|
zap.String("user_id", userID),
|
|
|
|
|
|
zap.String("record_id", record.ID))
|
2025-07-28 01:46:39 +08:00
|
|
|
|
|
|
|
|
|
|
err = s.userAggregateService.CreateEnterpriseInfo(
|
|
|
|
|
|
ctx,
|
|
|
|
|
|
userID,
|
|
|
|
|
|
record.CompanyName,
|
|
|
|
|
|
record.UnifiedSocialCode,
|
|
|
|
|
|
record.LegalPersonName,
|
|
|
|
|
|
record.LegalPersonID,
|
|
|
|
|
|
record.LegalPersonPhone,
|
|
|
|
|
|
record.EnterpriseAddress,
|
|
|
|
|
|
)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
s.logger.Error("保存企业信息到用户域失败", zap.Error(err))
|
|
|
|
|
|
return fmt.Errorf("保存企业信息失败: %s", err.Error())
|
|
|
|
|
|
} else {
|
|
|
|
|
|
s.logger.Info("企业信息已保存到用户域", zap.String("user_id", userID))
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 生成合同
|
2025-07-30 00:51:22 +08:00
|
|
|
|
err = s.generateAndAddContractFile(ctx, cert, record.CompanyName, record.LegalPersonName, record.UnifiedSocialCode, record.EnterpriseAddress, record.LegalPersonPhone, record.LegalPersonID)
|
2025-07-28 01:46:39 +08:00
|
|
|
|
if err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
2026-03-26 16:08:48 +08:00
|
|
|
|
s.logger.Info("完成企业认证-步骤3-合同文件生成并写入认证成功", zap.String("user_id", userID))
|
2025-07-28 01:46:39 +08:00
|
|
|
|
|
|
|
|
|
|
// 保存认证信息
|
|
|
|
|
|
err = s.aggregateService.SaveCertification(ctx, cert)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
s.logger.Error("保存认证信息失败", zap.Error(err))
|
|
|
|
|
|
return fmt.Errorf("保存认证信息失败: %w", err)
|
|
|
|
|
|
}
|
2026-03-26 16:08:48 +08:00
|
|
|
|
s.logger.Info("完成企业认证-步骤4-认证信息保存成功", zap.String("user_id", userID))
|
2025-07-28 01:46:39 +08:00
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// generateAndAddContractFile 生成并添加合同文件的公共方法
|
|
|
|
|
|
func (s *CertificationApplicationServiceImpl) generateAndAddContractFile(
|
|
|
|
|
|
ctx context.Context,
|
|
|
|
|
|
cert *entities.Certification,
|
|
|
|
|
|
companyName string,
|
|
|
|
|
|
legalPersonName string,
|
|
|
|
|
|
unifiedSocialCode string,
|
|
|
|
|
|
enterpriseAddress string,
|
|
|
|
|
|
legalPersonPhone string,
|
|
|
|
|
|
legalPersonID string,
|
|
|
|
|
|
) error {
|
2026-03-26 16:08:48 +08:00
|
|
|
|
s.logger.Info("合同生成-步骤1-开始填充合同模板",
|
|
|
|
|
|
zap.String("user_id", cert.UserID),
|
|
|
|
|
|
zap.String("company_name", companyName))
|
2025-07-28 01:46:39 +08:00
|
|
|
|
fileComponent := map[string]string{
|
|
|
|
|
|
"YFCompanyName": companyName,
|
|
|
|
|
|
"YFCompanyName2": companyName,
|
|
|
|
|
|
"YFLegalPersonName": legalPersonName,
|
|
|
|
|
|
"YFLegalPersonName2": legalPersonName,
|
|
|
|
|
|
"YFUnifiedSocialCode": unifiedSocialCode,
|
|
|
|
|
|
"YFEnterpriseAddress": enterpriseAddress,
|
|
|
|
|
|
"YFContactPerson": legalPersonName,
|
|
|
|
|
|
"YFMobile": legalPersonPhone,
|
|
|
|
|
|
"SignDate": time.Now().Format("2006年01月02日"),
|
|
|
|
|
|
"SignDate2": time.Now().Format("2006年01月02日"),
|
|
|
|
|
|
"SignDate3": time.Now().Format("2006年01月02日"),
|
|
|
|
|
|
}
|
|
|
|
|
|
fillTemplateResp, err := s.esignClient.FillTemplate(fileComponent)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
s.logger.Error("生成合同失败", zap.Error(err))
|
|
|
|
|
|
return fmt.Errorf("生成合同失败: %s", err.Error())
|
|
|
|
|
|
}
|
2026-03-26 16:08:48 +08:00
|
|
|
|
s.logger.Info("合同生成-步骤1-模板填充成功",
|
|
|
|
|
|
zap.String("user_id", cert.UserID),
|
|
|
|
|
|
zap.String("file_id", fillTemplateResp.FileID))
|
2025-07-28 01:46:39 +08:00
|
|
|
|
err = cert.AddContractFileID(fillTemplateResp.FileID, fillTemplateResp.FileDownloadUrl)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
s.logger.Error("加入合同文件ID链接失败", zap.Error(err))
|
|
|
|
|
|
return fmt.Errorf("加入合同文件ID链接失败: %s", err.Error())
|
|
|
|
|
|
}
|
2026-03-26 16:08:48 +08:00
|
|
|
|
s.logger.Info("合同生成-步骤2-合同文件写入认证实体成功",
|
|
|
|
|
|
zap.String("user_id", cert.UserID),
|
|
|
|
|
|
zap.String("file_id", fillTemplateResp.FileID))
|
2025-07-28 01:46:39 +08:00
|
|
|
|
return nil
|
2025-07-13 16:36:20 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-07-28 01:46:39 +08:00
|
|
|
|
// updateContractFile 更新合同文件的公共方法
|
|
|
|
|
|
func (s *CertificationApplicationServiceImpl) updateContractFile(ctx context.Context, cert *entities.Certification) error {
|
|
|
|
|
|
// 获取企业信息
|
|
|
|
|
|
enterpriseInfo, err := s.userAggregateService.GetUserWithEnterpriseInfo(ctx, cert.UserID)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
s.logger.Error("获取企业信息失败", zap.Error(err))
|
|
|
|
|
|
return fmt.Errorf("获取企业信息失败: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 生成合同
|
2025-07-30 00:51:22 +08:00
|
|
|
|
err = s.generateAndAddContractFile(ctx, cert, enterpriseInfo.EnterpriseInfo.CompanyName, enterpriseInfo.EnterpriseInfo.LegalPersonName, enterpriseInfo.EnterpriseInfo.UnifiedSocialCode, enterpriseInfo.EnterpriseInfo.EnterpriseAddress, enterpriseInfo.EnterpriseInfo.LegalPersonPhone, enterpriseInfo.EnterpriseInfo.LegalPersonID)
|
2025-07-28 01:46:39 +08:00
|
|
|
|
if err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 更新认证信息
|
|
|
|
|
|
err = s.aggregateService.SaveCertification(ctx, cert)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
s.logger.Error("保存认证信息失败", zap.Error(err))
|
|
|
|
|
|
return fmt.Errorf("保存认证信息失败: %w", err)
|
|
|
|
|
|
}
|
2025-07-21 15:13:26 +08:00
|
|
|
|
|
2025-07-28 01:46:39 +08:00
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// checkAndCompleteEnterpriseVerification 检查并完成企业认证的公共方法
|
|
|
|
|
|
func (s *CertificationApplicationServiceImpl) checkAndCompleteEnterpriseVerification(ctx context.Context, cert *entities.Certification) error {
|
|
|
|
|
|
record, err := s.enterpriseInfoSubmitRecordRepo.FindLatestByUserID(ctx, cert.UserID)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return fmt.Errorf("查找企业信息失败: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
identity, err := s.esignClient.QueryOrgIdentityInfo(&esign.QueryOrgIdentityRequest{
|
|
|
|
|
|
OrgName: record.CompanyName,
|
|
|
|
|
|
})
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
s.logger.Error("查询企业认证信息失败", zap.Error(err))
|
|
|
|
|
|
}
|
|
|
|
|
|
if identity != nil && identity.Data.RealnameStatus == 1 {
|
|
|
|
|
|
err = s.txManager.ExecuteInTx(ctx, func(txCtx context.Context) error {
|
|
|
|
|
|
return s.completeEnterpriseVerification(txCtx, cert, cert.UserID, record.CompanyName, record.LegalPersonName)
|
|
|
|
|
|
})
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return fmt.Errorf("完成企业认证失败: %w", err)
|
2025-07-20 20:53:26 +08:00
|
|
|
|
}
|
2025-07-28 01:46:39 +08:00
|
|
|
|
}
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
2025-07-21 15:13:26 +08:00
|
|
|
|
|
2025-07-28 01:46:39 +08:00
|
|
|
|
// checkAndUpdateSignStatus 检查并更新签署状态的公共方法
|
|
|
|
|
|
func (s *CertificationApplicationServiceImpl) checkAndUpdateSignStatus(ctx context.Context, cert *entities.Certification) (string, error) {
|
|
|
|
|
|
var reason string
|
|
|
|
|
|
err := s.txManager.ExecuteInTx(ctx, func(txCtx context.Context) error {
|
|
|
|
|
|
if cert.Status != enums.StatusContractApplied {
|
|
|
|
|
|
return fmt.Errorf("认证状态不正确")
|
2025-07-20 20:53:26 +08:00
|
|
|
|
}
|
2025-07-28 01:46:39 +08:00
|
|
|
|
detail, err := s.esignClient.QuerySignFlowDetail(cert.EsignFlowID)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return fmt.Errorf("查询签署流程详情失败: %s", err.Error())
|
2025-07-20 20:53:26 +08:00
|
|
|
|
}
|
2025-07-28 01:46:39 +08:00
|
|
|
|
if detail.Data.SignFlowStatus == 2 {
|
|
|
|
|
|
err = cert.SignSuccess()
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return fmt.Errorf("合同签署成功失败: %s", err.Error())
|
|
|
|
|
|
}
|
|
|
|
|
|
err = cert.CompleteCertification()
|
|
|
|
|
|
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)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return fmt.Errorf("合同签署失败: %s", err.Error())
|
|
|
|
|
|
}
|
|
|
|
|
|
reason = "合同签署拒签"
|
|
|
|
|
|
} else if detail.Data.SignFlowStatus == 5 {
|
|
|
|
|
|
err = cert.ContractExpiration()
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return fmt.Errorf("合同签署过期失败: %s", err.Error())
|
|
|
|
|
|
}
|
|
|
|
|
|
reason = "合同签署过期"
|
|
|
|
|
|
} else {
|
|
|
|
|
|
reason = "合同签署中"
|
|
|
|
|
|
}
|
|
|
|
|
|
err = s.aggregateService.SaveCertification(ctx, cert)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return fmt.Errorf("保存认证信息失败: %s", err.Error())
|
|
|
|
|
|
}
|
|
|
|
|
|
return nil
|
|
|
|
|
|
})
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return "", err
|
2025-07-21 15:13:26 +08:00
|
|
|
|
}
|
2025-07-28 01:46:39 +08:00
|
|
|
|
return reason, nil
|
2025-07-21 15:13:26 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-07-28 01:46:39 +08:00
|
|
|
|
// handleContractAfterSignComplete 处理签署完成后的合同
|
|
|
|
|
|
func (s *CertificationApplicationServiceImpl) handleContractAfterSignComplete(ctx context.Context, cert *entities.Certification) error {
|
|
|
|
|
|
// 获取用户的企业信息
|
2025-07-30 00:51:22 +08:00
|
|
|
|
user, err := s.userAggregateService.GetUserWithEnterpriseInfo(ctx, cert.UserID)
|
2025-07-28 01:46:39 +08:00
|
|
|
|
if err != nil {
|
|
|
|
|
|
return fmt.Errorf("加载用户信息失败: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
if user.EnterpriseInfo == nil {
|
|
|
|
|
|
return fmt.Errorf("用户企业信息不存在")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 1. 获取所有已签署合同文件信息
|
|
|
|
|
|
downloadSignedFileResponse, err := s.esignClient.DownloadSignedFile(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
|
|
|
|
|
|
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
|
2025-07-21 15:13:26 +08:00
|
|
|
|
}
|
2025-07-28 01:46:39 +08:00
|
|
|
|
|
|
|
|
|
|
// 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. 保存到合同聚合根
|
|
|
|
|
|
_, err = s.contractAggregateService.CreateContract(
|
|
|
|
|
|
ctx,
|
|
|
|
|
|
user.EnterpriseInfo.ID,
|
|
|
|
|
|
cert.UserID,
|
|
|
|
|
|
fileName,
|
|
|
|
|
|
user_entities.ContractTypeCooperation,
|
|
|
|
|
|
fileId,
|
|
|
|
|
|
qiniuURL,
|
|
|
|
|
|
)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
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))
|
2025-07-21 15:13:26 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-27 16:26:48 +08:00
|
|
|
|
// 合同签署完成后的基础激活流程
|
|
|
|
|
|
return s.completeUserActivationWithoutContract(ctx, cert)
|
2025-07-28 01:46:39 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// downloadFileContent 通过URL下载文件内容
|
|
|
|
|
|
func (s *CertificationApplicationServiceImpl) downloadFileContent(ctx context.Context, fileUrl string) ([]byte, error) {
|
|
|
|
|
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, fileUrl, nil)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return nil, err
|
|
|
|
|
|
}
|
|
|
|
|
|
resp, err := http.DefaultClient.Do(req)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return nil, err
|
|
|
|
|
|
}
|
|
|
|
|
|
defer resp.Body.Close()
|
|
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
|
|
|
|
return nil, fmt.Errorf("下载失败,状态码: %d", resp.StatusCode)
|
2025-07-13 16:36:20 +08:00
|
|
|
|
}
|
2025-07-28 01:46:39 +08:00
|
|
|
|
return io.ReadAll(resp.Body)
|
2025-07-13 16:36:20 +08:00
|
|
|
|
}
|
2025-07-30 00:51:22 +08:00
|
|
|
|
|
|
|
|
|
|
// 添加状态相关的元数据
|
|
|
|
|
|
func (s *CertificationApplicationServiceImpl) AddStatusMetadata(ctx context.Context, cert *entities.Certification) (map[string]interface{}, error) {
|
|
|
|
|
|
metadata := make(map[string]interface{})
|
|
|
|
|
|
metadata = cert.GetDataByStatus()
|
|
|
|
|
|
switch cert.Status {
|
2026-04-02 12:46:42 +08:00
|
|
|
|
case enums.StatusPending, enums.StatusInfoPendingReview, enums.StatusInfoRejected, enums.StatusInfoSubmitted, enums.StatusEnterpriseVerified:
|
2025-07-30 00:51:22 +08:00
|
|
|
|
record, err := s.enterpriseInfoSubmitRecordRepo.FindLatestByUserID(ctx, cert.UserID)
|
|
|
|
|
|
if err == nil && record != nil {
|
2025-07-30 02:26:04 +08:00
|
|
|
|
enterpriseInfo := map[string]interface{}{
|
|
|
|
|
|
"company_name": record.CompanyName,
|
|
|
|
|
|
"legal_person_name": record.LegalPersonName,
|
|
|
|
|
|
"unified_social_code": record.UnifiedSocialCode,
|
|
|
|
|
|
"enterprise_address": record.EnterpriseAddress,
|
|
|
|
|
|
"legal_person_phone": record.LegalPersonPhone,
|
|
|
|
|
|
"legal_person_id": record.LegalPersonID,
|
2026-04-02 12:46:42 +08:00
|
|
|
|
"submit_at": record.SubmitAt.Format(time.RFC3339),
|
2025-07-30 02:26:04 +08:00
|
|
|
|
}
|
|
|
|
|
|
metadata["enterprise_info"] = enterpriseInfo
|
2025-07-30 00:51:22 +08:00
|
|
|
|
}
|
|
|
|
|
|
case enums.StatusCompleted:
|
|
|
|
|
|
// 获取最终合同信息
|
|
|
|
|
|
contracts, err := s.contractAggregateService.FindByUserID(ctx, cert.UserID)
|
|
|
|
|
|
if err == nil && len(contracts) > 0 {
|
|
|
|
|
|
metadata["contract_url"] = contracts[0].ContractFileURL
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return metadata, nil
|
|
|
|
|
|
}
|
2025-09-12 01:15:09 +08:00
|
|
|
|
|
2026-01-27 16:26:48 +08:00
|
|
|
|
// completeUserActivationWithoutContract 创建钱包、API用户并在用户域标记完成认证(不依赖合同信息)
|
|
|
|
|
|
func (s *CertificationApplicationServiceImpl) completeUserActivationWithoutContract(ctx context.Context, cert *entities.Certification) error {
|
2026-04-25 11:59:10 +08:00
|
|
|
|
// 创建钱包:子账号认证通过后不赠送初始余额(初始额度为0)
|
|
|
|
|
|
isSubordinate := false
|
|
|
|
|
|
if s.subordinateRepo != nil {
|
|
|
|
|
|
if ok, err := s.subordinateRepo.IsUserSubordinate(ctx, cert.UserID); err != nil {
|
|
|
|
|
|
s.logger.Warn("检查子账号关系失败,按普通账号处理", zap.String("user_id", cert.UserID), zap.Error(err))
|
|
|
|
|
|
} else {
|
|
|
|
|
|
isSubordinate = ok
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if isSubordinate {
|
|
|
|
|
|
if _, err := s.walletRepo.GetByUserID(ctx, cert.UserID); err != nil {
|
|
|
|
|
|
zeroWallet := finance_entities.NewWallet(cert.UserID, decimal.Zero)
|
|
|
|
|
|
if _, createErr := s.walletRepo.Create(ctx, *zeroWallet); createErr != nil {
|
|
|
|
|
|
s.logger.Error("创建子账号钱包失败", zap.String("user_id", cert.UserID), zap.Error(createErr))
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
} else if _, err := s.walletAggregateService.CreateWallet(ctx, cert.UserID); err != nil {
|
2026-01-27 16:26:48 +08:00
|
|
|
|
s.logger.Error("创建钱包失败", zap.String("user_id", cert.UserID), zap.Error(err))
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 创建API用户
|
|
|
|
|
|
if err := s.apiUserAggregateService.CreateApiUser(ctx, cert.UserID); err != nil {
|
|
|
|
|
|
s.logger.Error("创建API用户失败", zap.String("user_id", cert.UserID), zap.Error(err))
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 标记用户域完成认证
|
|
|
|
|
|
if err := s.userAggregateService.CompleteCertification(ctx, cert.UserID); err != nil {
|
|
|
|
|
|
s.logger.Error("用户域完成认证失败", zap.String("user_id", cert.UserID), zap.Error(err))
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-25 19:33:56 +08:00
|
|
|
|
// 企业认证成功企业微信通知(仅展示企业名称和联系手机)
|
|
|
|
|
|
if s.wechatWorkService != nil {
|
|
|
|
|
|
user, err := s.userAggregateService.GetUserWithEnterpriseInfo(ctx, cert.UserID)
|
|
|
|
|
|
if err == nil {
|
|
|
|
|
|
companyName := "未知企业"
|
|
|
|
|
|
phone := ""
|
|
|
|
|
|
if user.EnterpriseInfo != nil {
|
|
|
|
|
|
companyName = user.EnterpriseInfo.CompanyName
|
|
|
|
|
|
if user.EnterpriseInfo.LegalPersonPhone != "" {
|
|
|
|
|
|
phone = user.EnterpriseInfo.LegalPersonPhone
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
if user.Phone != "" && phone == "" {
|
|
|
|
|
|
phone = user.Phone
|
|
|
|
|
|
}
|
|
|
|
|
|
content := fmt.Sprintf(
|
|
|
|
|
|
"### 【天远API】企业认证成功\n"+
|
|
|
|
|
|
"> 企业名称:%s\n"+
|
|
|
|
|
|
"> 联系手机:%s\n"+
|
|
|
|
|
|
"> 完成时间:%s\n"+
|
|
|
|
|
|
"\n该企业已完成认证,请相关同事同步更新内部系统。",
|
|
|
|
|
|
companyName,
|
|
|
|
|
|
phone,
|
|
|
|
|
|
time.Now().Format("2006-01-02 15:04:05"),
|
|
|
|
|
|
)
|
|
|
|
|
|
_ = s.wechatWorkService.SendMarkdownMessage(ctx, content)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-27 16:26:48 +08:00
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-12 01:15:09 +08:00
|
|
|
|
// RecognizeBusinessLicense OCR识别营业执照
|
|
|
|
|
|
func (s *CertificationApplicationServiceImpl) RecognizeBusinessLicense(
|
|
|
|
|
|
ctx context.Context,
|
|
|
|
|
|
imageBytes []byte,
|
|
|
|
|
|
) (*responses.BusinessLicenseResult, error) {
|
|
|
|
|
|
s.logger.Info("开始OCR识别营业执照", zap.Int("image_size", len(imageBytes)))
|
|
|
|
|
|
|
|
|
|
|
|
// 调用OCR服务识别营业执照
|
|
|
|
|
|
result, err := s.ocrService.RecognizeBusinessLicense(ctx, imageBytes)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
s.logger.Error("OCR识别营业执照失败", zap.Error(err))
|
|
|
|
|
|
return nil, fmt.Errorf("营业执照识别失败: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 验证识别结果
|
|
|
|
|
|
if err := s.ocrService.ValidateBusinessLicense(result); err != nil {
|
|
|
|
|
|
s.logger.Error("营业执照识别结果验证失败", zap.Error(err))
|
|
|
|
|
|
return nil, fmt.Errorf("营业执照识别结果不完整: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
s.logger.Info("营业执照OCR识别成功",
|
|
|
|
|
|
zap.String("company_name", result.CompanyName),
|
|
|
|
|
|
zap.String("unified_social_code", result.UnifiedSocialCode),
|
|
|
|
|
|
zap.String("legal_person_name", result.LegalPersonName),
|
|
|
|
|
|
zap.Float64("confidence", result.Confidence),
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
return result, nil
|
|
|
|
|
|
}
|