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) (flowId, signUrl string, err error) { // 获取模板信息 templateInfo, err := GetTemplateInfo(&tmplateName) 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 if userType == 1 { flowApproverInfos = buildPersonApprovers(personName, personMobile, personIdCardNumber, recipients) } else { flowApproverInfos = buildOrganizationApprovers(organizationName, recipients) } // 发起合同 resp, err := essapi.CreateFlowByTemplateDirectly(*templateInfo.TemplateName, *templateInfo.TemplateId, flowApproverInfos) 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 } flowId = *resp["flowIds"][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) { agent := utils.SetAgent() 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] // 获取对应电子签信息 var record *models.Useresignrecord record, err = models.GetUseresignRecordByFlowID(flowId) if err != nil { err = fmt.Errorf("获取电子签信息失败") return } // 更新电子签信息状态 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 *string) (templateInfo *essbasic.TemplateInfo, err error) { agent := utils.SetAgent() 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, recipients []*essbasic.Recipient) []*essbasic.FlowApproverInfo { var flowApproverInfos []*essbasic.FlowApproverInfo // 传入个人签署方 flowApproverInfo := &essbasic.FlowApproverInfo{} approverType := "PERSON" flowApproverInfo.ApproverType = &approverType flowApproverInfo.Name = &personName flowApproverInfo.Mobile = &personMobile flowApproverInfo.IdCardType = utils.SetPointValue("ID_CARD") flowApproverInfo.IdCardNumber = &personIdCardNumber // 模板中对应签署方的参与方id flowApproverInfo.RecipientId = recipients[0].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, recipients []*essbasic.Recipient) []*essbasic.FlowApproverInfo { var flowApproverInfos []*essbasic.FlowApproverInfo // 传入企业签署方 flowApproverInfo := &essbasic.FlowApproverInfo{} approverType := "ORGANIZATION" flowApproverInfo.ApproverType = &approverType flowApproverInfo.OrganizationName = &organizationName // 模板中对应签署方的参与方id flowApproverInfo.RecipientId = recipients[0].RecipientId flowApproverInfos = append(flowApproverInfos, flowApproverInfo) return flowApproverInfos } 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)] }