391 lines
12 KiB
Go
391 lines
12 KiB
Go
|
|
package state_machine
|
|||
|
|
|
|||
|
|
import (
|
|||
|
|
"context"
|
|||
|
|
"encoding/json"
|
|||
|
|
"fmt"
|
|||
|
|
|
|||
|
|
"tyapi-server/internal/domains/certification/enums"
|
|||
|
|
|
|||
|
|
"go.uber.org/zap"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
// EsignCallbackData e签宝回调数据结构
|
|||
|
|
type EsignCallbackData struct {
|
|||
|
|
// 基础信息
|
|||
|
|
Action string `json:"action"` // 回调动作类型
|
|||
|
|
FlowID string `json:"flow_id"` // 流程ID
|
|||
|
|
AccountID string `json:"account_id"` // 账户ID
|
|||
|
|
Status string `json:"status"` // 状态
|
|||
|
|
Message string `json:"message"` // 消息
|
|||
|
|
Timestamp int64 `json:"timestamp"` // 时间戳
|
|||
|
|
|
|||
|
|
// 扩展数据
|
|||
|
|
Data map[string]interface{} `json:"data,omitempty"` // 扩展数据
|
|||
|
|
OriginalData string `json:"original_data"` // 原始回调数据
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// EsignCallbackHandler e签宝回调处理器
|
|||
|
|
// 负责处理e签宝的异步回调,将外部回调转换为内部状态转换
|
|||
|
|
type EsignCallbackHandler struct {
|
|||
|
|
stateMachine *CertificationStateMachine
|
|||
|
|
logger *zap.Logger
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// NewEsignCallbackHandler 创建e签宝回调处理器
|
|||
|
|
func NewEsignCallbackHandler(
|
|||
|
|
stateMachine *CertificationStateMachine,
|
|||
|
|
logger *zap.Logger,
|
|||
|
|
) *EsignCallbackHandler {
|
|||
|
|
return &EsignCallbackHandler{
|
|||
|
|
stateMachine: stateMachine,
|
|||
|
|
logger: logger,
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// HandleCallback 处理e签宝回调
|
|||
|
|
func (h *EsignCallbackHandler) HandleCallback(
|
|||
|
|
ctx context.Context,
|
|||
|
|
certificationID string,
|
|||
|
|
callbackData *EsignCallbackData,
|
|||
|
|
) error {
|
|||
|
|
h.logger.Info("接收到e签宝回调",
|
|||
|
|
zap.String("certification_id", certificationID),
|
|||
|
|
zap.String("action", callbackData.Action),
|
|||
|
|
zap.String("flow_id", callbackData.FlowID),
|
|||
|
|
zap.String("status", callbackData.Status))
|
|||
|
|
|
|||
|
|
// 根据动作类型分发处理
|
|||
|
|
switch callbackData.Action {
|
|||
|
|
case "auth_result":
|
|||
|
|
return h.handleAuthResult(ctx, certificationID, callbackData)
|
|||
|
|
case "sign_result":
|
|||
|
|
return h.handleSignResult(ctx, certificationID, callbackData)
|
|||
|
|
case "flow_status":
|
|||
|
|
return h.handleFlowStatus(ctx, certificationID, callbackData)
|
|||
|
|
default:
|
|||
|
|
h.logger.Warn("未知的e签宝回调动作",
|
|||
|
|
zap.String("certification_id", certificationID),
|
|||
|
|
zap.String("action", callbackData.Action))
|
|||
|
|
return fmt.Errorf("未知的回调动作: %s", callbackData.Action)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// handleAuthResult 处理企业认证结果回调
|
|||
|
|
func (h *EsignCallbackHandler) handleAuthResult(
|
|||
|
|
ctx context.Context,
|
|||
|
|
certificationID string,
|
|||
|
|
callbackData *EsignCallbackData,
|
|||
|
|
) error {
|
|||
|
|
h.logger.Info("处理企业认证结果回调",
|
|||
|
|
zap.String("certification_id", certificationID),
|
|||
|
|
zap.String("flow_id", callbackData.FlowID),
|
|||
|
|
zap.String("status", callbackData.Status))
|
|||
|
|
|
|||
|
|
switch callbackData.Status {
|
|||
|
|
case "success", "verified", "completed":
|
|||
|
|
// 认证成功
|
|||
|
|
_, err := h.stateMachine.TransitionToEnterpriseVerified(
|
|||
|
|
ctx,
|
|||
|
|
certificationID,
|
|||
|
|
callbackData.FlowID,
|
|||
|
|
)
|
|||
|
|
return err
|
|||
|
|
|
|||
|
|
case "failed", "rejected", "error":
|
|||
|
|
// 认证失败
|
|||
|
|
failureReason := h.parseAuthFailureReason(callbackData)
|
|||
|
|
_, err := h.stateMachine.TransitionToInfoRejected(
|
|||
|
|
ctx,
|
|||
|
|
certificationID,
|
|||
|
|
failureReason,
|
|||
|
|
callbackData.Message,
|
|||
|
|
)
|
|||
|
|
return err
|
|||
|
|
|
|||
|
|
default:
|
|||
|
|
h.logger.Warn("未知的企业认证状态",
|
|||
|
|
zap.String("certification_id", certificationID),
|
|||
|
|
zap.String("status", callbackData.Status))
|
|||
|
|
return fmt.Errorf("未知的认证状态: %s", callbackData.Status)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// handleSignResult 处理合同签署结果回调
|
|||
|
|
func (h *EsignCallbackHandler) handleSignResult(
|
|||
|
|
ctx context.Context,
|
|||
|
|
certificationID string,
|
|||
|
|
callbackData *EsignCallbackData,
|
|||
|
|
) error {
|
|||
|
|
h.logger.Info("处理合同签署结果回调",
|
|||
|
|
zap.String("certification_id", certificationID),
|
|||
|
|
zap.String("flow_id", callbackData.FlowID),
|
|||
|
|
zap.String("status", callbackData.Status))
|
|||
|
|
|
|||
|
|
switch callbackData.Status {
|
|||
|
|
case "signed", "completed", "success":
|
|||
|
|
// 签署成功
|
|||
|
|
contractURL := h.extractContractURL(callbackData)
|
|||
|
|
_, err := h.stateMachine.TransitionToContractSigned(
|
|||
|
|
ctx,
|
|||
|
|
certificationID,
|
|||
|
|
contractURL,
|
|||
|
|
)
|
|||
|
|
return err
|
|||
|
|
|
|||
|
|
case "rejected", "refused":
|
|||
|
|
// 用户拒绝签署
|
|||
|
|
_, err := h.stateMachine.TransitionToContractRejected(
|
|||
|
|
ctx,
|
|||
|
|
certificationID,
|
|||
|
|
enums.FailureReasonContractRejectedByUser,
|
|||
|
|
callbackData.Message,
|
|||
|
|
)
|
|||
|
|
return err
|
|||
|
|
|
|||
|
|
case "expired", "timeout":
|
|||
|
|
// 签署超时
|
|||
|
|
_, err := h.stateMachine.TransitionToContractExpired(
|
|||
|
|
ctx,
|
|||
|
|
certificationID,
|
|||
|
|
fmt.Sprintf("合同签署超时: %s", callbackData.Message),
|
|||
|
|
)
|
|||
|
|
return err
|
|||
|
|
|
|||
|
|
case "failed", "error":
|
|||
|
|
// 签署失败
|
|||
|
|
failureReason := h.parseSignFailureReason(callbackData)
|
|||
|
|
_, err := h.stateMachine.TransitionToContractRejected(
|
|||
|
|
ctx,
|
|||
|
|
certificationID,
|
|||
|
|
failureReason,
|
|||
|
|
callbackData.Message,
|
|||
|
|
)
|
|||
|
|
return err
|
|||
|
|
|
|||
|
|
default:
|
|||
|
|
h.logger.Warn("未知的合同签署状态",
|
|||
|
|
zap.String("certification_id", certificationID),
|
|||
|
|
zap.String("status", callbackData.Status))
|
|||
|
|
return fmt.Errorf("未知的签署状态: %s", callbackData.Status)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// handleFlowStatus 处理流程状态回调
|
|||
|
|
func (h *EsignCallbackHandler) handleFlowStatus(
|
|||
|
|
ctx context.Context,
|
|||
|
|
certificationID string,
|
|||
|
|
callbackData *EsignCallbackData,
|
|||
|
|
) error {
|
|||
|
|
h.logger.Info("处理流程状态回调",
|
|||
|
|
zap.String("certification_id", certificationID),
|
|||
|
|
zap.String("flow_id", callbackData.FlowID),
|
|||
|
|
zap.String("status", callbackData.Status))
|
|||
|
|
|
|||
|
|
// 流程状态回调主要用于监控和日志记录
|
|||
|
|
// 实际的状态转换由具体的auth_result和sign_result处理
|
|||
|
|
|
|||
|
|
switch callbackData.Status {
|
|||
|
|
case "started", "processing", "in_progress":
|
|||
|
|
h.logger.Info("e签宝流程进行中",
|
|||
|
|
zap.String("certification_id", certificationID),
|
|||
|
|
zap.String("flow_id", callbackData.FlowID))
|
|||
|
|
|
|||
|
|
case "paused", "suspended":
|
|||
|
|
h.logger.Warn("e签宝流程被暂停",
|
|||
|
|
zap.String("certification_id", certificationID),
|
|||
|
|
zap.String("flow_id", callbackData.FlowID),
|
|||
|
|
zap.String("message", callbackData.Message))
|
|||
|
|
|
|||
|
|
case "cancelled", "terminated":
|
|||
|
|
h.logger.Warn("e签宝流程被取消",
|
|||
|
|
zap.String("certification_id", certificationID),
|
|||
|
|
zap.String("flow_id", callbackData.FlowID),
|
|||
|
|
zap.String("message", callbackData.Message))
|
|||
|
|
|
|||
|
|
default:
|
|||
|
|
h.logger.Info("收到其他流程状态",
|
|||
|
|
zap.String("certification_id", certificationID),
|
|||
|
|
zap.String("status", callbackData.Status))
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// parseAuthFailureReason 解析企业认证失败原因
|
|||
|
|
func (h *EsignCallbackHandler) parseAuthFailureReason(callbackData *EsignCallbackData) enums.FailureReason {
|
|||
|
|
// 根据e签宝返回的错误信息解析失败原因
|
|||
|
|
message := callbackData.Message
|
|||
|
|
|
|||
|
|
// 检查扩展数据中的错误码
|
|||
|
|
if errorCode, exists := callbackData.Data["error_code"]; exists {
|
|||
|
|
switch errorCode {
|
|||
|
|
case "ENTERPRISE_NOT_FOUND", "ORG_NOT_EXISTS":
|
|||
|
|
return enums.FailureReasonEnterpriseNotExists
|
|||
|
|
case "INFO_MISMATCH", "ORG_INFO_ERROR":
|
|||
|
|
return enums.FailureReasonEnterpriseInfoMismatch
|
|||
|
|
case "STATUS_ABNORMAL", "ORG_STATUS_ERROR":
|
|||
|
|
return enums.FailureReasonEnterpriseStatusAbnormal
|
|||
|
|
case "LEGAL_PERSON_ERROR", "LEGAL_REP_ERROR":
|
|||
|
|
return enums.FailureReasonLegalPersonMismatch
|
|||
|
|
case "DOCUMENT_INVALID", "ID_CARD_ERROR":
|
|||
|
|
return enums.FailureReasonInvalidDocument
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 根据错误消息文本判断
|
|||
|
|
if message != "" {
|
|||
|
|
if h.containsKeywords(message, []string{"企业不存在", "机构不存在", "not found"}) {
|
|||
|
|
return enums.FailureReasonEnterpriseNotExists
|
|||
|
|
}
|
|||
|
|
if h.containsKeywords(message, []string{"信息不匹配", "信息错误", "mismatch"}) {
|
|||
|
|
return enums.FailureReasonEnterpriseInfoMismatch
|
|||
|
|
}
|
|||
|
|
if h.containsKeywords(message, []string{"状态异常", "status abnormal"}) {
|
|||
|
|
return enums.FailureReasonEnterpriseStatusAbnormal
|
|||
|
|
}
|
|||
|
|
if h.containsKeywords(message, []string{"法定代表人", "legal person", "法人"}) {
|
|||
|
|
return enums.FailureReasonLegalPersonMismatch
|
|||
|
|
}
|
|||
|
|
if h.containsKeywords(message, []string{"证件", "身份证", "document", "id card"}) {
|
|||
|
|
return enums.FailureReasonInvalidDocument
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 默认返回e签宝验证失败
|
|||
|
|
return enums.FailureReasonEsignVerificationFailed
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// parseSignFailureReason 解析合同签署失败原因
|
|||
|
|
func (h *EsignCallbackHandler) parseSignFailureReason(callbackData *EsignCallbackData) enums.FailureReason {
|
|||
|
|
// 根据e签宝返回的错误信息解析失败原因
|
|||
|
|
message := callbackData.Message
|
|||
|
|
|
|||
|
|
// 检查扩展数据中的错误码
|
|||
|
|
if errorCode, exists := callbackData.Data["error_code"]; exists {
|
|||
|
|
switch errorCode {
|
|||
|
|
case "USER_REJECTED", "SIGN_REJECTED":
|
|||
|
|
return enums.FailureReasonContractRejectedByUser
|
|||
|
|
case "FLOW_EXPIRED", "SIGN_EXPIRED":
|
|||
|
|
return enums.FailureReasonContractExpired
|
|||
|
|
case "FLOW_ERROR", "SIGN_PROCESS_ERROR":
|
|||
|
|
return enums.FailureReasonSignProcessFailed
|
|||
|
|
case "ESIGN_ERROR", "SYSTEM_ERROR":
|
|||
|
|
return enums.FailureReasonEsignFlowError
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 根据错误消息文本判断
|
|||
|
|
if message != "" {
|
|||
|
|
if h.containsKeywords(message, []string{"拒绝", "rejected", "refused"}) {
|
|||
|
|
return enums.FailureReasonContractRejectedByUser
|
|||
|
|
}
|
|||
|
|
if h.containsKeywords(message, []string{"过期", "超时", "expired", "timeout"}) {
|
|||
|
|
return enums.FailureReasonContractExpired
|
|||
|
|
}
|
|||
|
|
if h.containsKeywords(message, []string{"流程错误", "process error", "flow error"}) {
|
|||
|
|
return enums.FailureReasonSignProcessFailed
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 默认返回e签宝流程错误
|
|||
|
|
return enums.FailureReasonEsignFlowError
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// extractContractURL 提取合同URL
|
|||
|
|
func (h *EsignCallbackHandler) extractContractURL(callbackData *EsignCallbackData) string {
|
|||
|
|
// 优先从扩展数据中获取
|
|||
|
|
if contractURL, exists := callbackData.Data["contract_url"]; exists {
|
|||
|
|
if url, ok := contractURL.(string); ok && url != "" {
|
|||
|
|
return url
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if downloadURL, exists := callbackData.Data["download_url"]; exists {
|
|||
|
|
if url, ok := downloadURL.(string); ok && url != "" {
|
|||
|
|
return url
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if fileURL, exists := callbackData.Data["file_url"]; exists {
|
|||
|
|
if url, ok := fileURL.(string); ok && url != "" {
|
|||
|
|
return url
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 如果没有找到URL,返回空字符串
|
|||
|
|
h.logger.Warn("未能从回调数据中提取合同URL",
|
|||
|
|
zap.Any("callback_data", callbackData.Data))
|
|||
|
|
|
|||
|
|
return ""
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// containsKeywords 检查文本是否包含关键词
|
|||
|
|
func (h *EsignCallbackHandler) containsKeywords(text string, keywords []string) bool {
|
|||
|
|
for _, keyword := range keywords {
|
|||
|
|
if len(text) >= len(keyword) {
|
|||
|
|
for i := 0; i <= len(text)-len(keyword); i++ {
|
|||
|
|
if text[i:i+len(keyword)] == keyword {
|
|||
|
|
return true
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return false
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ValidateCallbackData 验证回调数据
|
|||
|
|
func (h *EsignCallbackHandler) ValidateCallbackData(callbackData *EsignCallbackData) error {
|
|||
|
|
if callbackData == nil {
|
|||
|
|
return fmt.Errorf("回调数据不能为空")
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if callbackData.Action == "" {
|
|||
|
|
return fmt.Errorf("回调动作不能为空")
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if callbackData.FlowID == "" {
|
|||
|
|
return fmt.Errorf("流程ID不能为空")
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if callbackData.Status == "" {
|
|||
|
|
return fmt.Errorf("状态不能为空")
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ParseCallbackData 解析原始回调数据
|
|||
|
|
func (h *EsignCallbackHandler) ParseCallbackData(rawData string) (*EsignCallbackData, error) {
|
|||
|
|
var callbackData EsignCallbackData
|
|||
|
|
|
|||
|
|
if err := json.Unmarshal([]byte(rawData), &callbackData); err != nil {
|
|||
|
|
h.logger.Error("解析e签宝回调数据失败", zap.Error(err), zap.String("raw_data", rawData))
|
|||
|
|
return nil, fmt.Errorf("解析回调数据失败: %w", err)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 保存原始数据
|
|||
|
|
callbackData.OriginalData = rawData
|
|||
|
|
|
|||
|
|
// 验证数据完整性
|
|||
|
|
if err := h.ValidateCallbackData(&callbackData); err != nil {
|
|||
|
|
return nil, fmt.Errorf("回调数据验证失败: %w", err)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return &callbackData, nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// GetCallbackType 获取回调类型描述
|
|||
|
|
func (h *EsignCallbackHandler) GetCallbackType(action string) string {
|
|||
|
|
types := map[string]string{
|
|||
|
|
"auth_result": "企业认证结果",
|
|||
|
|
"sign_result": "合同签署结果",
|
|||
|
|
"flow_status": "流程状态更新",
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if typeName, exists := types[action]; exists {
|
|||
|
|
return typeName
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return "未知类型"
|
|||
|
|
}
|