274 lines
9.8 KiB
Go
274 lines
9.8 KiB
Go
|
|
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,
|
|||
|
|
}
|
|||
|
|
}
|