package tencent import ( "crypto/aes" "crypto/cipher" "crypto/hmac" "crypto/sha256" "encoding/base64" "encoding/hex" "encoding/json" "fmt" "mtp2_if/config" "mtp2_if/db" "mtp2_if/logger" "mtp2_if/models" "mtp2_if/services/tencent/essapi" "mtp2_if/utils" essbasic "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/essbasic/v20210526" ) func CreateConsoleLoginUrl(agent *essbasic.Agent, proxyOrganizationName string) (response *essbasic.CreateConsoleLoginUrlResponse, err error) { response, err = essapi.CreateConsoleLoginUrl(agent, proxyOrganizationName) return } // InitTencentESS 按用户ID和机构ID创建腾讯电子签业务信息 func InitTencentESS(userId, areaUserId int) (err error) { esignTemplateConfigs, err := models.QueryEsignTemplateConfigs(2, 4) if err != nil { return } session := db.GetEngine().NewSession() defer session.Close() // 开启事务 session.Begin() // 新增 MdUserSwapProtocol err = models.InsertMdUserSwapProtocol(userId, areaUserId, 1, session) if err != nil { session.Rollback() return } // 新增 UserEsignRecord for _, item := range esignTemplateConfigs { err = models.InsertUserEsignRecord(userId, areaUserId, item, session) if err != nil { session.Rollback() return } } return session.Commit() } func InitMdUserSwapProtocol(userId, areaUserId int) (err error) { // 新增 MdUserSwapProtocol err = models.InsertMdUserSwapProtocol(userId, areaUserId, 3, db.GetEngine().NewSession()) if err != nil { return } return } // CreateFlowByTemplateDirectly 通过合同模板创建合同签署流程 func CreateFlowByTemplateDirectly(tmplateName string, userType int, personName, personMobile, personIdCardNumber string, organizationName string, record *models.Useresignrecord, idCardType int) (flowId, signUrl string, err error) { var ( appId *string proxyOrganizationOpenId *string proxyOperatorOpenId *string ) if esignConfig, err := models.GetEsignareatemplateconfig(int(record.AREAUSERID)); err == nil && esignConfig.USERID != 0 { appId = &esignConfig.APPID proxyOrganizationOpenId = &esignConfig.PROXYORGANIZATIONOPENID proxyOperatorOpenId = &esignConfig.PROXYOPERATOROPENID } // 获取模板信息 templateInfo, err := GetTemplateInfo(&tmplateName, appId, proxyOrganizationOpenId, proxyOperatorOpenId) if err != nil { return } if templateInfo == nil { err = fmt.Errorf("获取模板信息失败, tmplateName:%v", tmplateName) logger.GetLogger().Errorf("CreateFlowByTemplateDirectly, %v", err.Error()) return } // 获取模板里面的参与方RecipientId recipients := templateInfo.Recipients if recipients == nil { err = fmt.Errorf("获取模板参与方信息失败, tmplateName:%v", tmplateName) logger.GetLogger().Errorf("CreateFlowByTemplateDirectly, %v", err.Error()) return } // 此处为快速发起的签署方;如果是正式接入,构造签署方,请参考函数内说明,构造需要的场景参数 var flowApproverInfos []*essbasic.FlowApproverInfo for i := range recipients { recipient := recipients[i] // if config.SerCfg.TencentCfg.ProxyOrganizationName == *recipient.RoleName { // if *recipient.SignType != 1 { // // 签署方为本企业,同时不是自动签署时(一般为甲方非自动签署) // flowApproverInfos = append(flowApproverInfos, buildSelfOrganizationApprovers(recipient)...) // } // } else { // // 乙方 // if userType == 1 { // // 个人 // flowApproverInfos = append(flowApproverInfos, buildPersonApprovers(personName, personMobile, personIdCardNumber, idCardType, recipient)...) // } else { // // 企业 // flowApproverInfos = append(flowApproverInfos, buildOrganizationApprovers(organizationName, recipient)...) // } // } if *recipient.SignType != 1 { if userType == 1 { // 个人 flowApproverInfos = append(flowApproverInfos, buildPersonApprovers(personName, personMobile, personIdCardNumber, idCardType, recipient)...) } else { // 企业 flowApproverInfos = append(flowApproverInfos, buildOrganizationApprovers(organizationName, recipient)...) } } } // 判断是否添加发起方角色的填写控件 // 说明:如果合同模板开启了“本企业自动填写”,合同甲乙双方都不能添加填写控件,需要由发起方添加填写控件 fields, err := models.GetEsignTemplateFields(int(record.TEMPLATECONFIGID), 3) if err != nil { return } formFields := buildSelfFormFields(int(record.AREAUSERID), int(record.USERID), fields) // 发起合同 resp, err := essapi.CreateFlowByTemplateDirectly(*templateInfo.TemplateName, *templateInfo.TemplateId, flowApproverInfos, formFields, appId, proxyOrganizationOpenId, proxyOperatorOpenId) if err != nil { return } if resp == nil || len(resp["flowIds"]) == 0 || len(resp["urls"]) == 0 { err = fmt.Errorf("发起合同签署流程失败, tmplateName:%v", tmplateName) logger.GetLogger().Errorf("CreateFlowByTemplateDirectly, %v", err.Error()) return } if len(resp["flowIds"]) > 0 { flowId = *resp["flowIds"][0] } if len(resp["urls"]) > 0 { signUrl = *resp["urls"][0] } // 更新电子签记录表信息状态 record.CONTRACTNO = flowId record.SIGNURL = signUrl record.RECORDSTATUS = 2 if err = record.Update("CONTRACTNO,SIGNURL,RECORDSTATUS"); err != nil { logger.GetLogger().Errorf("CreateFlowByTemplateDirectly, %v", err.Error()) } return } // GetFlowStatus 获取合同状态 func GetFlowStatus(flowId string) (recordStatus int, err error) { // 获取对应电子签信息 var record *models.Useresignrecord record, err = models.GetUseresignRecordByFlowID(flowId) if err != nil { err = fmt.Errorf("获取电子签信息失败") return } var ( appId *string proxyOrganizationOpenId *string proxyOperatorOpenId *string ) if esignConfig, err := models.GetEsignareatemplateconfig(int(record.AREAUSERID)); err == nil && esignConfig.USERID != 0 { appId = &esignConfig.APPID proxyOrganizationOpenId = &esignConfig.PROXYORGANIZATIONOPENID proxyOperatorOpenId = &esignConfig.PROXYOPERATOROPENID } agent := utils.SetAgent(appId, proxyOrganizationOpenId, proxyOperatorOpenId) response, err := essapi.DescribeFlowDetailInfo(agent, []*string{&flowId}) if err == nil { if len(response.Response.FlowInfo) == 0 { err = fmt.Errorf("获取合同明细失败") return } flowDetailInfo := response.Response.FlowInfo[0] // 更新电子签信息状态 if *flowDetailInfo.FlowStatus == "ALL" { recordStatus = 3 } if *flowDetailInfo.FlowStatus == "REJECT" { recordStatus = 4 } if recordStatus == 0 { err = fmt.Errorf("合同状态异常") return } record.RECORDSTATUS = int32(recordStatus) if err = record.Update("RECORDSTATUS"); err != nil { logger.GetLogger().Errorf("GetFlowStatus, %v", err.Error()) } if recordStatus == 3 { // 更新用户掉期协议签署表 UpdateMdUserSwapProtocol(flowId) } } return } func UpdateMdUserSwapProtocol(flowId string) (err error) { // 获取对应的电子签记录 var record *models.Useresignrecord record, err = models.GetUseresignRecordByFlowID(flowId) if err != nil { logger.GetLogger().Errorf("UpdateMdUserSwapProtocol, 获取对应的电子签记录失败:%v", err.Error()) return } // 获取此用户对应机构的电子签记录列表 records, err := models.QueryUsereSignRecords(int(record.USERID), int(record.AREAUSERID), nil, nil, nil) if err == nil { // 所有合同签署完成后,更新用户掉期协议签署表 flag := true for _, item := range records { if item.RECORDSTATUS != 3 { flag = false break } } if flag { // 获取对应用户掉期协议签署记录 var datas []models.Mduserswapprotocol datas, err = models.QueryMdUserSwapProtocol(int(record.USERID), &record.AREAUSERID) if err == nil { if len(datas) > 0 { data := datas[0] // 获取用户信息,如果是用户所属机构则改状态为 4:已审核,否则改为 3:已签署 var userAccount *models.Useraccount if userAccount, err = models.GetUserAccount(int(record.USERID)); err == nil { status := 4 if userAccount.Memberuserid != record.AREAUSERID { status = 3 } data.PROTOCOLSTATUS = int32(status) err = data.Update("PROTOCOLSTATUS") } } } else { logger.GetLogger().Errorf("UpdateMdUserSwapProtocol, 获取对应用户掉期协议签署记录失败:%v", err.Error()) } } } else { logger.GetLogger().Errorf("UpdateMdUserSwapProtocol, 获取对应的机构电子签记录失败:%v", err.Error()) } return } // GetTemplateInfo 获取模板信息 func GetTemplateInfo(contractName, appId, proxyOrganizationOpenId, proxyOperatorOpenId *string) (templateInfo *essbasic.TemplateInfo, err error) { agent := utils.SetAgent(appId, proxyOrganizationOpenId, proxyOperatorOpenId) templatesResp, err := essapi.DescribeTemplates(agent, contractName) if err == nil { if len(templatesResp.Response.Templates) > 0 { templateInfo = templatesResp.Response.Templates[0] } else { err = fmt.Errorf("获取模板信息失败") } } return } // buildPersonApprovers 构造个人签署人 - 以BtoC为例, 实际请根据自己的场景构造签署方、控件 func buildPersonApprovers(personName, personMobile, personIdCardNumber string, idCardType int, recipient *essbasic.Recipient) []*essbasic.FlowApproverInfo { var flowApproverInfos []*essbasic.FlowApproverInfo // 传入个人签署方 flowApproverInfo := &essbasic.FlowApproverInfo{} approverType := "PERSON" flowApproverInfo.ApproverType = &approverType flowApproverInfo.Name = &personName flowApproverInfo.Mobile = &personMobile if idCardType == 0 { flowApproverInfo.IdCardType = utils.SetPointValue("ID_CARD") } else if idCardType == 1 { flowApproverInfo.IdCardType = utils.SetPointValue("HONGKONG_AND_MACAO") } else { flowApproverInfo.IdCardType = utils.SetPointValue("ID_CARD") } flowApproverInfo.IdCardNumber = &personIdCardNumber // 模板中对应签署方的参与方id flowApproverInfo.RecipientId = recipient.RecipientId flowApproverInfos = append(flowApproverInfos, flowApproverInfo) // 传入企业静默签署,此处需要在config.php中设置一个持有的印章值serverSignSealId // flowApproverInfos = append(flowApproverInfos, BuildServerSignApprover()) // 内容控件填充结构,详细说明参考 // https://cloud.tencent.com/document/api/1420/61525#FormField return flowApproverInfos } // buildOrganizationApprovers 构造企业签署人 func buildOrganizationApprovers(organizationName string, recipient *essbasic.Recipient) []*essbasic.FlowApproverInfo { var flowApproverInfos []*essbasic.FlowApproverInfo // 传入企业签署方 flowApproverInfo := &essbasic.FlowApproverInfo{} approverType := "ORGANIZATION" flowApproverInfo.ApproverType = &approverType flowApproverInfo.OrganizationName = &organizationName // 模板中对应签署方的参与方id flowApproverInfo.RecipientId = recipient.RecipientId flowApproverInfos = append(flowApproverInfos, flowApproverInfo) return flowApproverInfos } // buildSelfOrganizationApprovers 构造本企业签署人 func buildSelfOrganizationApprovers(recipient *essbasic.Recipient) []*essbasic.FlowApproverInfo { var flowApproverInfos []*essbasic.FlowApproverInfo // 传入企业签署方 flowApproverInfo := &essbasic.FlowApproverInfo{} approverType := "ORGANIZATION" flowApproverInfo.ApproverType = &approverType flowApproverInfo.OrganizationOpenId = &config.SerCfg.TencentCfg.ProxyOrganizationOpenId // 本企业OpenID // 模板中对应签署方的参与方id flowApproverInfo.RecipientId = recipient.RecipientId flowApproverInfo.OpenId = &config.SerCfg.TencentCfg.ProxyOperatorOpenId // 本企业员工OpenID flowApproverInfos = append(flowApproverInfos, flowApproverInfo) return flowApproverInfos } // buildSelfFormFields 构造本企业填写控件 // selfUserId 本企业用户ID // userId 乙方用户ID func buildSelfFormFields(selfUserId int, userId int, fields []models.Esigntemplatefield) (formFields []*essbasic.FormField) { formFields = make([]*essbasic.FormField, 0) // 获取本企业信息(合同发起方,一般为交易所或合同所属机构) selfuserInfo, err := models.GetUserInfo(selfUserId) if err != nil { return } userInfo, err := models.GetUserInfo(userId) if err != nil { return } key, _ := hex.DecodeString(utils.AESSecretKey) for _, item := range fields { filedName := item.FIELDNAME switch filedName { case "甲方地址": address := selfuserInfo.Province + selfuserInfo.City + selfuserInfo.District + selfuserInfo.Address formFields = append(formFields, &essbasic.FormField{ ComponentName: &filedName, ComponentValue: &address, }) case "甲方邮箱": email := "" if len(selfuserInfo.Email) > 0 { // 手机号码解密 if h, err := hex.DecodeString(selfuserInfo.Email); err == nil { // hex -> []byte if d, err := utils.AESDecrypt(h, key); err == nil { email = string(d) } } } formFields = append(formFields, &essbasic.FormField{ ComponentName: &filedName, ComponentValue: &email, }) case "甲方电话号码": telphone := "" if len(selfuserInfo.Email) > 0 { // 手机号码解密 if h, err := hex.DecodeString(selfuserInfo.Telphone); err == nil { // hex -> []byte if d, err := utils.AESDecrypt(h, key); err == nil { telphone = string(d) } } } formFields = append(formFields, &essbasic.FormField{ ComponentName: &filedName, ComponentValue: &telphone, }) case "乙方地址": address := userInfo.Province + userInfo.City + userInfo.District + userInfo.Address formFields = append(formFields, &essbasic.FormField{ ComponentName: &filedName, ComponentValue: &address, }) } } return } func ProcessNotice(content string) { // "{\"MsgId\":\"yDSLWUUckposmdf8UBxiJvuDbgiYRYbj\",\"MsgType\":\"FlowStatusChange\",\"MsgVersion\":\"ThirdPartyApp\",\"MsgData\":{\"ApplicationId\":\"yDwiuUUckpogfoa4UxhigrYChFMdSJQV\",\"ProxyOrganizationOpenId\":\"TJMD\",\"CustomerData\":\"\",\"FlowId\":\"yDSLWUUckposcsthUwvcaGSuV5EKZAzu\",\"FlowName\":\"1000_P_风险揭示书\",\"FlowType\":\"合同\",\"FlowStatus\":\"INIT\",\"FlowMessage\":\"\",\"CreateOn\":1699077064,\"Deadline\":1730613064,\"FlowApproverInfo\":[{\"ProxyOrganizationOpenId\":\"\",\"ProxyOperatorOpenId\":\"\",\"recipientId\":\"yDSLNUUckpos1i71UuGNih5yMGbZij46\",\"RecipientId\":\"yDSLNUUckpos1i71UuGNih5yMGbZij46\",\"PhoneNumber\":\"15914012152\",\"ProxyOrganizationName\":\"\",\"SignOrder\":0,\"ApproveName\":\"曹晓亮\",\"ApproveStatus\":\"PENDING\",\"ApproveMessage\":\"\",\"ApproveTime\":0,\"CaSign\":\"\"}],\"OccurTime\":1699077064,\"CcInfo\":[]}}" m := make(map[string]interface{}) if err := json.Unmarshal([]byte(content), &m); err == nil { // 判断通知类型 msgType, _ := m["MsgType"].(string) if msgType == "FlowStatusChange" { // 合同相关回调 // https://qian.tencent.com/developers/partner/callback_types_contracts_sign msgData, _ := m["MsgData"].(map[string]interface{}) flowId, _ := msgData["FlowId"].(string) flowStatus, _ := msgData["FlowStatus"].(string) if flowStatus == "ALL" || flowStatus == "REJECT" { // 更新电子签合同状态 if record, err := models.GetUseresignRecordByFlowID(flowId); err == nil { if flowStatus == "ALL" { record.RECORDSTATUS = 3 } else { record.RECORDSTATUS = 4 } if err = record.Update("RECORDSTATUS"); err != nil { logger.GetLogger().Errorf("ProcessNotice, %v", err.Error()) } if record.RECORDSTATUS == 3 { // 更新用户掉期协议签署表 UpdateMdUserSwapProtocol(flowId) } } } } } } // VerifySign 电子签通知推送验签 func VerifySign(payload, signFromHeader string) bool { // 验证签名 hash := "sha256=" + hmacsha256hex(payload, config.SerCfg.TencentCfg.SignToken) return hash == signFromHeader } // DecryptContent 电子签通知推送内容解密 func DecryptContent(payload string) (content string, err error) { // string -> json m := make(map[string]string) err = json.Unmarshal([]byte(payload), &m) if err != nil { return } encrypt, ok := m["encrypt"] if !ok { err = fmt.Errorf("电子签通知推送内容解密失败") logger.GetLogger().Errorf("DecryptContent, %v", err.Error()) return } // base64解密 crypted, err := base64.StdEncoding.DecodeString(encrypt) if err != nil { logger.GetLogger().Errorf("base64 DecodeString returned: %s", err) return } b, err := aesDecrypt(crypted, []byte(config.SerCfg.TencentCfg.SignKey)) if err != nil { logger.GetLogger().Errorf("AesDecrypt returned: %s", err) return } content = string(b) return } // Hmacsha256hex hmac sha256 func hmacsha256hex(s, key string) string { hashed := hmac.New(sha256.New, []byte(key)) hashed.Write([]byte(s)) return hex.EncodeToString(hashed.Sum(nil)) } // 使用callbackKey解密 func aesDecrypt(crypted, key []byte) ([]byte, error) { block, err := aes.NewCipher(key) if err != nil { return nil, err } blockSize := block.BlockSize() blockMode := cipher.NewCBCDecrypter(block, key[:blockSize]) origData := make([]byte, len(crypted)) blockMode.CryptBlocks(origData, crypted) origData = pkcs7UnPadding(origData) return origData, nil } // PKCS7UnPadding 去除填充 func pkcs7UnPadding(origData []byte) []byte { length := len(origData) unPadding := int(origData[length-1]) return origData[:(length - unPadding)] }